目录写在前面一、为什么需要继承1.1 没有继承的痛苦1.2 继承的解决方案二、继承的基本概念2.1 什么是继承2.2 继承的语法2.3 使用继承的注意事项三、子类中访问父类成员3.1 访问成员变量3.2 访问成员方法四、super关键字访问父类成员的通行证4.1 为什么需要super4.2 super的三种用法4.3 super的注意事项五、子类构造方法详解5.1 一个常见的问题5.2 正确的做法5.3 执行顺序5.4 构造方法中的super规则六、super和this的区别七、继承关系上的初始化顺序7.1 没有继承时的顺序7.2 有继承时的顺序八、访问权限修饰符protected九、继承方式与final关键字9.1 Java支持的继承方式9.2 final关键字十、继承与组合10.1 两种关系的区别10.2 组合的代码示例10.3 继承 vs 组合十一、总结写在前面在前面的文章中我们学习了面向对象的基础概念类与对象、封装、static关键字等。你已经知道如何用类来描述现实世界中的事物并通过封装来保护数据的安全。但现实世界是复杂的。猫和狗有很多相似之处——它们都有名字、年龄都会吃东西、睡觉。如果按照之前的方式我们需要在Dog类和Cat类中分别定义这些属性和方法造成大量的重复代码。有没有一种方式能把这些共性抽取出来让猫和狗共享呢这就是我们今天要学习的核心主题——继承。一、为什么需要继承1.1 没有继承的痛苦假设我们要用Java描述猫和狗这两种动物。按照之前的知识我们会写出这样的代码// Dog.java public class Dog { String name; int age; public void eat() { System.out.println(name 正在吃饭); } public void sleep() { System.out.println(name 正在睡觉); } public void bark() { System.out.println(name 汪汪汪~~~); } } // Cat.java public class Cat { String name; int age; public void eat() { System.out.println(name 正在吃饭); } public void sleep() { System.out.println(name 正在睡觉); } public void mew() { System.out.println(name 喵喵喵~~~); } }仔细观察你会发现这两个类中存在大量重复的代码name、age、eat()、sleep()。如果以后还要添加鸟类、鱼类重复只会越来越多。1.2 继承的解决方案面向对象思想中提出了继承的概念专门用来进行共性抽取实现代码复用。继承的核心思想是将共同的属性和行为抽取到一个父类中子类通过继承获得父类的所有非私有成员并在此基础上添加自己特有的内容。用一句话概括继承让代码从复制粘贴升级为复用扩展。二、继承的基本概念2.1 什么是继承继承是面向对象程序设计中实现代码复用的重要手段。它允许程序员在保持原有类特性的基础上进行扩展增加新功能从而产生新的类。在继承关系中父类也叫基类/超类被继承的类存放共性的属性和行为子类也叫派生类继承父类的类在父类基础上增加自己特有的内容继承的主要作用是共性的抽取实现代码复用。2.2 继承的语法在Java中使用extends关键字来表示继承关系修饰符 class 子类 extends 父类 { // 子类特有的成员 }现在我们用继承来重新设计猫和狗的代码// Animal.java —— 父类 public class Animal { String name; int age; public void eat() { System.out.println(name 正在吃饭); } public void sleep() { System.out.println(name 正在睡觉); } } // Dog.java —— 子类 public class Dog extends Animal { void bark() { System.out.println(name 汪汪汪~~~); } } // Cat.java —— 子类 public class Cat extends Animal { void mew() { System.out.println(name 喵喵喵~~~); } } // Test.java —— 测试类 public class Test { public static void main(String[] args) { Dog dog new Dog(); dog.name 旺财; dog.age 3; // 子类可以访问父类继承下来的成员 dog.eat(); // 旺财正在吃饭 dog.sleep(); // 旺财正在睡觉 dog.bark(); // 旺财汪汪汪~~~ Cat cat new Cat(); cat.name 咪咪; cat.eat(); // 咪咪正在吃饭 cat.mew(); // 咪咪喵喵喵~~~ } }关键理解Dog类中并没有定义name、age、eat()、sleep()但这些成员确实存在于Dog对象中——它们是从Animal类继承下来的。2.3 使用继承的注意事项子类会将父类中的成员变量和成员方法继承到子类中子类继承父类之后必须添加自己特有的成员体现出与父类的不同否则就没有必要继承Java只支持单继承一个子类只能有一个直接父类但可以多层继承三、子类中访问父类成员3.1 访问成员变量情况一子类和父类不存在同名成员变量class Base { int a; int b; } class Derived extends Base { int c; public void method() { a 10; // 访问父类的a b 20; // 访问父类的b c 30; // 访问自己的c } }这种情况下访问规则很简单子类有就用子类的子类没有就去父类找。情况二子类和父类存在同名成员变量class Base { int a; int b; } class Derived extends Base { int a; // 与父类同名 public void method() { a 100; // 访问的是子类自己的a b 200; // 父类没有同名变量访问父类的b } }核心规则成员变量访问遵循就近原则。子类自己有就优先用自己的子类没有再到父类中找。3.2 访问成员方法情况一方法名不同class Base { public void methodA() { System.out.println(Base中的methodA()); } } class Derived extends Base { public void methodB() { System.out.println(Derived中的methodB()); } public void test() { methodA(); // 子类没有调用父类的methodA() methodB(); // 调用自己的methodB() } }情况二方法名相同重写class Base { public void methodA() { System.out.println(Base中的methodA()); } } class Derived extends Base { // 重写了父类的methodA() public void methodA() { System.out.println(Derived中的methodA()); } public void test() { methodA(); // 调用的是子类自己的methodA() } }核心规则子类优先调用自己的方法没有则去父类找。如果子类重写了父类的方法默认调用子类的版本。四、super关键字访问父类成员的通行证4.1 为什么需要super当子类和父类中存在同名成员时直接在子类中访问永远访问的是子类自己的成员。那如果我们想在子类中访问父类的同名成员该怎么办这就需要super关键字了。4.2 super的三种用法用法一访问父类的成员变量class Base { int a 100; } class Derived extends Base { int a 200; public void show() { System.out.println(a); // 200访问子类的a System.out.println(this.a); // 200this也表示当前对象 System.out.println(super.a); // 100访问父类的a } }用法二访问父类的成员方法class Base { public void method() { System.out.println(父类方法); } } class Derived extends Base { public void method() { System.out.println(子类方法); } public void test() { method(); // 子类方法 super.method(); // 父类方法 } }用法三调用父类的构造方法class Base { public Base() { System.out.println(Base()); } } class Derived extends Base { public Derived() { super(); // 调用父类无参构造可以省略 System.out.println(Derived()); } }4.3 super的注意事项只能在非静态方法中使用在子类方法中用于访问父类的成员变量和方法在构造方法中super(...)必须是第一条语句super(...)和this(...)不能同时出现在同一个构造方法中五、子类构造方法详解5.1 一个常见的问题先看这段代码class Base { public Base(int n) { System.out.println(Base( n )); } } class Derived extends Base { public Derived() { System.out.println(Derived()); } }这段代码会编译报错为什么呢原因当构造子类对象时需要先调用父类的构造方法来完成父类部分的初始化。如果父类没有无参构造方法或者没有显式定义无参构造编译器就无法自动生成super()调用从而导致编译失败。5.2 正确的做法class Base { public Base(int n) { System.out.println(Base( n )); } } class Derived extends Base { public Derived() { super(10); // 显式调用父类的带参构造 System.out.println(Derived()); } }5.3 执行顺序class Base { public Base() { System.out.println(Base()); } } class Derived extends Base { public Derived() { // 这里默认有 super() System.out.println(Derived()); } } // 创建子类对象 Derived d new Derived(); // 输出 // Base() // Derived()执行顺序总结先执行父类的构造方法再执行子类的构造方法这是因为子类对象由两部分组成父类继承下来的部分 子类新增的部分。先有父后有子所以必须先完成父类部分的初始化。5.4 构造方法中的super规则如果父类有无参构造显式定义或默认生成子类构造方法第一行默认有隐含的super()调用如果父类只有带参构造子类必须显式调用父类的某个构造方法super(...)必须是子类构造方法中的第一条语句super(...)在子类构造方法中只能出现一次super(...)和this(...)不能同时出现六、super和this的区别对比维度thissuper概念当前对象的引用子类对象中从父类继承下来的部分的引用访问范围本类的成员父类的成员构造方法调用this(...)调用本类其他构造方法super(...)调用父类构造方法能否同时出现不能与super同时出现不能与this同时出现在构造方法中的位置必须是第一条语句必须是第一条语句一句话总结this指向自己super指向父类部分。七、继承关系上的初始化顺序7.1 没有继承时的顺序class Person { static { System.out.println(Person:静态代码块执行); } { System.out.println(Person:实例代码块执行); } public Person() { System.out.println(Person:构造方法执行); } }创建两次对象Person p1 new Person(); System.out.println(); Person p2 new Person();输出结果Person:静态代码块执行 Person:实例代码块执行 Person:构造方法执行 Person:实例代码块执行 Person:构造方法执行规律静态代码块在类加载时执行且只执行一次实例代码块和构造方法在每次创建对象时执行实例代码块先于构造方法执行7.2 有继承时的顺序class Person { static { System.out.println(Person:静态代码块执行); } { System.out.println(Person:实例代码块执行); } public Person() { System.out.println(Person:构造方法执行); } } class Student extends Person { static { System.out.println(Student:静态代码块执行); } { System.out.println(Student:实例代码块执行); } public Student() { System.out.println(Student:构造方法执行); } }创建两次Student对象Student s1 new Student(); System.out.println(); Student s2 new Student();输出结果Person:静态代码块执行 Student:静态代码块执行 Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行 Person:实例代码块执行 Person:构造方法执行 Student:实例代码块执行 Student:构造方法执行完整执行顺序总结类加载阶段仅一次 1. 父类静态代码块 2. 子类静态代码块 创建对象阶段每次创建都执行 3. 父类实例代码块 4. 父类构造方法 5. 子类实例代码块 6. 子类构造方法八、访问权限修饰符protected在继承体系中protected关键字扮演着重要的角色。它介于default和public之间访问权限同类同包不同包子类不同包非子类private✅❌❌❌default✅✅❌❌protected✅✅✅❌public✅✅✅✅protected的意义如果一个成员希望被子类访问即使是不同包的子类但又不想被所有类访问就应该使用protected。使用建议成员变量一般用private通过getter/setter访问希望子类访问的方法用protected对外公开的接口用public九、继承方式与final关键字9.1 Java支持的继承方式Java中只支持以下几种继承方式单继承一个子类只有一个直接父类多层继承A → B → CC继承BB继承A不同类继承同一个父类A → BA → CB和C都继承AJava不支持多继承一个子类有多个直接父类这是为了规避菱形继承带来的复杂性。9.2 final关键字final可以用来限制继承// 修饰类该类不能被继承 final class Animal { // ... } // 编译错误不能继承final类 class Dog extends Animal { // ❌ } // 修饰方法该方法不能被重写 class Base { public final void method() { // ... } } class Derived extends Base { // 编译错误不能重写final方法 public void method() { // ❌ // ... } } // 修饰变量变成常量不能修改 final int MAX_VALUE 100; MAX_VALUE 200; // ❌ 编译错误实际应用我们常用的String类就是用final修饰的所以不能被继承。十、继承与组合10.1 两种关系的区别除了继承组合也是一种表达类之间关系的方式同样能达到代码复用的效果。继承表示is-a是一个的关系。比如狗是动物猫是动物组合表示has-a有一个的关系。比如汽车有轮胎汽车有发动机10.2 组合的代码示例// 轮胎类 class Tire { // ... } // 发动机类 class Engine { // ... } // 汽车类通过组合使用Tire和Engine class Car { private Tire tire; // 汽车有轮胎 private Engine engine; // 汽车有发动机 public Car() { tire new Tire(); engine new Engine(); } }10.3 继承 vs 组合对比维度继承组合关系is-ahas-a耦合度高子类依赖父类实现低通过接口交互灵活性较差编译期确定较好运行时可替换代码复用继承父类所有非私有成员通过对象调用方法行业建议能用组合尽量用组合。组合的耦合度更低更易于维护和扩展。十一、总结今天我们学习了Java继承的全部核心知识继承的概念子类继承父类复用父类的非私有成员继承的语法使用extends关键字成员访问规则就近原则子类优先super关键字访问父类成员和构造方法子类构造方法必须先调用父类构造方法初始化顺序静态代码块→实例代码块→构造方法父类先于子类protected权限包访问权限的增强版允许不同包的子类访问final关键字限制继承和重写继承与组合is-a vs has-a优先使用组合继承是面向对象三大特性之一它为代码复用和多态奠定了坚实的基础。下一篇文章我们将学习继承的进阶应用——多态敬请期待