首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【JavaSE】继承与多态 && extends && super && final && override && instanceof

【JavaSE】继承与多态 && extends && super && final && override && instanceof

原创
作者头像
lirendada
发布2026-03-08 12:13:10
发布2026-03-08 12:13:10
1560
举报
文章被收录于专栏:JavaJava

Ⅰ. 继承

一、概念

继承(inheritance)是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承最大的作用就是:实现代码复用,还有就是来实现多态

二、继承语法 -- extends

Java 中如果要表示类之间的继承关系,需要借助 extends 关键字,具体如下:

代码语言:javascript
复制
修饰符 class 子类 extends 父类 {
    // ... 
}

☘️注意事项

  1. 子类会将父类中的「成员变量」和「成员方法」继承到子类中。
  2. 成员变量」访问遵循「就近原则」,自己有优先自己的,如果没有则向父类中找。
  3. 成员方法」的访问原则:
    1. 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
    2. 通过子类对象访问父类与子类中同名方法时,如果父类和子类同名方法的参数列表不同的话,则它们之间构成重载,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;如果说父类和子类同名方法的参数列表以及返回值类型相同的话,则构成重写

三、访问父类成员 -- super

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作❓❓❓

直接访问是无法做到的, 所以 Java 提供了 super 关键字,该关键字主要作用:在子类方法中访问父类的成员。也就是说,如果在子类方法中想要明确访问父类中成员时,借助 super 关键字即可!

☘️注意事项:

  1. this 一样,不能在「静态方法」中使用
  2. super 只是一个关键字,是为了提高代码的可读性的,并不会生成父类的引用,所以实际上创建子类是不会创建出父类引用的!
  3. super 只能指代当前类的父类,而不能指代当前类的爷爷类等!
代码语言:javascript
复制
class Base{
    public int a = 10;
}

class Derived extends Base {
    int a = 20;
    
    public void func(){
        System.out.println("a is " + a);           // 优先访问子类自己的a
        System.out.println("a is " + super.a); // 通过super直接访问父类的a
    }
}

public class test1 {
    public static void main(String[] args) {
        Derived d = new Derived();
        d.func();
    }
}

// 执行结果
a is 20
a is 10

四、子类的构造方法💥

牢记一个原则:先有父再有子!即子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法

☘️注意事项

  • 如果显式调用 super() 对父类进行构造的话,super() 语句必须放在子类构造方法的第一行
  • super()this() 不能放在一个方法中使用,因为两者都必须放在构造方法的第一行,这就导致了冲突,所以不能一起使用!如下所示:
  • super() 只能在子类的构造方法中调用,并且只能出现一次

下面是代码示例,如下所示:

代码语言:javascript
复制
class Base{
    public Base(){
        System.out.println("This is Base!");
    }
}

class Derived extends Base{
    public Derived(){
        super(); // 无参super()函数就算我们不写也会自动调用!!!
        System.out.println("This is Derived!");
    }
}

public class test1 {
    public static void main(String[] args) {
        Derived d = new Derived();
    }
}

// 执行结果
This is Base!
This is Derived!

如果父类中的构造函数需要我们传入参数的话,则我们必须要显式调用一下 super() 函数进行传参,不然会报错,如下图所示:

因为此时 Base 类有了有参构造方法,那么编译器就不会默认为 Base 类创建一个无参的构造方法,此时子类就必须要显式传入需要的参数!

五、superthis 的关系

superthis 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢❓❓❓

☘️相同点

  1. 都是关键字
  2. 都只能在类的「非静态方法」中使用,用来访问非静态成员方法和字段。
  3. 在构造方法中显式调用时,super()this() 都必须是构造方法中的第一条语句,所以它们不能同时存在

☘️不同点

  1. this 是当前对象的引用;而 super 是子类对象中从父类继承下来部分成员的引用!
  2. 在「非静态成员方法」中,this 用来访问本类的方法和属性,super 用来访问从父类继承下来的方法和属性。
  3. 构造方法中一定会存在 super(...) 的调用,就算用户没有写,编译器也会增加;但是 this(...) 用户不写则没有

六、再谈初始化

结合前面我们所学的类的初始化以及代码块的知识,再串上现在的继承,尝试着理解下面这道题:

代码语言:javascript
复制
class Base{
    static {
        System.out.println("This is Base 静态代码块");
    }

    {
        System.out.println("This is Base 实例代码块");
    }
    
    public Base(){
        System.out.println("This is Base()!");
    }
}

class Derived extends Base{
    static {
        System.out.println("This is Derived 静态代码块");
    }

    {
        System.out.println("This is Derived 实例代码块");
    }
    
    public Derived(){
        System.out.println("This is Derived()!");
    }
}

public class test1 {
    public static void main(String[] args) {
        Derived d1 = new Derived();
        System.out.println("----------------");
        Derived d2 = new Derived();
    }
}

