Java入门(继承)

目录

[一、为什么需要继承?------ 解决代码冗余问题](#一、为什么需要继承?—— 解决代码冗余问题)

无继承的弊端:代码重复率高

二、继承的核心概念

三、继承的基本语法

基于继承重构猫狗案例

继承的基础注意事项

四、父类成员的访问规则

[4.1 子类访问父类的成员变量](#4.1 子类访问父类的成员变量)

[场景 1:子类和父类无同名成员变量](#场景 1:子类和父类无同名成员变量)

[场景 2:子类和父类有同名成员变量](#场景 2:子类和父类有同名成员变量)

[4.2 子类访问父类的成员方法](#4.2 子类访问父类的成员方法)

[场景 1:子类和父类无同名成员方法](#场景 1:子类和父类无同名成员方法)

[场景 2:子类和父类有同名成员方法](#场景 2:子类和父类有同名成员方法)

[五、super 关键字:访问父类的同名成员](#五、super 关键字:访问父类的同名成员)

[5.1 super 的核心使用场景](#5.1 super 的核心使用场景)

[场景 1:访问父类的同名成员变量](#场景 1:访问父类的同名成员变量)

[场景 2:访问父类的同名成员方法](#场景 2:访问父类的同名成员方法)

[场景 3:调用父类的构造方法](#场景 3:调用父类的构造方法)

[5.2 super 关键字完整示例](#5.2 super 关键字完整示例)

[5.3 super 的注意事项](#5.3 super 的注意事项)

六、子类的构造方法:先有父,后有子

[6.1 子类构造方法的核心规则](#6.1 子类构造方法的核心规则)

[6.2 子类构造方法示例](#6.2 子类构造方法示例)

[示例 1:父类有无参构造(默认 / 显式)](#示例 1:父类有无参构造(默认 / 显式))

[示例 2:父类只有有参构造(必须显式调用)](#示例 2:父类只有有参构造(必须显式调用))

[七、super 和 this 的对比](#七、super 和 this 的对比)

八、继承体系中的初始化顺序

[8.1 初始化相关的代码块回顾](#8.1 初始化相关的代码块回顾)

[8.2 继承体系的初始化核心规则](#8.2 继承体系的初始化核心规则)

[8.3 继承体系初始化完整示例](#8.3 继承体系初始化完整示例)

[九、访问修饰符:protected 与继承的可见性](#九、访问修饰符:protected 与继承的可见性)

[9.1 4 种访问修饰符的访问范围](#9.1 4 种访问修饰符的访问范围)

[9.2 protected 的核心特性](#9.2 protected 的核心特性)

[步骤 1:创建包 1 的父类 Base(protected 修饰成员)](#步骤 1:创建包 1 的父类 Base(protected 修饰成员))

[步骤 2:创建包 2 的子类 Derived(可访问自身的 b)](#步骤 2:创建包 2 的子类 Derived(可访问自身的 b))

[9.3 访问修饰符的使用原则](#9.3 访问修饰符的使用原则)

[十、Java 的继承方式与 final 关键字](#十、Java 的继承方式与 final 关键字)

[10.1 Java 支持的继承方式](#10.1 Java 支持的继承方式)

[10.2 final 关键字:限制继承](#10.2 final 关键字:限制继承)

[final 修饰类的示例(禁止继承)](#final 修饰类的示例(禁止继承))

[10.3 继承的层次建议](#10.3 继承的层次建议)

十一、继承与组合:如何选择?

[11.1 组合的实现方式](#11.1 组合的实现方式)

[11.2 继承与组合的对比示例](#11.2 继承与组合的对比示例)

[11.3 继承与组合的选择原则](#11.3 继承与组合的选择原则)

[十二、Java 继承高频面试题解析](#十二、Java 继承高频面试题解析)

[题 1:以下关于 Java 继承的说法,正确的是?(D)](#题 1:以下关于 Java 继承的说法,正确的是?(D))

[题 2:以下关于 Java 继承的说法,错误的是?(C)](#题 2:以下关于 Java 继承的说法,错误的是?(C))

[题 3:子类构造方法中,super (...) 的使用要求是什么?](#题 3:子类构造方法中,super (...) 的使用要求是什么?)

[题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?](#题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?)

十三、总结


在 Java 面向对象的三大特性(封装、继承、多态)中,继承是实现代码复用、构建类层次结构的核心机制。它让我们可以基于已有类的特性扩展出新的类,避免重复编写相同代码,同时为多态的实现奠定基础。本文将从继承的核心需求出发,由浅入深讲解继承的语法、使用细节、核心关键字以及与组合的区别,结合完整可运行的代码示例,让你彻底吃透 Java 继承的精髓。

一、为什么需要继承?------ 解决代码冗余问题

在开发中,我们会用类描述现实世界的实体,但很多实体之间存在共性。比如狗和猫,都属于动物,都有nameage等属性,也有eat()sleep()等行为。如果单独为每个实体编写类,会出现大量重复代码。

无继承的弊端:代码重复率高

java 复制代码
// 狗类
public class Dog {
    String name;
    int age;
    float weight;

    // 吃饭方法
    public void eat() {
        System.out.println(name + "正在吃饭");
    }

    // 睡觉方法
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }

    // 狗的特有方法:叫
    void bark() {
        System.out.println(name + "汪汪汪~~~");
    }
}

// 猫类
public class Cat {
    String name;
    int age;
    float weight;

    // 与Dog类重复的吃饭方法
    public void eat() {
        System.out.println(name + "正在吃饭");
    }

    // 与Dog类重复的睡觉方法
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }

    // 猫的特有方法:叫
    void mew() {
        System.out.println(name + "喵喵喵~~~");
    }
}

观察上述代码,DogCat类中60% 以上的代码是重复的 ,这些共性的属性和方法分散在各个类中,后期维护成本极高(比如修改eat()方法的逻辑,需要修改所有相关类)。

继承 的核心就是抽取共性,实现代码复用,让子类只需关注自身的特有特性即可。

二、继承的核心概念

  1. 继承机制 :允许程序员在保持原有类特性的基础上扩展新功能,生成新的类。原有类称为父类 / 基类 / 超类 ,新类称为子类 / 派生类
  2. 核心作用:① 实现代码复用;② 为多态提供基础。
  3. 关系描述 :子类和父类是is-a的关系(比如狗是动物、猫是动物)。
  4. 核心特性:子类会继承父类的非私有成员(属性和方法),子类可在父类基础上新增自己的特有成员。

类层次结构示意

复制代码
Animal(父类):属性(name/age/weight)、方法(eat/sleep)
    ↗️        ↘️
Dog(子类):新增bark()方法    Cat(子类):新增mew()方法

三、继承的基本语法

在 Java 中,使用extends关键字表示类之间的继承关系,语法格式如下:

java 复制代码
// 修饰符 子类名 extends 父类名 { 子类特有成员 }
修饰符 class SubClass extends SuperClass {
    // 子类新增的属性和方法
}

基于继承重构猫狗案例

我们抽取Animal作为父类,封装所有动物的共性,DogCat作为子类继承Animal,仅实现自身特有方法:

java 复制代码
/**
 * 动物父类:封装所有动物的共性属性和方法
 */
public class Animal {
    String name; // 名字
    int age;     // 年龄
    float weight;// 体重

    // 吃饭方法
    public void eat() {
        System.out.println(name + "正在吃饭");
    }

    // 睡觉方法
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

/**
 * 狗子类:继承Animal父类,仅实现特有方法
 */
public class Dog extends Animal {
    // 狗的特有方法:汪汪叫
    void bark() {
        System.out.println(name + "汪汪汪~~~");
    }
}

/**
 * 猫子类:继承Animal父类,仅实现特有方法
 */
public class Cat extends Animal {
    // 猫的特有方法:喵喵叫
    void mew() {
        System.out.println(name + "喵喵喵~~~");
    }
}

/**
 * 测试类:验证继承的代码复用效果
 */
public class TestExtend {
    public static void main(String[] args) {
        // 创建狗对象
        Dog dog = new Dog();
        // 直接使用父类继承的属性
        dog.name = "大黄";
        dog.age = 3;
        // 直接使用父类继承的方法
        dog.eat();
        dog.sleep();
        // 调用子类特有方法
        dog.bark();

        System.out.println("==========分割线==========");

        // 创建猫对象
        Cat cat = new Cat();
        // 直接使用父类继承的属性
        cat.name = "小白";
        cat.age = 2;
        // 直接使用父类继承的方法
        cat.eat();
        cat.sleep();
        // 调用子类特有方法
        cat.mew();
    }
}

运行结果

复制代码
大黄正在吃饭
大黄正在睡觉
大黄汪汪汪~~~
==========分割线==========
小白正在吃饭
小白正在睡觉
小白喵喵喵~~~

核心亮点:子类无需重新定义父类的共性成员,直接继承使用,代码量大幅减少,维护性提升。

继承的基础注意事项

  1. 子类会继承父类的非私有成员变量和成员方法(私有成员被封装,子类无法直接访问);
  2. 子类继承父类后,必须新增自身特有成员,否则就没有继承的意义(直接使用父类即可);
  3. Java 中一个子类只能有一个直接父类(单继承),不支持多继承。

四、父类成员的访问规则

子类继承了父类的成员,那在子类中如何访问这些成员?是否存在优先级?核心遵循就近原则 ,同时分成员变量成员方法两种场景分析。

4.1 子类访问父类的成员变量

场景 1:子类和父类无同名成员变量

子类可直接访问父类的成员变量,无任何限制:

java 复制代码
/**
 * 父类Base
 */
public class Base {
    int a;
    int b;
}

/**
 * 子类Derived:无同名成员变量
 */
public class Derived extends Base {
    int c; // 子类特有变量

    public void method() {
        a = 10; // 直接访问父类的a
        b = 20; // 直接访问父类的b
        c = 30; // 访问子类自己的c
    }
}
场景 2:子类和父类有同名成员变量

当子类存在与父类同名的成员变量时,优先访问子类自己的成员变量;若子类无该变量,才会访问父类的,父类也无则编译报错。

java 复制代码
/**
 * 父类Base
 */
public class Base {
    int a = 10;
    int b = 20;
    int c = 30;
}

/**
 * 子类Derived:有同名成员变量
 */
public class Derived extends Base {
    int a = 100; // 与父类a同名同类型
    char b = 'b';// 与父类b同名不同类型

    public void method() {
        a = 200; // 访问子类自己的a,而非父类的a
        b = 'c'; // 访问子类自己的b,而非父类的b
        c = 400; // 子类无c,访问父类的c
        // d = 500; // 编译报错:父类和子类都无d
    }
}

4.2 子类访问父类的成员方法

场景 1:子类和父类无同名成员方法

子类可直接访问父类的成员方法,无任何限制,遵循自己没有找父类的原则:

java 复制代码
/**
 * 父类Base
 */
public class Base {
    public void methodA() {
        System.out.println("Base中的methodA()");
    }
}

/**
 * 子类Derived:无同名方法
 */
public class Derived extends Base {
    public void methodB() {
        System.out.println("Derived中的methodB()");
    }

    public void methodC() {
        methodB(); // 访问子类自己的methodB
        methodA(); // 子类无methodA,访问父类的methodA
        // methodD(); // 编译报错:整个继承体系无methodD
    }
}
场景 2:子类和父类有同名成员方法

当子类存在与父类同名的成员方法时,优先访问子类自己的方法 ;若方法是重载 (参数列表不同),则根据传参匹配方法;若方法是重写(参数列表、返回值、方法名完全相同),则直接访问子类的重写方法。

java 复制代码
/**
 * 父类Base
 */
public class Base {
    public void methodA() {
        System.out.println("Base中的methodA()");
    }

    public void methodB() {
        System.out.println("Base中的methodB()");
    }
}

/**
 * 子类Derived:有同名方法
 */
public class Derived extends Base {
    // 重写父类的methodA(参数列表、方法名完全相同)
    public void methodA() {
        System.out.println("Derived中的methodA()");
    }

    // 重写父类的methodB
    public void methodB() {
        System.out.println("Derived中的methodB()");
    }

    public void methodC() {
        methodA(); // 访问子类重写的methodA
        methodB(); // 访问子类重写的methodB
    }
}

五、super 关键字:访问父类的同名成员

当子类和父类存在同名成员 时,子类无法直接访问父类的同名成员,此时需要使用super 关键字 ------ 它的核心作用是在子类中访问父类的成员(变量 / 方法 / 构造方法) ,相当于子类对象中父类继承部分的引用

5.1 super 的核心使用场景

场景 1:访问父类的同名成员变量
java 复制代码
super.父类成员变量名
场景 2:访问父类的同名成员方法
java 复制代码
super.父类成员方法名(参数)
场景 3:调用父类的构造方法
java 复制代码
super(参数); // 必须是子类构造方法的第一行

5.2 super 关键字完整示例

java 复制代码
/**
 * 父类Base
 */
public class Base {
    int a = 10;
    int b = 20;

    public void methodA() {
        System.out.println("Base中的methodA()");
    }

    public void methodB() {
        System.out.println("Base中的methodB()");
    }
}

/**
 * 子类Derived:使用super访问父类同名成员
 */
public class Derived extends Base {
    int a = 100; // 与父类a同名

    // 与父类methodA重载(参数列表不同)
    public void methodA(int a) {
        System.out.println("Derived中的methodA(int):" + a);
    }

    // 与父类methodB重写(原型完全相同)
    public void methodB() {
        System.out.println("Derived中的methodB()");
    }

    public void methodC() {
        // 1. 访问成员变量:直接访问=子类自己,super=父类
        a = 200;        // 等价于this.a = 200,访问子类的a
        super.a = 300;  // 访问父类的a
        b = 400;        // 子类无b,直接访问父类的b(也可写super.b)

        // 2. 访问成员方法:重载通过参数区分,重写通过super访问父类
        methodA();      // 无参,访问父类的methodA
        methodA(500);   // 有参,访问子类的methodA(int)
        methodB();      // 直接访问,子类重写的methodB
        super.methodB();// super,访问父类的methodB
    }
}

/**
 * 测试类
 */
public class TestSuper {
    public static void main(String[] args) {
        Derived d = new Derived();
        d.methodC();
    }
}

运行结果

复制代码
Base中的methodA()
Derived中的methodA(int):500
Derived中的methodB()
Base中的methodB()

5.3 super 的注意事项

  1. 只能在非静态方法中使用:静态方法属于类,不依赖对象,而 super 是对象层面的引用;
  2. 不能和 this 同时在构造方法中使用:两者都要求是构造方法的第一行;
  3. super 不是对象:只是一个指向父类继承部分的关键字,无法单独打印或赋值。

六、子类的构造方法:先有父,后有子

在 Java 中,构造子类对象时,必须先构造父类对象------ 因为子类的成员由「父类继承的部分」+「子类新增的部分」组成,只有先初始化父类成员,才能完整初始化子类对象。

6.1 子类构造方法的核心规则

  1. 默认调用父类无参构造 :如果子类构造方法中没有显式调用父类构造方法,编译器会自动在子类构造方法的第一行添加super(),调用父类的无参构造;
  2. 显式调用父类有参构造 :如果父类没有无参构造 (显式定义了有参构造),子类必须在构造方法的第一行 通过super(参数)显式调用父类的有参构造,否则编译报错;
  3. super (...) 只能出现一次 :子类构造方法中,super(...)只能写一次,且必须是第一行代码;
  4. 不能和 this (...) 同时使用this(...)是调用子类自己的其他构造方法,也要求是第一行,两者冲突。

6.2 子类构造方法示例

示例 1:父类有无参构造(默认 / 显式)
java 复制代码
/**
 * 父类Base:显式无参构造
 */
public class Base {
    public Base() {
        System.out.println("Base的无参构造执行");
    }
}

/**
 * 子类Derived:默认调用父类无参构造
 */
public class Derived extends Base {
    public Derived() {
        // 编译器自动添加super(),无需手动写
        System.out.println("Derived的无参构造执行");
    }
}

/**
 * 测试类
 */
public class TestConstructor1 {
    public static void main(String[] args) {
        Derived d = new Derived();
    }
}

运行结果

复制代码
Base的无参构造执行
Derived的无参构造执行
示例 2:父类只有有参构造(必须显式调用)
java 复制代码
/**
 * 父类Base:只有有参构造,无默认无参构造
 */
public class Base {
    int a;

    public Base(int a) {
        this.a = a;
        System.out.println("Base的有参构造执行:a=" + a);
    }
}

/**
 * 子类Derived:必须显式调用父类有参构造
 */
public class Derived extends Base {
    public Derived() {
        super(10); // 显式调用父类有参构造,必须是第一行
        System.out.println("Derived的无参构造执行");
    }

    public Derived(int a, int b) {
        super(a); // 显式调用父类有参构造
        System.out.println("Derived的有参构造执行:b=" + b);
    }
}

/**
 * 测试类
 */
public class TestConstructor2 {
    public static void main(String[] args) {
        Derived d1 = new Derived();
        System.out.println("==========分割线==========");
        Derived d2 = new Derived(20, 30);
    }
}

运行结果

复制代码
Base的有参构造执行:a=10
Derived的无参构造执行
==========分割线==========
Base的有参构造执行:a=20
Derived的有参构造执行:b=30

七、super 和 this 的对比

super 和 this 都是 Java 的核心关键字,都能访问成员变量 / 方法,也能用于构造方法,两者的异同点如下表所示,帮你彻底区分:

对比维度 this super
核心含义 代表当前对象的引用 代表子类对象中父类继承部分的引用
访问成员 访问本类的成员(变量 / 方法),本类无则找父类 直接访问父类的成员(变量 / 方法)
构造方法 this(...)调用本类的其他构造方法 super(...)调用父类的构造方法
存在位置 非静态方法 / 构造方法中 非静态方法 / 构造方法中
第一行规则 构造方法中使用时,必须是第一行 构造方法中使用时,必须是第一行
共存性 不能和 super (...) 同时在构造方法中使用 不能和 this (...) 同时在构造方法中使用
编译器默认添加 构造方法中不默认添加,需手动写 子类构造方法中默认添加 super ()(父类有无参构造时)

核心总结this关注本类super关注父类,两者都是面向对象中封装和继承的重要体现。

八、继承体系中的初始化顺序

在 Java 中,类的初始化包含静态代码块实例代码块构造方法,当存在继承关系时,初始化顺序会有明确的优先级,这是面试高频考点,必须牢牢掌握。

8.1 初始化相关的代码块回顾

  1. 静态代码块 :用static{}修饰,类加载时执行,仅执行一次,用于初始化静态变量;
  2. 实例代码块 :用{}修饰,创建对象时执行,每次创建对象都会执行,执行在构造方法之前,用于初始化实例变量;
  3. 构造方法创建对象时执行,每次创建对象都会执行,用于初始化对象的完整状态。

8.2 继承体系的初始化核心规则

优先级从高到低

  1. 父类静态代码块 → 子类静态代码块(静态代码块仅执行一次,类加载时完成);
  2. 父类实例代码块 → 父类构造方法(初始化父类继承部分);
  3. 子类实例代码块 → 子类构造方法(初始化子类新增部分);
  4. 再次创建子类对象时,静态代码块不再执行(仅类加载时执行一次),其余步骤重复。

8.3 继承体系初始化完整示例

java 复制代码
/**
 * 父类Person
 */
public class Person {
    public String name;
    public int age;

    // 静态代码块
    static {
        System.out.println("Person:静态代码块执行");
    }

    // 实例代码块
    {
        System.out.println("Person:实例代码块执行");
    }

    // 构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
    }
}

/**
 * 子类Student:继承Person
 */
public class Student extends Person {
    // 静态代码块
    static {
        System.out.println("Student:静态代码块执行");
    }

    // 实例代码块
    {
        System.out.println("Student:实例代码块执行");
    }

    // 构造方法:必须显式调用父类有参构造
    public Student(String name, int age) {
        super(name, age);
        System.out.println("Student:构造方法执行");
    }
}

/**
 * 测试类:验证初始化顺序
 */
public class TestInitOrder {
    public static void main(String[] args) {
        System.out.println("=====第一次创建Student对象=====");
        Student s1 = new Student("张三", 19);
        System.out.println("=====第二次创建Student对象=====");
        Student s2 = new Student("李四", 20);
    }
}

运行结果

复制代码
=====第一次创建Student对象=====
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
=====第二次创建Student对象=====
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

结果分析:静态代码块仅在第一次创建对象时执行(类加载),第二次创建对象时不再执行,完全符合初始化规则。

九、访问修饰符:protected 与继承的可见性

在 Java 中,有 4 种访问修饰符:privatedefault(包访问)、protectedpublic,它们决定了成员的访问范围,其中protected 是为继承专门设计的,核心作用是让不同包的子类能访问父类的成员

9.1 4 种访问修饰符的访问范围

访问范围 private default protected public
同一类中
同一包的不同类
不同包的子类
不同包的非子类

9.2 protected 的核心特性

protected修饰的成员,不同包的子类只能通过自身对象访问不能访问其他子类对象的该成员,这是容易踩坑的点,示例说明:

步骤 1:创建包 1 的父类 Base(protected 修饰成员)
java 复制代码
package com.demo1;
/**
 * 父类:com.demo1包
 */
public class Base {
    protected int b = 100; // protected修饰的成员变量
}
步骤 2:创建包 2 的子类 Derived(可访问自身的 b)
java 复制代码
package com.demo2;
import com.demo1.Base;
/**
 * 子类1:com.demo2包,继承Base
 */
public class Derived extends Base {
    public static void main(String[] args) {
        Derived d = new Derived();
        System.out.println(d.b); // 合法:访问自身对象的protected成员

        C c = new C();
        // System.out.println(c.b); // 编译报错:不能访问其他子类对象的protected成员
    }
}

/**
 * 子类2:com.demo2包,继承Base
 */
class C extends Base {}

核心结论protected的可见性是 **"子类可见"**,而非 "子类的对象可见",不同包的子类之间不能互相访问 protected 成员。

9.3 访问修饰符的使用原则

为了保证类的封装性,使用修饰符时遵循 **"宁严勿宽"** 的原则:

  1. 仅类内部使用的成员,用private
  2. 同一包内的类使用的成员,用default
  3. 不同包的子类使用的成员,用protected
  4. 所有类都需要使用的成员,用public
  5. 成员变量优先用private(封装),通过get/set方法访问;成员方法根据实际使用范围选择修饰符。

十、Java 的继承方式与 final 关键字

10.1 Java 支持的继承方式

Java 是单继承 语言,一个子类只能有一个直接父类,但支持多层继承 (子类继承父类,父类再继承祖父类),不支持多继承(一个子类继承多个父类),避免类层次结构混乱。

继承方式 示例 是否支持
单继承 class B extends A{}
多层继承 class C extends Bclass B extends A
多继承 class C extends A,B{}

为什么不支持多继承? 如果多个父类有同名的方法,子类继承后会出现方法冲突,无法确定调用哪个父类的方法,导致程序歧义。

10.2 final 关键字:限制继承

final关键字可修饰变量方法,其中修饰类和方法时,与继承直接相关:

  1. 修饰类 :表示该类不能被继承 ,称为 "最终类";
    • 示例:public final class String{}(Java 的 String 类就是 final 类,不能被继承);
  2. 修饰方法 :表示该方法不能被子类重写(后序多态章节会详细讲解);
  3. 修饰变量 :表示该变量是常量,值不能被修改。
final 修饰类的示例(禁止继承)
java 复制代码
/**
 * final修饰的类:不能被继承
 */
public final class Animal {
    public void eat() {
        System.out.println("动物吃饭");
    }
}

/**
 * 编译报错:Cannot inherit from final 'Animal'
 */
public class Dog extends Animal { // 错误!final类不能被继承
    public void bark() {
        System.out.println("狗叫");
    }
}

10.3 继承的层次建议

实际开发中,不建议继承层次超过 3 层 ,如果继承层次太深,会导致类的耦合性过高,代码可读性和维护性大幅下降。若层次过深,建议考虑重构代码(比如使用组合)。

十一、继承与组合:如何选择?

除了继承,组合也是 Java 中实现代码复用的重要方式,两者的核心区别在于类之间的关系:

  • 继承 :表示is-a关系(狗是动物、学生是人);
  • 组合 :表示has-a关系(汽车有发动机、电脑有 CPU)。

11.1 组合的实现方式

组合无需特殊关键字,只需将一个类的对象作为另一个类的成员变量,通过该对象访问其成员,实现代码复用。

11.2 继承与组合的对比示例

汽车为例,汽车由发动机、轮胎、车载系统组成(has-a),使用组合实现;奔驰是汽车的一种(is-a),使用继承实现。

java 复制代码
/**
 * 轮胎类:组件类
 */
class Tire {
    public void run() {
        System.out.println("轮胎转动,汽车前进");
    }
}

/**
 * 发动机类:组件类
 */
class Engine {
    public void start() {
        System.out.println("发动机启动,提供动力");
    }
}

/**
 * 车载系统类:组件类
 */
class VehicleSystem {
    public void open() {
        System.out.println("车载系统启动,支持导航/音乐");
    }
}

/**
 * 汽车类:通过组合复用组件类的功能
 */
class Car {
    // 组合:将组件类的对象作为成员变量
    private Tire tire = new Tire();
    private Engine engine = new Engine();
    private VehicleSystem vs = new VehicleSystem();

    // 汽车的启动方法:调用组件类的方法
    public void startCar() {
        engine.start();
        tire.run();
        vs.open();
        System.out.println("汽车成功启动!");
    }
}

/**
 * 奔驰类:通过继承复用汽车类的功能
 */
class Benz extends Car {
    // 奔驰的特有方法
    public void luxury() {
        System.out.println("奔驰:高端内饰,智能驾驶");
    }
}

/**
 * 测试类
 */
public class TestCombine {
    public static void main(String[] args) {
        Benz benz = new Benz();
        benz.startCar(); // 继承自Car的方法
        benz.luxury();   // 自身特有方法
    }
}

运行结果

复制代码
发动机启动,提供动力
轮胎转动,汽车前进
车载系统启动,支持导航/音乐
汽车成功启动!
奔驰:高端内饰,智能驾驶

11.3 继承与组合的选择原则

核心建议:能用组合,尽量用组合,原因如下:

  1. 组合的耦合性更低:组合是 "使用关系",子类仅使用组件类的功能,不依赖其内部实现;继承是 "父子关系",子类依赖父类的实现,耦合性高;
  2. 组合更灵活:可随时替换组件类的实现,而继承的父类一旦确定,无法轻易修改;
  3. 组合避免单继承限制:Java 不支持多继承,但可通过组合多个组件类,实现多 "功能" 的复用;
  4. 继承的适用场景 :只有当类之间确实是is-a的关系时,才使用继承(比如学生是人、狗是动物)。

十二、Java 继承高频面试题解析

结合本文内容,整理几道 Java 继承的高频面试题,帮你巩固知识点:

题 1:以下关于 Java 继承的说法,正确的是?(D)

A. 一个类可以同时继承多个类B. 子类可以直接访问父类的私有 (private) 成员C. 使用 final 关键字修饰的类可以被继承D. 子类可以重写 (override) 父类的方法

解析:A 错误,Java 不支持多继承;B 错误,私有成员被封装,子类无法直接访问;C 错误,final 类不能被继承;D 正确,子类可重写父类的非 final 方法。

题 2:以下关于 Java 继承的说法,错误的是?(C)

A. 子类可以通过 super 关键字调用父类的构造方法B. 所有的 Java 类都直接或间接继承自 java.lang.Object 类C. 构造方法可以被子类继承D. 使用 protected 修饰的成员可以被子类访问

解析:A 正确,super (...) 可调用父类构造方法;B 正确,Object 是 Java 所有类的根父类;C 错误,构造方法不能被继承,子类只能通过 super 调用;D 正确,protected 的核心作用是让子类访问。

题 3:子类构造方法中,super (...) 的使用要求是什么?

答案:① 必须是子类构造方法的第一行代码;② 只能出现一次;③ 不能和 this (...) 同时使用;④ 若父类无无参构造,子类必须显式调用 super (...)。

题 4:继承体系中,静态代码块、实例代码块、构造方法的执行顺序是什么?

答案:父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法;静态代码块仅执行一次。

十三、总结

Java 继承是面向对象编程的核心机制,其核心价值是抽取共性、实现代码复用,同时为多态奠定基础。本文从实际需求出发,讲解了继承的语法、父类成员访问规则、super 关键字、子类构造方法、初始化顺序、访问修饰符、final 关键字,以及与组合的区别,核心知识点可总结为以下几点:

  1. 继承使用extends关键字,Java 支持单继承和多层继承,不支持多继承;
  2. 子类继承父类的非私有成员,必须新增自身特有成员,否则无继承意义;
  3. 父类成员访问遵循就近原则,同名成员需用 super 关键字访问;
  4. 子类构造时先构造父类,super (...) 必须是子类构造方法的第一行;
  5. 继承体系的初始化顺序:静态代码块(父→子)→ 实例代码块 + 构造方法(父→子);
  6. protected 为继承设计,允许不同包的子类访问父类成员;
  7. final 修饰的类不能被继承,修饰的方法不能被重写;
  8. 继承是 is-a 关系,组合是 has-a 关系,实际开发中优先使用组合。

掌握继承的核心知识点,不仅能写出更简洁、易维护的代码,还能为后续学习多态打下坚实的基础。建议结合本文的代码示例亲自运行、调试,加深对知识点的理解。

相关推荐
Bert.Cai2 小时前
Python默认参数详解
开发语言·python
loading小马2 小时前
解决jdk17版本与seata冲突问题
java·jvm·jdk·intellij-idea
_饭团2 小时前
指针核心知识:5篇系统梳理4
c语言·开发语言·c++·笔记·深度学习·算法·面试
0xDevNull2 小时前
Java 高频面试题
java·开发语言
014-code2 小时前
Kafka + Spring Boot 实战入门
java·spring boot·kafka·消息队列
GGbond--2 小时前
2026年最佳静态ISP代理:最佳提供商、功能和测评
java·服务器·人工智能·深度学习·接口隔离原则
Ronin3052 小时前
【Qt常用控件】多元素控件
开发语言·qt·常用控件·多元素控件
qwert10372 小时前
Spring Boot从0到1 -day02
java·spring boot·后端
m0_584624502 小时前
调用接口返回的json数据被截断
java·json