导读:中篇的难点不是知识点多,而是这些知识点互相会串。面向对象、equals/hashCode、String 常量池看起来分散,面试里往往会被连着追问。
系列阅读:上篇:语法、数据类型与方法 | 下篇:异常、泛型、反射与 I/O
一、面向对象和面向过程,到底差在哪
很多人背这道题的时候容易空。更实用的答法是:面向过程更关注步骤,面向对象更关注对象和对象之间的职责分工。前者适合把任务拆成流程,后者适合构建可维护、可扩展的系统。
所以,面向对象的优势通常落在这三点:
- 代码结构更稳定,维护成本更低。
- 抽象得更好,复用性更强。
- 随着需求变复杂,更容易做扩展而不是推翻重写。
面试里别把它讲成“面向对象一定比面向过程高级”。它们只是组织复杂度的方式不同,简单任务用过程式写法反而更直接。
二、对象、引用和构造方法,别在最开始就混了
new 出来的是对象实例,对象引用则是保存地址值的变量。引用可以为空,也可以多个引用同时指向同一个对象,这就是后面很多“修改一个对象为什么另一个也变了”的根源。
构造方法相关的高频点也就三个:
- 构造方法名必须和类名一致。
- 构造方法没有返回值,也不能写 void。
- 如果你没有手写构造方法,编译器会提供默认无参构造;一旦你自己声明了构造方法,这个默认无参构造就不会再自动存在。
所以,项目里一旦手动写了有参构造,又希望框架或序列化组件能无参实例化对象,通常还要补回无参构造。
三、封装、继承、多态,面试别只背定义
这三大特征每个人都能背,但拉开差距的是能不能把它们落到代码行为上。
- 封装:隐藏内部状态,对外只暴露必要操作。真正价值是降低误用风险,而不是单纯把字段设成 private。
- 继承:建立“是什么”的关系,复用共性逻辑,但不能为了少写代码乱继承。
- 多态:父类引用指向子类对象,方法调用在运行时决定真正执行哪个实现。
面试最常见的一句补充是:多态能提高扩展性,但父类引用只能直接调用父类中声明过的方法。也就是说,子类特有的方法不能靠父类引用直接访问。
四、接口和抽象类,不是二选一的背题
很多人把这道题讲成列表对比,其实更容易记的方法是先看设计意图:接口更像能力约束,抽象类更像共享模板。
| 维度 | 接口 | 抽象类 |
|---|---|---|
| 设计定位 | 定义行为规范 | 沉淀共性代码和状态 |
| 继承实现 | 一个类可实现多个接口 | 一个类只能继承一个抽象类 |
| 成员变量 | 默认 public static final | 可定义普通成员变量 |
| 方法能力 | 可有 default、static、private 方法 | 可有抽象方法和普通方法 |
如果你在业务里需要一套统一能力规范,比如“可缓存、可导出、可序列化”,接口通常更合适;如果你已经有一批子类需要共享字段和默认实现,抽象类更顺手。
五、深拷贝、浅拷贝、引用拷贝,核心只看一件事
真正要分清的不是术语,而是内部引用对象有没有被复制。
- 引用拷贝:只是多一个引用,底层还是同一个对象。
- 浅拷贝:外层对象复制了,但内部引用字段仍然共享。
- 深拷贝:外层对象和内部引用对象都复制了一份。
所以,一旦对象里还有 Address、Profile、OrderItem 这种引用属性,只做 Object.clone 的默认实现通常是不够的。
Person p1 = ...;
Person p2 = p1.clone();
// 如果下面是 true,说明内部引用对象仍然共享,属于浅拷贝
System.out.println(p1.getAddress() == p2.getAddress());
六、Object 类最该掌握的,不是把 11 个方法全背出来
Object 当然是所有类的父类,但面试真正高频的是这几组关系:
- equals 和 == 的区别。
- hashCode 的作用,以及为什么要和 equals 一起重写。
- toString 的价值。
- wait、notify、notifyAll 的使用语义。
== 对基本类型比较值,对引用类型比较地址。equals 默认也是比较地址,只不过很多类会重写它,让它改为比较“内容”。String 就是最典型的例子。
而 hashCode 的意义,不在于“给对象一个编号”,而在于让哈希容器先快速定位桶位,再在桶内用 equals 做精确比较。也正因为这样,才会有那条经典规则:如果两个对象 equals 相等,那么它们的 hashCode 也必须相等。
面试易错点:“hashCode 相等的两个对象一定相等”这句话是错的。hashCode 相等只能说明可能发生了哈希碰撞,还要继续看 equals。
七、String、StringBuilder、StringBuffer 该怎么选
这道题本质只看两个维度:可变性和线程安全。
| 类型 | 是否可变 | 线程安全 | 适用场景 |
|---|---|---|---|
| String | 不可变 | 天然安全 | 少量字符串处理 |
| StringBuilder | 可变 | 否 | 单线程高频拼接 |
| StringBuffer | 可变 | 是 | 多线程下共享缓冲区 |
String 的不可变性也经常被追问。更准确的答法是:不是因为它底层数组被 final 修饰就完了,而是因为底层存储不对外暴露、类本身不可继承、修改操作会返回新对象,这几层约束一起保证了不可变语义。
八、字符串常量池、intern 和 new String("abc") 是 String 面试的核心
字符串常量池的目标只有一个:复用相同内容的字符串,减少重复创建。因此,字面量形式创建的字符串,会优先进入常量池;new String 则一定会在堆上再创建一个新对象。
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a.equals(c)); // true
再往前一步,intern 的作用可以理解成:帮你拿到常量池中那份“标准引用”。如果池里已经有同内容字符串,就直接返回那份引用;没有的话,就把当前字符串内容放进池里。
另外,字符串拼接题也很经典。简单说就是:
- 编译期能确定结果的常量拼接,会发生常量折叠,直接进入常量池。
- 运行期变量拼接,一般会转成 StringBuilder 的 append 逻辑。
- 循环内大量用 + 拼接字符串,不如手写 StringBuilder 稳定。
九、中篇最值得记住的答题主线
如果你只想留住一条主线,那就是:对象如何被创建、如何被引用、如何判断相等、如何被字符串化和哈希化。这条线一旦建立起来,OOP、Object、String 就不会再显得零碎。
- 先讲对象和引用、构造方法。
- 再讲封装继承多态和接口/抽象类。
- 接着讲 equals/hashCode 和拷贝语义。
- 最后把 String 不可变、常量池、intern、拼接优化收尾。
一句话总结:中篇的重点不是背 API,而是理解“对象语义”。只要对象语义清楚,Java 中一大批高频题都会显得非常自然。