// 执行结果:
This is Base 静态代码块
This is Derived 静态代码块
This is Base 实例代码块
This is Base()!
This is Derived 实例代码块
This is Derived()!
----------------
This is Base 实例代码块
This is Base()!
This is Derived 实例代码块
This is Derived()!

通过分析执行结果,得出以下结论:

先执行父类和子类的静态,再执行父类的实例和构造方法,最后执行子类的实例和构造方法

七、protected 关键字

在类和对象中,为了实现封装特性,Java 中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

学了继承之后我们就知道了,protected 关键字是为了给不同包中的子类提供访问父类的成员的权限

因为如果没有 protected 关键字,只有 defaultpublic 的话,不同包中的子类要想访问父类的成员的话,此时就没办法控制好访问粒度,所以才引入了 protected 关键字!

注意:父类中 private 成员变量虽然在子类中不能直接访问,但也继承到子类中了

八、继承方式

Java 中只支持以下几种继承方式:

注意:Java 中不支持多继承

九、final 关键字

  1. 修饰变量:表示常量(相当于 C 中的 const 关键字)
  2. 修饰:表示此类不能被继承
  3. 修饰方法:表示该方法不能被重写

此时就有一个易错点,如下代码所示:

代码语言:javascript
复制
public static void main(String[] args) {
    final int[] arr = new int[9];
    
    arr = new int[10]; // ❌
    arr[0] = 20;       // ✅
}

因为 final 修饰的是 arr 变量,也就是 arr 中保存的值,也就是 arr 的引用地址不能改变,但如果修改的是 arr[0],即改的是堆区中的元素,不会被 final 影响!

十、继承和组合

和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。

  • 继承表示对象之间是 is-a 的关系。比如:狗是动物,猫是动物
  • 组合表示对象之间是 has-a 的关系。比如:汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:尽量用组合,因为组合更安全,更简单,更灵活,更高效!

代码语言:javascript
复制
// 轮胎类
class Tire{
    ...
}

// 发动机类
class Engine{
    ...
}

// 车载系统类
class VehicleSystem{
    ...
}

// 使用组合方式
class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
    ...
}

// 使用继承方式:奔驰是汽车
class Benz extend Car{
    ...
}

它们的优缺点如下所示:

组合关系

继承关系

优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立

缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性

优点:具有较好的可扩展性

缺点:支持扩展,但是往往以增加系统结构的复杂度为代价

优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象

缺点:不支持动态继承。在运行时,子类无法选择不同的父类

优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点:子类不能改变父类的接口

缺点:整体类不能自动获得和局部类同样的接口

优点:子类能自动继承父类的接口

缺点:创建整体类的对象时,需要创建所有局部类的对象

优点:创建子类的对象时,无须创建父类的对象

Ⅱ. 多态

通俗来说,就是多种形态,具体点就是去在完成某个动作时,不同的对象完成会产生出不同的状态

一、多态实现条件

java 中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 继承关系
  2. 子类对父类中方法进行了重写
  3. 通过父类的引用调用重写的方法,也称为向上转型
代码语言:javascript
复制
class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}

class Cat extends Animal{
    public Cat(String name, int age){
        super(name, age);
    }

    // 下面的注解表示当前方法进行了重写,编译器会帮忙检查是否重写了
    @Override
    public void eat(){
        System.out.println(name+"吃鱼~~~");
    }
}

class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    }

    // 下面的注解表示当前方法进行了重写,编译器会帮忙检查是否重写了
    @Override
    public void eat(){
        System.out.println(name+"吃骨头~~~");
    }
}

///////////////////////////////分割线//////////////////////////////////////////////
public class test1 {
    // 编译器在编译代码时,并不知道要调用Dog还是Cat中eat()的方法
    // 等程序运行起来后,形参a引用的具体对象确定后,才知道调用哪个方法
    // 注意:此处的形参类型必须时父类类型才可以
    public static void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("元宝", 2);
        Dog dog = new Dog("小七", 1);
        eat(cat);
        eat(dog);
    }
}

// 执行结果:
元宝吃鱼~~~
小七吃骨头~~~

在上述代码中,假设分割线上方的代码是类的实现者编写的,分割线下方的代码是类的调用者编写的。

当类的调用者在编写 eat() 这个方法的时候,参数类型为 Animal(父类),此时在该方法内部并不知道、也不关注当前的 a 引用指向的是哪个类型(哪个子类)的实例,此时 a 这个引用调用 eat() 方法可能会有多种不同的表现(和 a 引用的实例相关),这种行为就称为多态。

