【从基础学 Java】继承

来源:互联网 发布:世界史书推荐知乎 编辑:程序博客网 时间:2024/04/24 10:40

和现实世界中:子女可以继承父母的一些特征(如:基因)、财产
等一样。OOP 中也有提供类似的特性,一个类完全可以从其它类里获得一些属性和方法,而不需要我们自己重新定义。这种特性简单但强大 (Simple and powerful)。

快速了解继承

在Java的继承关系里:子类可以从获取父类的所有的公共和受保护成员(字段、方法和内部类)。当然,构造方法不是成员 (members) ,所以不能被继承。同时,在 Java 的继承里,子类可以做如下事情:

  • 直接使用继承来的字段
  • 直接使用继承来的方法
  • 声明和父类同名字段,隐藏 掉父类字段
  • 通过父类提供的共有/受保护的方法访问父类的私有成员
  • 创建新的字段
  • 重写父类同方法签名的实例方法,隐藏 掉父类方法
  • 重写父类同方法签名的静态方法,隐藏 掉父类方法
  • 编写父类中不存在的方法
  • 使用 super 关键字,利用父类构造方法

覆盖

当子类拥有和父类(或接口)同样方法签名的方法,这种现象叫做覆盖。如以下代码:

class Father{    public void doSomthing(){        // Father do something    }}class Son extends Father{    @Override    public void doSomething(){        // Son do something    }}

Java 规定了子类有以下覆盖行为:

  • 覆盖父类实例方法
  • 覆盖父类静态方法
  • 覆盖接口默认方法 (JDK8+)

对于实例方法的覆盖,实际是子类拥有自己的方法。
对于静态方法的覆盖,要记住:静态方法时属于类的,在多态中,调用的始终是类的方法。

    Father father = new Son();    Father.staticMethod(); // 这里使用的是父类Father的静态方法

对于接口方法的覆盖,遵循以下原则:
1.实例方法的优先级大于接口方法 (JDK8+)

interface Animal{    default void saySomething(){        // Animal say something    }}interface Cow extends Animal{    default void saySomething(){        // Cow say something    }}class MyCow implements Cow{    @Override    public void saySomething(){        // MyCow say something    }    Animal myCow = new MyCow();    myCow.saySomething(); // MyCow say something}

2.有共同祖先的接口,先被覆盖的方法(继承深度低)会被后覆盖的方法(继承级别高)覆盖

interface Animal{    default void saySomething(){        // Animal say something    }}interface Pig extends Animal{    default void saySomething(){        // Pig say something    }}interface BigPig extends Pig{    default void saySomething(){        // BigPig say something    }}class MyPig implements Pig, BigPig{    public static void main(String...args){        MyPig myPig = new MyPig();        myPig.saySomething(); // BigPig saySomething()    }}

P.S. 如果出现同一继承级别(上例中 BigPig 和 Pig 都继承 Animal 接口)或者子类继承的接口无相关关系,但是接口间有同方法签名的方法,就会出现覆盖冲突。需要用super关键字指明具体实现哪个接口的方法或者直接覆盖。

interface Run{    default void run(){        // Run run    }}interface Car{    default void run(){        // Car run    }}class MyCar implements Run, Car{    @Override    public void run(){        Car.super.run();    }}

同时,我们需要注意,Java 子类覆盖父类方法,应该:

  • 方法的访问权限大于等于父类
  • 方法的返回值小于等于父类
  • 方法抛出的异常小余等于父类

多态

我们已经知道,子类可以覆盖父类的方法。如果有一个类继承自另外一个类,我们完全可以用一个父类来引用一个子类,如:

class Person{    public void saySomething(){        // Person say something    }}class Father{    @Override    public void saySomething(){        // Father say something    }}class Test{    pubilc static void main(String...args){        Father father = new Father();        Person person = father; // 父类引用子类对象    }}

像这种父类引用子类对象的现象,Java 里叫做多态 (Polymorphism)。在 Java 里,只有满足如下三个条件,才能叫多态:

  • 继承关系
  • 覆盖方法
  • 父类引用子类对象

对于多态方法的运行,编译器会列举所有父类和子类符合调用方法签名(方法名+参数列表)的方法,然后以以下原则编译、调用方法:

  • 成员变量(编译和运行都看左边)
  • 成员方法(编译看左边,运行看右边)
  • 静态方法(编译和运行都看左边)

其中,调用 private, final, static 方法的过程叫静态绑定,否则叫动态绑定
在使用多态的过程中,最有效的判断语句能否通过编译的方法是:

右边的类是否是(IS-A)左边的类

如示例代码里的 father(Father) IS-A Person。

阻止继承

有些情况下,我们可能不希望子类覆盖父类的方法,这时候,用final关键字修饰方法即可实现该目的。编译器可以对用final的方法进行内联操作优化处理。

强制转换

在多态里,我们知道一个父类可以引用一个子类对象:

Father father = new Son();

但是,反过来就不行了:

Son son = new Father();

如果需要让编译器不报错,我们就得进行强制类型转换操作:

Son son = (Son)new Father();

不过,这么做有风险,我们一般要使用 instanceof关键字先判断,能否将父类“安全”转换成子类:

Father f = ...;if (f instanceof Son){    Son son = (Son)f;}

抽象类与接口

在继承体系里,比较常用的就是抽象类和接口。它们比较相似:

  • 都能被继承
  • 都包含抽象方法
  • 都不能被实例化

然而,它们还是有不同的,例如:

  • 抽象类可以声明非final&&static的字段,接口不行(默认public static final)
  • 抽象类可以声明非public方法,接口不行(默认方法是public)
  • 一个类只能继承一个抽象类,可以继承多个接口

然后,抽象类和接口有不同的使用场景:P
抽象类:

  • 在相关类里共享代码
  • 规定了一系列通用的方法和属性
  • 需要定义非静态、非共有的方法

接口:

  • 定义属性,如可比较 (Comparable)、可飞(Flyable)
  • 定义行为,不关心具体实现
  • 希望是多继承的一部分

继承的技巧

在 Java 里,我们一般按照如下规则使用继承:

  • 将公共操作放在超类(父类)中
  • 不要使用受保护的域
  • 继承严格遵循is-a原则
  • 不要过多得使用反射
0 0
原创粉丝点击