目录
写在前面
一、为什么需要继承?
1.1 没有继承的痛苦
1.2 继承的解决方案
二、继承的基本概念
2.1 什么是继承?
2.2 继承的语法
2.3 使用继承的注意事项
三、子类中访问父类成员
3.1 访问成员变量
3.2 访问成员方法
四、super关键字:访问父类成员的通行证
4.1 为什么需要super?
4.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); // 200,this也表示当前对象 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的区别
对比维度 | 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 → C(C继承B,B继承A)
不同类继承同一个父类:A → B,A → C(B和C都继承A)
Java不支持多继承(一个子类有多个直接父类),这是为了规避菱形继承带来的复杂性。
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-a | has-a |
耦合度 | 高(子类依赖父类实现) | 低(通过接口交互) |
灵活性 | 较差(编译期确定) | 较好(运行时可替换) |
代码复用 | 继承父类所有非私有成员 | 通过对象调用方法 |
行业建议:能用组合尽量用组合。组合的耦合度更低,更易于维护和扩展。
十一、总结
今天我们学习了Java继承的全部核心知识:
继承的概念:子类继承父类,复用父类的非私有成员
继承的语法:使用
extends关键字成员访问规则:就近原则,子类优先
super关键字:访问父类成员和构造方法
子类构造方法:必须先调用父类构造方法
初始化顺序:静态代码块→实例代码块→构造方法,父类先于子类
protected权限:包访问权限的增强版,允许不同包的子类访问
final关键字:限制继承和重写
继承与组合:is-a vs has-a,优先使用组合
继承是面向对象三大特性之一,它为代码复用和多态奠定了坚实的基础。下一篇文章,我们将学习继承的进阶应用——多态,敬请期待!