跳转至

Java 继承与多态⚓︎

约 1776 个字 54 行代码 预计阅读时间 10 分钟

继承⚓︎

继承(inheritance) 是面向对象设计方法论的重要组成部分,是一种将一个类的行为或实现定义为另一个类的超集的能力。简单地说,就是调用另一个类的方法和属性的能力。

继承的语法如下:

1
2
3
class ThisClass extends SuperClass {
    // class body
}

继承类型⚓︎

Java 不支持多继承,但支持多重继承。

不能同时继承多个类,但是每个类的父类都可以有一个父类(可以变相实现继承多个类)

继承的性质⚓︎

继承的属性和方法

  • 子类拥有父类非 private 的属性、方法/子类对象的一部分就是父类对象--> 子类构造函数执行前,必须先初始化父类
  • 子类可以拥有自己的属性和方法
  • 子类可以用自己的方式实现父类的方法(重写)

继承的实现

  • 继承可以使用 extendsimplements 实现,而且所有的类都是继承于 java.lang.Object
  • 当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。

不会被继承的东西

  • 构造函数
  • 子类的构造函数需要自己写
  • new 构造子类时,如果没有显式传递参数,子类会调用默认构造函数,父类会调用不父类中不含参的构造方法
  • new 构造子类时,如果有显式传递参数,子类含对应参数的构造函数必须用 super 调用父类中对应的带参构造器
  • 子类构造函数执行前,必须先初始化父类
  • 私有数据被隐藏,但仍然存在

向上转型(upcasting):把子类对象当成父类来看待。

  • 逻辑:因为“狗”一定是“动物”,所以 Animal a = new Dog(); 是绝对安全的。
  • 后果:编译时,变量 a 的结构和方法都是 Animal 的。

方法调用绑定(method call binding)

  • 静态绑定
  • 定义:在程序 编译阶段(还没运行),编译器根据你 变量的类型 就决定了调用谁。
  • 适用对象
    1. 字段(属性):Java 属性不支持多态。
    2. 私有方法 (private):外部看不见,无法重写。
    3. 静态方法 (static):属于类,不属于对象。
    4. 构造函数:创建对象时固定调用的。
  • 动态绑定
  • 定义:在程序 运行阶段,JVM 根据 实际在内存里的那个对象 是谁,来决定调用谁。
  • 适用对象:普通的实例方法(即被 override 的方法)。

  • 总结

  • 编译看左边:属性、静态方法、私有方法,左边变量是什么类型,就用谁的。
  • 运行看右边:普通实例方法,右边 new 出来的是什么对象,就执行谁的。
class Super {
    public String name = "父类属性";

    public void show() {
        System.out.println("执行:父类方法");
    }

    private void secret() {
        System.out.println("父类的秘密");
    }
}

class Sub extends Super {
    public String name = "子类属性";

    @Override
    public void show() {
        System.out.println("执行:子类方法");
    }

    // 这不是重写,因为父类的 secret 是 private,子类看不见
    public void secret() {
        System.out.println("子类的秘密");
    }
}

public class BindingTest {
    public static void main(String[] args) {
        // 向上转型
        Super obj = new Sub(); 

        // 1. 字段:静态绑定
        // 编译器看 obj 的类型是 Super,所以直接找 Super 的 name
        System.out.println(obj.name); // 输出:父类属性

        // 2. 方法:动态绑定
        // 运行时发现 obj 实际上是 Sub 对象,所以执行 Sub 的 show
        obj.show(); // 输出:执行:子类方法

        // 3. 构造函数与私有方法
        // 它们在编译时就固定了,不会因为对象不同而产生多态
    }
}

多态⚓︎

多态是同一个行为具有多个不同表现形式或形态的能力。

  • 在多态下,引用对象(reference variable) 和对象类型(object type)可以是不同的类型

  • 运用多态,引用类型可以是实际对象类型的父类


方法的重写和重载⚓︎

重写/覆盖的规则

  • 参数必须要一样,且返回类型必须要兼容
  • 父类使用了哪种参数,覆盖此方法的子类也一定要使用相同的参数
  • 父类声明的返回类型是什么,子类必须要声明一样的返回类型

  • 不能降低方法的存取权限

  • 存取权限必须相同,或更为开放
  • 不能覆盖掉一个 public 方法并将它标记为 private

重载的意义,是两个方法的名称相同,但参数不同


抽象⚓︎

抽象类⚓︎

抽象类是对所有从它派生出来的类的一个 公共接口。包含抽象方法的类。用 abstract 来声明

  • 禁止实例化:抽象类不能实例化对象,它必须被继承,才能被使用,或者说抽象类是一种特殊的,专门用来被继承的类可以视作是模板

  • 允许“残缺”:它可以包含 抽象方法(用 abstract 修饰,没有方法体 {},只有一个签名)

抽象方法⚓︎

也是用 abstract 来声明,是不完整的,只有声明,没有方法体。

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
  • 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

接口⚓︎

接口是完全的抽象类,用于被实现方法。通过 interface 声明

接口与类 、抽象列的区别

  • 成员变量:接口不能包含成员变量,除了 static 和 final 变量。

  • 接口中所有的数据成员都是 public static final

  • 构造方法: 接口没有构造方法。

  • 方法: 接口中的所有方法都是 public

  • 接口中所有的方法必须是抽象方法(JDK 1.8 以后, 接口中可以使用 default 关键字修饰的非抽象方法、JDK 1.9 以后,允许将方法定义为 private

  • 实例化: 接口不能用于实例化对象。

  • 继承: 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承--一个类只能继承一个抽象类,而一个类却可以实现多个接口。(继承与实现)
  • 一个接口能继承另一个 / 多个接口,但不能继承自类

接口特点

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。
1
2
3
4
5
6
//e.g.
/* 文件名 : Animal.java */
interface Animal {
   public void eat();
   public void travel();
}

接口的实现

...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

接口的多继承

接口支持多重继承,可能会遇到两个接口都提供了具有相同签名的默认方法的情况。有以下三条法则

  1. 任何类都优于任何接口。所以如果超类链中有一个带有方法体或抽象声明的函数,我们可以完全忽略接口。
  2. 子类型优于附类型。如果我们有两个接口抢着提供默认方法,并且其中一个接口扩展另一个接口,此时子类获胜。
  3. 没有规则 3。如果前两条规则不能给我们答案,子类必须实现该方法或声明它为抽象。

接口中的静态方法

  • 属于接口本身,不被实现类继承,只能通过接口名调用,必须带方法体
  • 默认 public,不允许写 abstractdefaultsynchronized

类型 能不能 new? 能不能有抽象方法? 存在意义
普通类 不能 它是具体的,可以直接拿来用。
抽象类 不能 “半成品”。提取公共特征,强迫子类统一标准。
接口 不能 全是(默认) “协议”。定义具备某种能力(如:会飞、会游泳)。