1. 深拷贝和浅拷贝的区别?
、
浅拷贝只复制对象的“表层”(引用地址),深拷贝则复制对象的“深层”(实际内容)。
- 浅拷贝 (Shallow Copy):
- 基本数据类型(如
int,boolean):复制的是具体的值。 - 引用数据类型(如
String,List,对象):复制的是内存地址(引用)。 - 结果: 新对象和原对象指向堆内存中的同一个实体。如果你修改了新对象里的引用类型数据,原对象也会被改变。
- 默认行为:
Object类提供的clone()方法默认是浅拷贝。
- 基本数据类型(如
- 深拷贝 (Deep Copy):
- 基本数据类型:复制具体的值。
- 引用数据类型:创建一个新的对象,并复制原对象中该引用指向的具体内容。
- 结果: 新对象和原对象在堆内存中完全独立。修改新对象,不会影响原对象。
2. 如何实现深拷贝?
面试中常问“有哪些方法可以实现深拷贝”,主要有以下三种常见方式:
- 重写 clone 方法,序列化与反序列化,json化再返 json 化
- 重写
clone()方法(比较麻烦):- 实现
Cloneable接口。 - 重写
clone()方法。在方法内部,不仅要调用super.clone(),还需要手动对类中的每一个引用类型的成员变量再次调用clone()。 - 缺点: 代码繁琐,如果对象嵌套层级很深,代码会非常难写。
- 实现
- 使用序列化 (Serialization)(推荐,最常用):
- 将对象序列化为字节流(Write),再反序列化为新对象(Read)。
- 可以使用 Java 原生的
ObjectOutputStream/ObjectInputStream,要求对象实现Serializable接口。 - 原理: 序列化会把对象及其引用的所有对象都转成二进制流,重新生成时自然是全新的对象。
- 缺点: 性能相对较差。
- 使用第三方库(实际开发中最常用):
- JSON 转换: 先把对象转成 JSON 字符串,再转回对象(如使用 Jackson, Gson, FastJson)。
MyObject copy = JSON.parseObject(JSON.toJSONString(original), MyObject.class);
- 工具类: 如 Apache Commons Lang 的
SerializationUtils.clone()。
- JSON 转换: 先把对象转成 JSON 字符串,再转回对象(如使用 Jackson, Gson, FastJson)。
3. final 关键字 (不可变性)
一句话总结: final 代表“最终的”、“不可更改的”。它可以修饰变量、方法和类。
核心作用域与区别:
- 修饰变量 (最常考):
- 基本数据类型(如
int,double):数值一旦初始化,就不能被修改(常量)。 - 引用数据类型(如
Object,List):引用地址不可变,但对象内部的数据可以改变。- 话术示例: “比如
final List list = new ArrayList();,我不能让list指向另一个新的ArrayList,但我可以调用list.add()往里面加数据。”
- 话术示例: “比如
- 基本数据类型(如
- 修饰方法:
- 该方法不能被子类重写 (Override)。
- 注意:
private方法隐式地被指定为final,因为子类无法访问它,自然也无法重写。
- 修饰类:
- 该类不能被继承。
- 典型例子:
String类就是final的,主要是为了保证字符串的不可变性和安全性。
4. * static 关键字 (类级别/静态)(比任何构造方法(new)都要早执行)
静态变量在堆中的 Class 对象中,而不在常量池中。
一句话总结: static 表示“静态的”,意味着成员属于类本身,而不是属于某个具体的对象实例。它先于对象存在。
核心作用域与特性:
- 静态变量 (Static Variable):
- 被所有对象共享,在内存中只有一份副本。
- 当类被加载时初始化。
- 静态方法 (Static Method):
- 可以直接通过
类名.方法名调用,不需要创建对象。 - 重要限制: 静态方法中不能使用
this或super关键字,也不能直接访问非静态的成员变量或方法。(原因:静态方法加载时,对象可能还没创建)。
- 可以直接通过
- 静态代码块 (Static Block):
static { ... }- 在类加载时执行,且只执行一次。通常用于初始化静态资源。
- 静态内部类 (Static Inner Class):
- 不需要依赖外部类的实例即可创建。
- 只能访问外部类的静态成员。
5. 面试高频考察点:执行顺序
面试官经常会给一段代码,让你判断 static 代码块、构造代码块和构造方法的执行顺序。
口诀: 父类早于子类,静态早于非静态,代码块早于构造方法。
详细执行流程:
- 父类 静态代码块 (类加载时,只一次)
- 子类 静态代码块 (类加载时,只一次)
- 父类 非静态代码块 (每次 new 时)
- 父类 构造方法
- 子类 非静态代码块 (每次 new 时)
- 子类 构造方法