Java 继承

继承是什么

继承通常用来表示类之间的 is-a关系,比如猫是一种哺乳动物;汽车是一种机器,香樟树是一种植物等,继承符合人类的认知思维,比较好理解

继承

继承有单继承和多继承之分。Java 中的继承是单继承。继承的意义一是可以代码复用,相同的属性和方法可以抽取到父类之中,在子类中直接调用,避免重复的代码。

但是继承也有很多问题,如果继承层次如果过多,代码的阅读会非常复杂,且子类和父类高度耦合,修改父类代码有时会影响子类。也因此有些人支持多用组合少用继承。

但是不管怎么说,继承都是面向对象中的重要概念,需要详细的介绍一下。

为什么要继承

有时候,你会遇到大费周折的创建了一个类后,又要创建一个和它只有一点点差异的类,如果重写拷贝一份代码,这种感觉不太好受,我们希望能复制现有的类,然后在此基础上对齐进行增改,那感觉就十分美妙了。

继承相关概念

父类:又叫基类,超类,指最初的类,比如猫是一种动物中,动物是猫的父类。

子类:又叫派生类,继承类,比如猫是一种动物中,猫是子类,是父类动物的派生类。

举例:JDK 中 ArrayList 类继承了父类 AbstractListAbstractList 又继承了 AbstractCollection。这里可以说 AbstractCollectionAbstractListArrayList 的父类,ArrayList 是两者的子类。

ArrayList 继承关系

Java 的继承

Java 中的继承使用关键字 extends 表示,比如我们人类都需要吃饭睡觉,而学生作为人类的基础上,还要进行学习,用 Java 的继承表示就是下面的代码。

class Person {
    public void eat() {
        System.out.println("吃饭");
    }

    public void sleep() {
        System.out.println("睡觉");
    }
}

class Student extends Person {
    public void study() {
        System.out.println("学习");
    }
}

如果没有继承功能,那么这个吃饭睡觉的人类共同需求,就需要在遇到学生、老师、打工人等角色定义时重复一遍。

在 Java 中,子类会继承父类除了 private 权限的所有信息,比如属性、方法、接口等。

public static void main(String[] args) {
    Student student = new Student();
    student.eat(); // 吃饭
    student.sleep(); // 睡觉
    student.study(); // 学习
}

Java 继承的特点

单继承

Java 单继承

在 Java 中,继承只能是单继承,用上面的例子来说,如果 Student 继承了 Person ,那么它就不能再继承其他任何类了。你想像下面这样,在 Java 中是不支持的。

class Person{
}
class ClassXX{
}
class Student extends Person,ClassXX{ // 报错,不支持

}

因为单继承的原因,所以在 Java 中,一个类最多只有一个父类Object 类是所有类的父类。因此 Java 是单根层级结构,这让我们可以方便的使用 Object 类型来作为通用类型。

子类权限

子类可以调用父类的非 private 属性和方法,使用 super 关键词可以调用父类的属性或信息,使用 this 关键字调用当前子类的属性或信息。

class Student extends Person {
    public Student() {
        super(); // 调用父类无参构造
    }
}

如果 sleep 方法是被 private 修饰,那么不能被继承。

class Person {
    public void eat() {
        System.out.println("吃饭");
    }

    private void sleep() {
        System.out.println("睡觉");
    }
}

class Student extends Person {
    public void study() {
        System.out.println("学习");
    }
}

此时使用 Student 对象调用 sleep 是会报错的。

Student student = new Student();
student.eat();
student.sleep();
student.study();
// java: 找不到符号
//  符号:   方法 sleep()

方法重写

在继承时,子类可以重写父类的方法实现,比如下面的例子中,子类 Student 重写了父类中的 Sleep 方法。

class Person {
    public void eat() {
        System.out.println("吃饭");
    }

    public void sleep() {
        System.out.println("睡觉");
    }
}

class Student extends Person {
    public void study() {
        System.out.println("学习");
    }

    @Override
    public void sleep() {
        System.out.println("上课时不能睡觉");
    }
}

子类调用方法时会输出自己的方法实现结果。

Student student = new Student();
student.sleep();  // 上课时不能睡觉

多重继承

Java 虽然只能单继承,但是多重继承是被允许的,像下面这样。

Java 多重继承

子类和基类有相同类型

在方法 printSleep 中,指定参数类型为 Person,但是我们传入 Person 的子类。

package com.wdbyte.oop;

public class JavaExtends {
    public static void main(String[] args) {
        Student student = new Student();
        printSleep(student);
    }
    public static void printSleep(Person person){
        person.sleep(); // 上课时不能睡觉
    }
}

class Person {
    public void eat() {
        System.out.println("吃饭");
    }

    public void sleep() {
        System.out.println("睡觉");
    }
}

class Student extends Person {
    public void study() {
        System.out.println("学习");
    }

    @Override
    public void sleep() {
        System.out.println("上课时不能睡觉");
    }
}

Java 继承多个

从上面的介绍中可以发现,在 Java 中继承只能是单继承,如果有时我们需要多继承,可以通过接口的多继承特性来实现类的多继承。

interface One {
    public void print_geek();
}
interface Two {
    public void print_for();
}
interface Three extends One, Two {
    public void print_geek();
}
class Teach implements Three{

    @Override
    public void print_for() {

    }
    @Override
    public void print_geek() {

    }
}

他们的继承关系图如下:

Java 通过接口实现继承多个

Java 继承的优点

  1. 复用性:继承允许子类继承父类的所有方法和属性,从而避免了重复编写类似的代码。
  2. 扩展性:通过继承,子类可以添加新的方法和属性,从而扩展父类的功能。
  3. 多态性:继承允许子类重写父类的方法,从而实现多态性,即同一个方法在不同情况下会呈现不同的行为。
  4. 代码逻辑清晰:通过继承,可以将代码结构化,并使代码逻辑更加清晰。
  5. 可维护性:通过继承,可以提高代码的可维护性,因为当需要修改某个方法或属性时,只需要修改父类即可,而不必修改每个子类。

Java 继承的缺点

  1. 牵一发而动全身:继承具有传递性,子类继承了父类的属性和方法,如果父类中某个属性或方法需要修改,那么可能会影响到所有继承了该父类的子类,导致代码的不稳定和难以维护。
  2. 继承的滥用:有些开发者可能过度使用继承,导致类之间的继承关系过于复杂,难以理解和维护。
  3. 破坏封装性:继承允许子类访问父类的 protected 成员变量和方法,虽然保护了一定的封装性,但是也可能破坏了类的封装性,导致代码的安全性下降。
  4. 耦合度高:类之间的继承关系会造成紧耦合,一个类的修改可能会影响到其他的类,造成不必要的麻烦。
  5. 限制类的设计:继承是静态的,一旦继承关系建立,就不能轻易地改变。如果后面需要修改继承关系,可能需要对大量的代码进行修改,增加了代码的维护成本。同时,也限制了类的设计和扩展。

is-a 和 is-like -a

通过介绍,可以发现在继承实现时,通常有两种区别。

  1. 简单继承,不增加新的方法,可能会对父类的中的方法进行方法重写。这时子类和父类符合 is-a 关系,也可以称为替换原则这是一种比较理想的继承方式
  2. 继承后增加新的方法,扩展原来类的功能,这时符合 is-like-a 关系,只能说明子类和父类很像,这种继承方式不如第一种好,会增加代码的复杂度。

一如既往,文章中代码存放在 Github.com/niumoo/javaNotes.