在面试中回答时,建议按照 “最常用 较常用 高级/偏门” 的顺序由浅入深地介绍。

Java 创建对象的 5 种方式

1. 使用 new 关键字 (最常用)

  • 描述: 这是最常见、最标准的创建对象方式。
  • 原理: JVM 会为对象分配内存,并调用构造函数进行初始化。
  • 示例: User user = new User();

2. 使用反射机制 (框架常用)

反射有两种具体方式:

  • A. 使用 Class 类的 newInstance() 方法:
    • 示例: User user = (User) Class.forName("com.example.User").newInstance();
    • 限制: 只能调用无参构造函数,且该构造函数必须是 public 的。如果类没有无参构造或构造函数私有,会抛异常。
  • B. 使用 Constructor 类的 newInstance() 方法:
    • 示例: Constructor<User> constructor = User.class.getConstructor(); User user = constructor.newInstance();
    • 优势: 更强大。它可以调用有参构造函数,甚至可以通过 setAccessible(true) 暴力破解私有构造函数。

3. 使用 clone() 方法 (复制对象)

  • 描述: 不通过构造函数,而是直接从内存中复制一个现有的对象。
  • 条件: 目标类必须实现 Cloneable 接口并重写 Object.clone() 方法。
  • 特点: 这是一个浅拷贝(除非自己手动实现深拷贝逻辑),且不会调用构造函数
  • 示例: User copyUser = (User) originalUser.clone();

4. 使用序列化与反序列化 (深拷贝常用)

  • 描述: 将对象转为字节流,再还原为新对象。
  • 条件: 类必须实现 Serializable 接口。
  • 特点: 不会调用构造函数。这是实现深拷贝的标准手段之一。
  • 示例: 通过 ObjectInputStreamreadObject() 方法。

5. 使用第三方库 (如 Unsafe) (极少直接使用)


面试加分总结表

方式是否调用构造函数?核心特点适用场景
new 关键字最简单、最标准日常开发 99% 的场景
反射 (Reflection)动态性强Spring 等框架底层、工厂模式
clone()内存级复制 (浅拷贝)快速复制对象副本
反序列化深拷贝网络传输、缓存、深拷贝
Unsafe底层、危险框架底层黑科技

2. New出的对象什么时候回收?

这是一个考察 JVM 垃圾回收(GC)机制 的核心问题。

面试回答逻辑

一句话直接回答: 通过 new 创建的对象,由 Java 虚拟机(JVM)的垃圾回收器自动管理。简单来说,当一个对象没有任何引用指向它(即“不可达”)时,它就有可能被回收。

1. 怎么判断对象“该死了”?(核心算法)

JVM 主要通过两种算法来判断对象是否存活:

  • A. 引用计数法 (Reference Counting) [了解即可]
    • 原理: 给对象添加一个引用计数器,每有一个地方引用它,计数器+1;引用失效,计数器-1。当计数器为 0 时,对象可回收。
    • 致命缺陷: 无法解决 “循环引用” 问题(例如 A 引用 B,B 引用 A,但没其他人引用它俩,计数器都不为 0,导致无法回收)。
    • 注: 现代主流 JVM(如 HotSpot)一般不使用这种方法。
  • B. 可达性分析算法 (Reachability Analysis) [标准答案]
    • 原理: 以一系列被称为 “GC Roots” 的对象作为起始点,从这些节点开始向下搜索。
    • 判定: 如果一个对象到 GC Roots 没有任何引用链相连(即不可达),则证明该对象是无用的,可以被回收。
2. 什么是 GC Roots?(加分点)

面试官通常会追问:“哪些对象可以作为 GC Roots?” 常见的有:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象(比如方法里定义的局部变量 User user = new User())。
  2. 方法区中类静态属性引用的对象 (static 变量)。
  3. 方法区常量引用的对象 (final 常量)。
  4. 本地方法栈中 JNI(即 Native 方法)引用的对象。
3. 对象最后的“遗言”:finalize()
  • 机制: 在对象被标记为不可达之后,如果该对象重写了 finalize() 方法且未被执行过,JVM 会把它放入一个队列中。
  • 自救: 对象可以在 finalize() 中通过重新与引用链建立联系(比如把自己赋值给某个全局变量)来“复活”。
  • 现状: 极不推荐使用。因为它的执行时间不确定,开销大,且容易导致性能问题。现代 Java 开发中通常忽略它的存在。

总结表格
判定算法核心逻辑缺点现状
引用计数法计数器为 0 即回收无法解决循环引用主流 JVM 不用
可达性分析从 GC Roots 搜索不到即回收实现稍繁琐主流标准

3. 如何获取私有对象?

在面试中,这个问题通常是在考察你对 封装性(Encapsulation) 的理解以及对 反射机制(Reflection) 的掌握程度。

1. 常规方式:通过公共方法 (Getter)

这是符合面向对象设计原则的“正规军”做法。

  • 原理: 类的设计者为了安全性,将属性设为 private,但同时提供 public 的访问器方法(如 getXxx())。
  • 适用场景: 正常的业务开发。
  • 代码示例:
    // 外部直接调用 public 方法获取内部 private 属性
    String value = obj.getPrivateField(); 
    

2. 暴力方式:通过反射机制 (Reflection) —— 面试重点

7.反射与注解

这是绕过 Java 访问控制检查的“黑客”做法,也是面试官最想听到的答案。

  • 原理: 利用 Java 的反射 API,在运行时动态获取类的元信息,并强制取消访问检查
  • 核心步骤(三步走):
    1. 获取 Class 对象: obj.getClass()
    2. 获取私有字段: 使用 getDeclaredField("fieldName")
      • 注意: 不能用 getField(),因为它只能获取 public 字段。
    3. 暴力破解(关键点): 调用 field.setAccessible(true)
      • 这一步关闭了 Java 的访问安全检查机制,允许你访问私有成员。
  • 代码示例: Java
    Class<?> clazz = obj.getClass();
    Field field = clazz.getDeclaredField("privateField"); // 1. 获取字段
    field.setAccessible(true);                            // 2. 暴力破解:设为可访问
    String value = (String) field.get(obj);               // 3. 获取值
    

总结表格

方式安全性复杂度是否破坏封装评价
Getter 方法推荐的标准写法
反射机制强大的非常规手段,主要用于框架底层