面向对象三大特性是什么
候选人小李坐在美团 P6 面试间,面试官翻开简历上"熟练掌握 Java 面向对象编程",直接开炮:
"面向对象三大特性说一下。"
小李脱口而出:"封装、继承、多态。"
面试官点点头:"多态怎么实现的?"
小李开始背书:"通过方法重写和向上转型..."
面试官打断:"我问的是底层怎么实现的。你知道虚方法表吗?"
小李愣了三秒,开始擦汗。
【面试官心理】 三大特性本身不难,但我问"底层怎么实现",是在筛选两类候选人:一类背过八股,能说概念;另一类真正看过 JVM 规范或字节码,能说原理。能说出"虚方法表"、"动态分派"的,基本确认是 P6+。
一、封装 🔴
1.1 问题拆解
封装是把数据(属性)和操作数据的方法(行为)包装在一起,对外只暴露必要的接口,隐藏内部实现细节。
追问链:
- 第一层:为什么要封装?
- 第二层:
private/protected/public/默认 有什么区别? - 第三层:封装破坏的场景是什么?(反射、序列化)
1.2 ❌ 错误示范
候选人原话:"封装就是用 private 修饰属性,然后写 getter 和 setter。"
问题诊断:
- 把"实现方式"当成"概念本质"
- 完全没有说明封装的目的和价值
- 对访问修饰符的理解停留在语法层
面试官内心 OS:"这个候选人只知道写 get/set,没有理解封装为什么存在。"
1.3 标准回答
P5 版本:
封装是隐藏对象的内部状态和实现细节,只通过公开的接口与外界交互。核心是访问控制,
private属性配合 getter/setter 是最常见的实现。
P6 版本:
封装的本质是「最小知识原则」。通过访问修饰符控制暴露范围:
private:仅本类可见默认(package-private):同包可见protected:同包 + 子类可见public:所有类可见封装的价值在于降低耦合,修改内部实现不影响外部调用方。
P7 版本(加分点):
封装并不只是 getter/setter,还体现在:
- 接口设计:暴露行为而非状态,如
account.deposit(100)而非account.setBalance(account.getBalance() + 100)- 不可变对象:所有字段
private final,无 setter,如String- 防御性拷贝:避免外部通过引用修改内部状态
能说出"暴露行为而非状态"和"防御性拷贝"的候选人,面试官会认为有一定的设计意识,直接加分。
1.4 追问升级
面试官追问:"反射能破坏封装吗?"
正确回答:能,setAccessible(true) 可以绕过 Java 的访问控制机制。这是反射的强大之处,也是安全风险之一。Java 9 模块系统引入后,对跨模块的反射访问进行了限制。
二、继承 🔴
2.1 问题拆解
继承允许子类复用父类的属性和方法,实现代码复用和类型层次化。
追问链:
- 第一层:Java 为什么不支持多继承?
- 第二层:接口与抽象类的区别?
- 第三层:继承会带来什么问题?(菱形问题、脆弱基类问题)
2.2 ❌ 错误示范
候选人原话:"继承就是 extends 关键字,子类继承父类的属性和方法,实现代码复用。"
问题诊断:
- 只说了语法,没说设计原则
- 没有提到继承的缺点
- 无法区分继承和组合的适用场景
2.3 标准回答
Java 为什么只支持单继承?
多继承会导致「菱形问题(Diamond Problem)」:
Java 用接口的多实现来替代多继承,但接口方法冲突时由编译器强制要求实现类显式解决。
继承的缺点——脆弱基类问题:
父类修改方法实现,可能导致子类行为异常。经典案例:
继承是强耦合关系,优先使用组合(Composition over Inheritance)。能说出这一点的候选人,面试官会认为有工程意识。
【面试官心理】 我问继承,真正在考的是候选人有没有踩过坑。能背出"Java 单继承"的人很多,能说出"脆弱基类"和"组合优于继承"的才是有项目积累的人。
三、多态 🔴
3.1 多态的底层实现
多态是最容易背但最难真正理解的特性。
为什么能做到运行时才决定调用哪个方法?
这是 JVM 的动态分派(Dynamic Dispatch)机制,底层基于虚方法表(vtable)。
3.2 虚方法表详解
JVM 在类加载时,为每个类创建一张虚方法表(方法区中):
当执行 animal.speak() 时:
- JVM 查找
animal对象的实际类型(运行时是Dog) - 在
Dog的 vtable 中找到speak()对应的入口 - 调用
Dog.speak()
这就是运行时多态的本质:方法的具体调用在运行时才确定。
3.3 静态分派 vs 动态分派
能说出"虚方法表"和"静态分派/动态分派"的候选人,面试官会认为真正看过 JVM 规范,P6+ 水准。
3.4 多态的局限性
【面试官心理】 追问"字段和 static 方法是否参与多态",能把候选人对多态的理解逼到极限。95% 的人只知道实例方法多态,能说出字段和 static 方法例外情况的,才是真正读懂过 JVM 规范的。
四、生产避坑
4.1 多态导致的空指针风险
禁止在父类构造器中调用可被子类重写的方法。这是多态的经典陷阱,生产中真实发生过。
4.2 继承滥用的生产教训
有一段业务代码,把 ArrayList 作为父类,通过继承添加"只允许存 User 对象"的限制:
这种做法导致:
- 任何
ArrayList的修改都可能影响UserList(脆弱基类) Collections.sort等工具方法可能绕过类型检查
正确做法:组合,UserList 内部持有一个 List<User>,暴露有限接口。