1. Java继承机制深度解析
Java继承是面向对象编程的三大特性之一(封装、继承、多态),它允许我们基于现有类创建新类。这种"is-a"关系在Java中通过extends关键字实现,是代码复用的重要手段。
1.1 继承的核心语法与内存模型
基础语法示例:
class Animal { String name; void eat() { System.out.println("Eating..."); } } class Dog extends Animal { void bark() { System.out.println("Barking..."); } }在JVM内存中,子类实例包含完整的父类结构:
- 子类对象创建时,首先递归调用父类构造器
- 父类成员变量在堆内存中优先分配
- 子类新增成员紧随其后
关键提示:父类私有成员虽然被继承,但子类无法直接访问,必须通过父类提供的公共方法
1.2 方法重写与super关键字
方法重写(Override)必须遵守以下规则:
- 方法名、参数列表完全相同
- 返回类型相同或是其子类
- 访问修饰符不能更严格
- 不能抛出比父类更宽泛的异常
super关键字的三种用法:
- 调用父类构造器:super()
- 访问父类成员变量:super.var
- 调用父类方法:super.method()
典型应用场景:
class Smartphone extends Phone { @Override void call() { super.call(); // 保留父类功能 startVideo(); // 扩展新功能 } }1.3 继承体系设计原则
里氏替换原则(LSP):
- 子类必须完全实现父类方法契约
- 子类可以扩展功能,但不能改变原有行为
- 违反示例:重写方法时抛出新的受检异常
组合优于继承:
- 当关系是"has-a"而非"is-a"时
- 需要多继承效果时(Java单继承限制)
- 示例:汽车包含发动机(组合),而非继承发动机
抽象类与接口选择:
- 抽象类:存在具体成员变量或方法实现
- 接口:纯粹定义行为契约(Java8后支持默认方法)
2. 内部类全面剖析
2.1 四种内部类对比
| 类型 | 声明位置 | 外部类访问 | 静态成员 | 典型用途 |
|---|---|---|---|---|
| 成员内部类 | 类内方法外 | 可直接访问 | 不可有 | 逻辑紧密关联的组件 |
| 静态内部类 | 类内方法外 | 需通过实例 | 可有 | 工具类辅助实现 |
| 局部内部类 | 方法/代码块内 | 仅限final | 不可有 | 临时性特殊处理 |
| 匿名内部类 | 表达式形式 | 同局部类 | 不可有 | 事件监听等一次性实现 |
2.2 成员内部类内存泄漏问题
经典内存泄漏场景:
class Outer { private int[] data = new int[1000000]; class Inner { void process() { System.out.println(data.length); // 隐式持有Outer引用 } } Inner getInner() { return new Inner(); } } // 使用方式 void leakExample() { Outer.Inner inner = new Outer().getInner(); // 即使Outer实例不再需要,仍被inner持有无法回收 }解决方案:
- 改为静态内部类(若不需访问外部实例成员)
- 显式清除引用:
class Inner { private WeakReference<Outer> outerRef; Inner(Outer outer) { this.outerRef = new WeakReference<>(outer); } }
2.3 匿名内部类进阶用法
Lambda表达式无法替代的场景:
- 需要重写多个方法时
- 需要维护状态(成员变量)时
- 需要构造器逻辑时
典型示例(Android Handler内存泄漏):
public class MainActivity extends Activity { private Handler mHandler = new Handler() { // 匿名内部类 @Override public void handleMessage(Message msg) { // 隐式持有Activity引用 updateUI(); } }; // 正确做法 private static class SafeHandler extends Handler { private final WeakReference<Activity> mActivity; SafeHandler(Activity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { Activity activity = mActivity.get(); if (activity != null) { ((MainActivity)activity).updateUI(); } } } }3. 继承与内部类的联合应用
3.1 模板方法模式实现
结合继承与匿名内部类的经典设计模式:
abstract class Game { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); // 模板方法 public final void play() { initialize(); startPlay(); endPlay(); } } // 使用匿名内部类实现具体游戏 Game cricket = new Game() { @Override void initialize() { /* cricket初始化 */ } @Override void startPlay() { /* 开始比赛 */ } @Override void endPlay() { /* 结束处理 */ } }; cricket.play();3.2 回调机制实现
事件处理中的典型应用:
interface ClickListener { void onClick(); } class Button { private ClickListener listener; void setListener(ClickListener listener) { this.listener = listener; } void doClick() { if (listener != null) { listener.onClick(); } } } // 使用方式 Button btn = new Button(); btn.setListener(new ClickListener() { @Override public void onClick() { System.out.println("按钮被点击"); } });4. 高频面试问题解析
4.1 继承相关考点
构造器调用顺序问题:
class A { A() { System.out.print("A"); } } class B extends A { B() { System.out.print("B"); } } // new B() 输出什么?重载与重写区别:
- 重载:同一类中,方法名相同参数不同
- 重写:子类中,方法签名完全相同
final关键字影响:
- final类:不可被继承
- final方法:不可被重写
- final变量:不可修改引用
4.2 内部类刁钻问题
外部类访问内部类私有成员:
class Outer { void accessInner() { Inner inner = new Inner(); System.out.println(inner.secret); // 可以访问! } class Inner { private int secret = 42; } }局部内部类访问局部变量必须final的原因:
- 生命周期不一致问题
- Java8的"effectively final"优化
静态内部类单例模式:
class Singleton { private Singleton() {} private static class Holder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return Holder.INSTANCE; } }
5. 实战经验与性能优化
5.1 继承体系设计陷阱
避免过深的继承层次(通常不超过3层)
- 增加维护复杂度
- 影响方法调用性能(虚方法表查找)
慎用继承的场合:
- 父类频繁变更
- 子类差异过大
- 需要多继承场景
组合替代继承的改造示例:
// 改造前 class Stack extends ArrayList { void push(Object o) { add(o); } Object pop() { return remove(size()-1); } } // 改造后 class Stack { private ArrayList list = new ArrayList(); void push(Object o) { list.add(o); } Object pop() { return list.remove(list.size()-1); } }
5.2 内部类优化建议
序列化问题:
- 内部类默认包含外部类引用
- 解决方案:改为静态内部类或实现Externalizable
性能影响:
- 每个非静态内部类实例都会持有外部类引用(额外内存开销)
- 在性能敏感场景考虑改为静态内部类
调试技巧:
- 匿名内部类在字节码中会生成Outer$1.class形式
- 使用javap -v查看合成构造器参数
6. 现代Java中的演进
6.1 Java16的密封类(Sealed Class)
public sealed class Shape permits Circle, Square, Rectangle { ... } public final class Circle extends Shape { ... } public final class Square extends Shape { ... }6.2 Java8的Lambda与内部类
Lambda本质上是匿名内部类的语法糖,但有以下区别:
- 没有自己的this引用(指向外围实例)
- 只能访问effectively final的局部变量
- 不会生成额外的.class文件(使用invokedynamic)
6.3 记录类(Record)与继承
记录类隐式final,禁止被继承:
record Point(int x, int y) { } // 编译错误:不能继承final类 class SubPoint extends Point { ... }7. 典型应用场景案例
7.1 安卓开发中的View体系
// 自定义View的典型模式 class MyView extends View { public MyView(Context context) { super(context); // 初始化 } @Override protected void onDraw(Canvas canvas) { // 绘制逻辑 } }7.2 Spring框架中的匿名内部类
jdbcTemplate.query("SELECT * FROM users", new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int rowNum) { User user = new User(); user.setId(rs.getLong("id")); // ... return user; } });7.3 JUnit测试中的模板模式
public abstract class TestBase { @Before public void setUp() { /* 通用准备 */ } @After public void tearDown() { /* 通用清理 */ } protected abstract void testLogic(); } public class ConcreteTest extends TestBase { @Override protected void testLogic() { // 具体测试逻辑 } }8. 调试与问题排查技巧
8.1 继承关系调试
- 使用getClass().getSuperclass()查看运行时类型
- 通过反射获取所有继承的方法:
Method[] methods = obj.getClass().getMethods();
8.2 内部类诊断
识别内存泄漏:
- 使用MAT分析堆转储
- 查找外部类$内部类的实例引用链
反编译查看合成访问方法:
javap -c Outer\$Inner.class
8.3 常见异常处理
NoSuchMethodError:
- 检查父类方法是否被删除或修改
- 确认编译时和运行时类版本一致
InstantiationError:
- 尝试实例化抽象类或接口
- 内部类没有提供默认构造器
IllegalAccessError:
- 访问权限变更导致
- 常见于跨模块继承场景