1. java的基本数据类型、包装类型
java常见的基本数据类型有:byte、short、int、long、double、float、char、boolean
对应的包装类型有:Byte、Short、Integer、Long、Double、Float、Character、Boolean
二者的主要区别:基础类型存储在栈中,包装类型存储在堆中;基础类型有默认值比如int默认为0,包装类型默认为null,因为是对象;基础数据类型只能进行基本的算术操作和逻辑操作,包装类型提供许多方法和属性,可以进行更复杂的操作;包装类型还能自动装箱和拆箱。
2. 装箱和拆箱的原理和作用
装箱:int 变为 Integer,将基本数据类型转变为包装类型的过程
拆箱:Integet 变为 int,将包装类型转变为基本数据类型的过程
public class demo1 {
public static void main(String[] args) {
// 自动装箱
int x = 5;
Integer y = x;
System.out.println(y);
// 手动装箱
Integer y1 = Integer.valueOf(x);
System.out.println(y1);
// 自动拆箱
Integer x1 = 6;
int a = x1;
System.out.println(a);
// 手动拆箱
int a1 = x1.intValue();
System.out.println(a1);
}
}
作用:java集合框架不能存储基本数据类型,只能存储对象,装箱和拆箱能让基本数据类型在集合中使用更加方便。自动装箱和拆箱可以减少代码的显示转换,让代码更加易懂。
3. String、StringBuffer、StringBuilder的区别
String是不可变的、StringBuffer和StringBuilder是可变的。String和StringBuffer是线程安全的、StringBuilder是非线程安全的。
那为什么String是不可变的呢?
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法;
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
String和StringBuilder创建对象的数量区别
String result = "A" + "B" + "C";
使用 String 拼接、编译器会自动转换为 StringBuilder 实现:
String result = new StringBuilder().append("A").append("B").append("C").toString();
创建的对象:
1个 StringBuilder 对象
toString() 时创建 1个新的 String 对象
常量池中的 “A”、“B”、“C”(首次使用时创建,此处假设已存在)
总计:2个新对象(1个StringBuilder + 1个String)。
显式使用 StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("A").append("B").append("C");
String result = sb.toString();
创建的对象:
1个 StringBuilder 对象(内部创建1个 char[16])
toString() 时创建 1个新的 String 对象
总计:2个新对象(与编译器优化后的 String 拼接一致)。
String result = "";
for (int i = 0; i < N; i++) {
result += "a"; // 每次循环产生新对象
}
使用 String 拼接(低效!)每次循环的实质:
result = new StringBuilder().append(result).append("a").toString();
创建的对象:
循环 N 次,每次创建:
1个 StringBuilder 对象
1个新的 String 对象(由 toString() 生成)
总计:2N个新对象(N个StringBuilder + N个String)。
使用 StringBuilder(高效!)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
sb.append("a");
}
String result = sb.toString();
创建的对象:
1个 StringBuilder 对象(初始 char[16])
扩容时的新数组(若长度超过当前容量):
扩容次数 ≈ log₂(N/16)(按指数扩容)
每次扩容创建 1个新的 char[]
toString() 时创建 1个新的 String 对象
总计:
固定1个 StringBuilder 对象
1个 String 对象
少量 char[] 数组(通常远小于 N)
为什么StringBuffer是线程安全
通过 synchronized 关键字 在关键方法上实现了同步锁机制。
4. String转换Integer原理
public class demo2 {
public static void main(String[] args) {
String content = "11111";
int count1 = Integer.parseInt(content);
System.out.println(count1);
Integer count2 = Integer.valueOf(content);
System.out.println(count2);
Integer count3 = new Integer(content);
System.out.println(count3);
}
}
public static int parseInt(String s) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
int result = 0;
boolean negative = false;
int i = 0, len = s.length();
// 处理负号
char firstChar = s.charAt(0);
if (firstChar == '-') {
negative = true;
i++;
}
while (i < len) {
int digit = s.charAt(i++) - '0'; // 将字符 '1' 转为数字 1
if (digit < 0 || digit > 9) throw new NumberFormatException();
result = result * 10 + digit;
}
return negative ? -result : result;
}
- s.charAt(i) - ‘0’:通过字符 ASCII 值转换成整数(比如 ‘3’ 的 ASCII 是 51,‘0’ 是 48,51-48=3)
- 字符逐个读取并计算:result = result * 10 + digit
- 支持负数
- 抛出异常:如果不是合法数字字符,就抛出 NumberFormatException
Integer.valueOf() 有缓存机制吗?
有,范围是 [-128, 127]
反过来 Integer → String 怎么转?
String s = String.valueOf(666);
System.out.println(s);
String s2 = Integer.toString(777);
System.out.println(s2);
5. 接口和抽象类的区别
接口:使用interface关键字定义,一个类可以实现多个接口,一个接口也可以继承多个接口,用于定义一组不相关类的公共行为,适合API设计,更适合定义能力接口。
抽象类:使用abstract关键字定义,一个类只能继承一个抽象类,用于定义一组相关类的公共行为,适合定义类之间的结构层次,提供公共的实现和状态
当强调 “做什么” 且实现者无关时 → 用接口
当需要 “是什么” 且共享代码时 → 用抽象类
现代 Java 中:接口为主,抽象类为辅(尤其需要默认方法后)
// 接口 - 自动为 public static final
interface Flyable {
int MAX_ALTITUDE = 10000; // 常量
}
// 抽象类 - 常规变量
abstract class Bird {
protected String species; // 实例变量
private static int count; // 静态变量
}
// 接口支持多继承
interface Robot extends Movable, Programmable {
void recharge();
}
// 抽象类单继承
abstract class Sparrow extends Bird implements Flyable {
// 继承Bird + 实现Flyable
}
接口(Interface)定义“能做什么”,是纯行为契约(可多继承),用于声明跨类别的能力;抽象类(Abstract Class)定义“是什么”,是部分实现的类模板(单继承),用于封装相关类的共性代码和状态。优先用接口解耦,需要代码复用时用抽象类。
// 接口:定义行为契约(能做什么)
interface Flyable {
void fly(); // 抽象方法(默认public abstract)
default void land() { // 默认方法(Java8+)
System.out.println("准备降落...");
}
}
// 抽象类:定义基础实现(是什么)
abstract class Bird {
protected String name;
public Bird(String name) {
this.name = name;
}
// 具体方法:所有鸟类共享的实现
public void breathe() {
System.out.println(name + "正在呼吸空气");
}
// 抽象方法:需要子类实现
public abstract void makeSound();
}
// 具体类:继承抽象类 + 实现接口
class Sparrow extends Bird implements Flyable {
public Sparrow() {
super("麻雀");
}
// 实现抽象类的方法
@Override
public void makeSound() {
System.out.println("叽叽喳喳!");
}
// 实现接口的方法
@Override
public void fly() {
System.out.println(name + "正在低空飞行");
}
// 覆盖默认方法
@Override
public void land() {
System.out.println("收起翅膀着陆");
}
}
// 演示类
public class BirdDemo {
public static void main(String[] args) {
Sparrow sparrow = new Sparrow();
// 调用抽象类方法
sparrow.breathe(); // 输出:麻雀正在呼吸空气
sparrow.makeSound();// 输出:叽叽喳喳!
// 调用接口方法
sparrow.fly(); // 输出:麻雀正在低空飞行
sparrow.land(); // 输出:收起翅膀着陆
// 多态演示
Bird myBird = sparrow;
myBird.makeSound(); // 输出:叽叽喳喳!
Flyable flyingThing = sparrow;
flyingThing.fly(); // 输出:麻雀正在低空飞行
}
}
6. java的对象的创建过程
public class MyClass {
private int value = 10; // 实例变量初始化
private static int count = 0; // 静态变量
// 实例初始化块
{
System.out.println("实例初始化块执行");
value = 20;
}
// 静态初始化块
static {
System.out.println("静态初始化块执行");
count = 100;
}
// 构造方法
public MyClass() {
System.out.println("构造方法执行");
value = 30;
}
}
public class Main {
public static void main(String[] args) {
// 对象创建过程从这里开始
MyClass obj = new MyClass();
System.out.println("最终 value 值: " + obj.value);
}
}
1️⃣ 类加载(首次使用时)
检查类是否已加载
若未加载,则执行类加载过程:
加载 → 验证 → 准备 → 解析 → 初始化
静态初始化:
执行静态变量初始化和静态初始化块
输出:静态初始化块执行
📝 注意:类加载只发生一次,后续创建对象时不再重复
2️⃣ 内存分配
JVM 在堆内存中分配对象所需空间
空间大小在类加载后即可确定(包括所有实例变量)
3️⃣ 初始化零值
将所有实例变量设为默认值:
数值类型:0
布尔类型:false
引用类型:null
4️⃣ 设置对象头
创建对象头(Object Header),包含:
Mark Word:哈希码、GC 信息、锁状态等
类型指针:指向类元数据(方法区中)
数组长度(如果是数组)
5️⃣ 执行实例初始化
按顺序执行初始化操作:
实例变量显式初始化
java
value = 10; // 初始化为10
实例初始化块执行
java
{
System.out.println(“实例初始化块执行”);
value = 20; // 修改为20
}
输出:实例初始化块执行
构造方法执行
java
public MyClass() {
System.out.println(“构造方法执行”);
value = 30; // 修改为30
}
输出:构造方法执行
6️⃣ 建立引用
将栈中的变量 obj 指向堆中的对象地址
对象创建完成,可以使用
7. 什么是深克隆、浅克隆,java函数传参是值传递还是引用传递
深克隆:复制对象的值类型属性以及递归复制引用类型属性。修改新对象的引用类型属性不会影响原对象
浅克隆:复制对象的值类型属性,对于引用类型属性,只复制引用。修改新对象的引用类型属性会影响原对象
import java.util.Arrays;
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
// 重写 clone 方法实现浅克隆
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
String name;
int age;
Address address; // 引用类型
String[] hobbies; // 数组类型
public Person(String name, int age, Address address, String[] hobbies) {
this.name = name;
this.age = age;
this.address = address;
this.hobbies = hobbies;
}
// 浅克隆实现
@Override
protected Object shallowClone() throws CloneNotSupportedException {
return super.clone();
}
// 深克隆实现
@Override
protected Object deepClone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
// 克隆引用类型成员
cloned.address = (Address) address.clone();
// 克隆数组(创建新数组)
cloned.hobbies = hobbies.clone();
return cloned;
}
@Override
public String toString() {
return "Name: " + name +
", Age: " + age +
", City: " + address.city +
", Hobbies: " + Arrays.toString(hobbies);
}
}
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr = new Address("Beijing");
String[] hobbies = {"Reading", "Swimming"};
Person original = new Person("Alice", 25, addr, hobbies);
// 浅克隆
Person shallowCopy = (Person) original.shallowClone();
// 深克隆
Person deepCopy = (Person) original.deepClone();
// 修改原始对象的属性
original.name = "Bob";
original.age = 30;
original.address.city = "Shanghai";
original.hobbies[0] = "Gaming";
System.out.println("Original: " + original);
System.out.println("Shallow Copy: " + shallowCopy); // 引用类型被修改
System.out.println("Deep Copy: " + deepCopy); // 保持独立
}
}
在java中,函数传递的机制是基于值传递的(对于基本类型参数和引用类型参数都是基于值传递的)。
对于引用类型也是传递值的拷贝,它只是在函数内部通过引用修改对象的内容,但不能改变引用本身指定对象。
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class PassByValueExample {
public static void main(String[] args) {
// 情况1:基本类型参数传递
int num = 10;
System.out.println("基本类型修改前: " + num); // 10
modifyPrimitive(num);
System.out.println("基本类型修改后: " + num); // 10 (未改变)
// 情况2:引用类型参数传递 - 修改对象内容
Person person = new Person("Alice");
System.out.println("引用类型修改前: " + person.name); // Alice
modifyObjectContent(person);
System.out.println("引用类型修改后: " + person.name); // Bob (内容被修改)
// 情况3:引用类型参数传递 - 尝试改变引用
System.out.println("尝试改变引用前: " + person.name); // Bob
tryChangeReference(person);
System.out.println("尝试改变引用后: " + person.name); // Bob (引用未改变)
}
// 情况1: 基本类型参数传递 - 值拷贝
public static void modifyPrimitive(int value) {
value = 20; // 修改的是副本
}
// 情况2: 引用类型参数传递 - 通过引用修改对象内容
public static void modifyObjectContent(Person p) {
p.name = "Bob"; // 修改的是原对象的内容
}
// 情况3: 引用类型参数传递 - 尝试改变引用本身
public static void tryChangeReference(Person p) {
p = new Person("Charlie"); // 改变的是引用的副本
p.name = "David"; // 修改的是新对象
}
}
8. &与&&区别、==与equals的区别
在Java中,&和&&的核心区别在于短路行为:&&是短路与运算符,当左侧表达式为false时直接跳过右侧计算,更高效安全,适用于条件判断;&是非短路与运算符,始终计算两侧表达式,还可作为位运算符处理整数二进制操作。简单来说:条件判断用&&避免不必要计算(如if(a != null && a.isEmpty())),需要强制计算两侧或位运算时用&。
==:是一个比较操作符,比较的是引用(内存地址)是否相同,可以用于比较基本数据类型和对象的引用。
equals:是Object类中的一个方法,比较两个对象的内容是否相同。但Object类默认情况下equlas方法与==用法相同,但许多类重写equals方法用于比较对象内容是否相同比如String、Integer等。
9.hashcode与quals的区别,为什么重写equals方法要重写hashcode
hashcode:这个方法返回一个哈希码。Object类中默认的是将对象的内存地址转换为一个整数。
equals:这给方法比较的是两个对象的内容是否相同(在重写的情况下),默认的Object比较的是引用和==一样。
如果两个对象的根据equals方法是相同的,那么他们的hashcode的值也要相同。只重写equals方法步重写hashcode方法,会导致在哈希表中查找、删除、插入对象出现问题。(同一个对象多次调用hashcode值应该返回相同的值)
10. this、final、finally、finalize的区别
在Java中,this 是当前对象的引用(用于访问实例成员或调用构造器),final 是修饰符(使变量不可变、方法不可重写、类不可继承),finally 是异常处理块(无论是否异常都必执行,用于资源清理),而**finalize** 是Object类的废弃方法(GC前回调,Java 9+已弃用)。四者本质不同:this指向对象,final强制不变性,finally保证代码执行,finalize是过时的回收钩子。
11. 四种访问修饰符是什么,有什么区别
在Java中,public(所有类可访问)、protected(同包类+子类可访问)、默认(包级)(同包类可访问)、private(仅本类可访问)四种修饰符控制类和成员的可见性,核心区别在于访问范围:public > protected > 默认 > private。
package com.example;
public class AccessModifiersExample {
public int publicVar = 1; // 所有类可见
protected int protectedVar = 2; // 同包类+子类可见
int defaultVar = 3; // 同包类可见(无修饰符)
private int privateVar = 4; // 仅本类可见
public void accessCheck() {
System.out.println(privateVar); // 本类内可访问所有成员
}
}
// 同包类测试
class SamePackageTester {
void test() {
AccessModifiersExample obj = new AccessModifiersExample();
System.out.println(obj.publicVar); // ✓ 可访问
System.out.println(obj.protectedVar); // ✓ 可访问
System.out.println(obj.defaultVar); // ✓ 可访问
// System.out.println(obj.privateVar); // ✗ 编译错误
}
}
// 不同包子类测试
package other;
import com.example.AccessModifiersExample;
class SubClass extends AccessModifiersExample {
void test() {
System.out.println(publicVar); // ✓ 可访问
System.out.println(protectedVar); // ✓ 可访问(子类权限)
// System.out.println(defaultVar); // ✗ 编译错误(非同包)
// System.out.println(privateVar); // ✗ 编译错误
}
}
// 无关类测试
package other;
import com.example.AccessModifiersExample;
class UnrelatedClass {
void test() {
AccessModifiersExample obj = new AccessModifiersExample();
System.out.println(obj.publicVar); // ✓ 可访问
// System.out.println(obj.protectedVar); // ✗ 编译错误(非同包非子类)
// System.out.println(obj.defaultVar); // ✗ 编译错误
// System.out.println(obj.privateVar); // ✗ 编译错误
}
}
12. 面向对象和面向过程区别、面向对象的三大特征
面向过程以步骤为中心(如C语言),将问题分解为线性流程,直接操作数据;面向对象(如Java)以对象为中心,将数据和方法封装成独立实体,通过对象交互解决问题,更符合现实世界模型。
面向对象三大特征:
封装:隐藏对象内部细节(如private变量),通过公共方法(getter/setter)安全访问
继承:子类复用父类特性(如class Dog extends Animal),实现代码复用和层次关系
多态:同一操作对不同对象有不同行为(如父类引用指向子类对象Animal a = new Dog(); a.sound()),提高扩展性
// 封装:隐藏细节,暴露接口
class BankAccount {
private double balance; // 私有属性封装
public void deposit(double amount) { // 公共方法操作数据
if (amount > 0) balance += amount;
}
}
// 继承:子类复用父类功能
class Animal { // 父类
void makeSound() { System.out.println("Some sound"); }
}
class Dog extends Animal { // 继承
@Override void makeSound() { System.out.println("Bark!"); }
}
// 多态:同一接口不同实现
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog(); // 父类引用指向子类对象
Animal animal2 = new Cat(); // 另一个子类对象
animal1.makeSound(); // 输出"Bark!" (动态绑定)
animal2.makeSound(); // 输出"Meow!" (多态行为)
}
}
13.重载和重写的区别
重载是在同一类中定义方法名相同但参数不同(类型/数量/顺序)的方法,用于扩展功能(如print(int)和print(String)),编译时确定调用;
重写是子类覆盖父类方法,要求方法名、参数、返回类型完全相同,用于实现多态(如子类重写Animal.sound()),运行时动态绑定。
class Animal {
// 可被重写的方法
public void makeSound() {
System.out.println("Animal makes sound");
}
// 重载示例:参数数量不同
public void eat() {
System.out.println("Animal eats");
}
public void eat(String food) { // 重载eat()
System.out.println("Animal eats " + food);
}
}
class Dog extends Animal {
// 重写父类方法 (方法签名完全相同)
@Override
public void makeSound() {
System.out.println("Dog barks: Woof!");
}
// 重载示例:参数类型不同
public void fetch() {
System.out.println("Dog fetches stick");
}
public void fetch(String item) { // 重载fetch()
System.out.println("Dog fetches " + item);
}
}
public class Main {
public static void main(String[] args) {
// 重载演示
Dog dog = new Dog();
dog.eat(); // 调用Animal.eat()
dog.eat("bone"); // 调用Animal.eat(String) - 重载
dog.fetch(); // 调用Dog.fetch()
dog.fetch("ball"); // 调用Dog.fetch(String) - 重载
// 重写演示 (多态)
Animal animal = new Dog();
animal.makeSound(); // 运行时绑定Dog.makeSound() - 输出"Woof!"
}
}
14.序列化和反序列化是什么
序列化是将对象转换为字节流的过程(保存游戏存档、微服务通信、缓存),
反序列化是将字节流恢复为对象的过程(读取配置文件、接收网络数据)
本质是对象状态的持久化和重建,与编程语言无关。
import java.io.*;
// 必须实现Serializable接口(标记接口)
class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本控制
private String name;
private transient int age; // transient字段不被序列化
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + " years)";
}
}
public class SerializationDemo {
public static void main(String[] args) {
User user = new User("Alice", 30);
// === 序列化 ===
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.data"))) {
oos.writeObject(user); // 对象→字节流
System.out.println("序列化完成: " + user);
} catch (IOException e) {
e.printStackTrace();
}
// === 反序列化 ===
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.data"))) {
User restoredUser = (User) ois.readObject(); // 字节流→对象
System.out.println("反序列化结果: " + restoredUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
15.什么是泛型,有什么作用,泛型擦除是什么
泛型(Generics) 是Java的类型参数化机制(如List),核心作用是:
-
类型安全(编译时检测类型错误)
-
消除强制转换(自动处理类型转换)
-
代码复用(一套逻辑处理多种类型)
泛型擦除是Java泛型的实现机制:编译器移除泛型类型信息(运行时不可见),保证兼容性但导致无法获取运行时泛型类型等限制。
import java.util.*;
public class GenericsDemo {
// 泛型方法(类型安全 + 消除转换)
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " "); // 无需强制转换
}
System.out.println();
}
public static void main(String[] args) {
// 1. 类型安全集合
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // 编译错误:类型安全保护
// 2. 泛型方法调用
Integer[] ints = {1, 2, 3};
String[] strs = {"Hello", "Generics"};
printArray(ints); // 输出: 1 2 3
printArray(strs); // 输出: Hello Generics
// 3. 泛型擦除验证
List<Integer> intList = new ArrayList<>();
List<String> strList = new ArrayList<>();
System.out.println("运行时类型相同? " +
(intList.getClass() == strList.getClass())); // 输出: true
}
}
16. 什么是反射
反射是Java在运行时动态获取类信息(类名、方法、字段等)并操作对象的能力,它打破了"编译时确定"的限制,允许程序在运行时自省和修改行为。核心是通过Class对象获取元数据,实现:类动态加载、方法动态调用、字段动态访问、绕过访问限制等高级功能。
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 获取类的Class对象(三种方式)
Class<?> clazz = Class.forName("java.util.Date");
// Class<?> clazz = Date.class;
// Class<?> clazz = new Date().getClass();
// 2. 动态创建对象(相当于 new Date())
Object date = clazz.getConstructor().newInstance();
System.out.println("创建对象: " + date);
// 3. 动态调用方法(相当于 date.getTime())
Method getTime = clazz.getMethod("getTime");
long timestamp = (long) getTime.invoke(date);
System.out.println("时间戳: " + timestamp);
// 4. 访问私有字段(演示突破封装)
Class<?> userClass = Class.forName("User");
Object user = userClass.getConstructor(String.class).newInstance("Alice");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true); // 关键:解除private限制
System.out.println("反射获取私有字段: " + nameField.get(user));
// 5. 修改私有字段
nameField.set(user, "Bob");
System.out.println("修改后字段值: " + nameField.get(user));
}
}
// 辅助类
class User {
private String name;
public User(String name) {
this.name = name;
}
}
928

被折叠的 条评论
为什么被折叠?