二、重写/覆盖 -- override

  1. 子类在重写父类的方法时,必须与父类方法原型一致: 即「返回值类型」、「方法名」、「参数列表」要完全一致
  2. 被重写的方法返回值类型可以不同,但是必须是具有继承关系的,这种情况称为协变
  3. 「子类」访问权限要比「父类」中被重写的方法的访问权限「大」。例如:如果父类方法被 public 修饰,则子类中重写该方法就不能声明为 protected
  4. 父类被 finalstaticprivate 修饰的方法都不能被重写
  5. 构造方法不能被重写
  6. 重写的方法要使用 @Override 注解来显式指定。有了这个注解能帮我们进行一些合法性校验,例如不小心将方法名字拼写错了(比如写成 aet),那么此时编译器就会发现父类中没有 aet 方法,就会编译报错,提示无法构成重写。

重写和重载的区别:

区别点

重写(override)

重载(overload)

参数列表

不能修改

必须修改

返回类型

不能修改(除非构成父子关系)

可以修改

访问限定符

权限不能比父类该方法小

可以修改

静态绑定:也称为前期绑定,即在编译时根据用户所传递实参类型就确定了具体调用那个方法。典型代表就是方法重载

动态绑定:也称为后期绑定,即在编译时不能确定方法的行为,需要等到程序运行时才能够确定具体调用那个类的方法。典型代表就是方法重写

三、向上转型

简单地说就是拿父类的引用指向子类的空间!比如下面的代码:

代码语言:javascript
复制
Base tmp = new Derived();
  • 优点:让代码实现更简单灵活
  • 缺点不能调用到子类特有的方法(所以才需要引出向下转型)

使用场景:

  1. 直接赋值:比如上面的代码
  2. 方法传参
    代码语言:javascript
    复制
    // 方法传参:形参为父类型引用,可以接收任意子类的对象
    public static void eatFood(Animal a) {
        a.eat();
    }
    
    public static void main(String[] args) {
        Dog dog = new Dog("小七", 1);
        eatFood(dog);
    }

3. 方法返回

代码语言:javascript
复制
// 作为返回值:返回任意子类对象对应的父类引用
public static Animal buyAnimal(String var){
    if("狗" == var) {
        return new Dog("狗狗",1);
    }else if("猫" == var) {
        return new Cat("猫猫", 1);
    }else {
        return null;
    }
}

四、向下转型 -- instanceof

有时候可能需要调用子类特有的方法,此时将父类引用再还原为子类对象即可,称为向下转型。

但是向下转型用的比较少,并且向下转型是不安全的,因为向下转型到不是原来引用的子类的时候,运行时就会抛异常,也是因为不同子类可能有不同数量的拓展成员。

Java为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为 true,则可以安全转换,如下所示:

代码语言:javascript
复制
public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝", 2);
        Dog dog = new Dog("小七", 1);
        
        // 向上转型,是安全的,可以直接转换
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
        System.out.println("--------------");
        
        // 向下转型,是不安全的,要使用instanceof关键字判断
        if(animal instanceof Cat){
            cat = (Cat)animal;
            cat.mew();
        }
        if(animal instanceof Dog){
            dog = (Dog)animal;
            dog.bark();
        }
    }
}

// 执行结果:
元宝吃鱼~~~
小七吃骨头~~~
--------------
狗bark~

五、多态的优缺点

优点:

  1. 能够降低代码的 "圈复杂度",避免使用大量的 if-else 语句。
  2. 可扩展能力更强。如果要新增一种新的形状,使用多态的方式代码改动成本也比较低!

缺点:

  1. 属性没有多态性。当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性!
  2. 构造方法没有多态性。

六、避免在构造方法中调用重写过的方法☢️

一段有坑的代码,我们创建两个类,B 是父类,D 是子类,D 中重写 func 方法,并且在 B 的构造方法中调用 func,如下所示:

代码语言:javascript
复制
class B {
    public B() {
        func();
    }
    
    public void func() {
        System.out.println("B.func()");
    }
}

class D extends B {
    private int num = 1;
    
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}

public class test {
    public static void main(String[] args) {
        D d = new D();
    }
}

// 执行结果:
D.func() 0
  1. 首先构造 D 对象的同时,会先调用 B 的构造方法。
  2. B 的构造方法中调用了 func 方法,此时会触发动态绑定,会调用到 D 中的 func
  3. 此时 D 对象自身还没有构造,此时 num 处在未初始化的状态,所以值为 0

结论:"用尽量简单的方式使对象进入可工作状态",尽量不要在构造器中调用方法,因为如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成,可能会出现一些隐藏的但是又极难发现的问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 继承
    • 一、概念
    • 二、继承语法 -- extends
    • 三、访问父类成员 -- super
    • 四、子类的构造方法💥
    • 五、super 和 this 的关系
    • 六、再谈初始化
    • 七、protected 关键字
    • 八、继承方式
    • 九、final 关键字
    • 十、继承和组合
  • Ⅱ. 多态
    • 一、多态实现条件
    • 二、重写/覆盖 -- override
    • 三、向上转型
    • 四、向下转型 -- instanceof
    • 五、多态的优缺点
    • 六、避免在构造方法中调用重写过的方法☢️
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档