【JAVA】一文弄清什么是`继承`,`多态`,`向上转型`,`向下转型`

对于刚刚学习 java 的小白来说,初次面对对象的时候难免会觉得有些不知所措。在这篇文章中我将写下我自己对于继承多态向上转型向下转型的一些理解 希望我的文章对你更深刻的理解 java 的这些概念能有帮助


继承

在生活中我们会说继承家产,继承的意思就是孩子能拥有父母的遗产面对对象语言中也有两个概念父类,子类.分别就是父亲孩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal { // 父类 Animal
public void eat() {
System.out.println("Animal eat something");
}
}

class Dog extends Animal { // 子类 Dog
}

public class Main {
public static void main(String[] args) {
Dog a = new Dog();
a.eat(); // 直接输出Animal eat something
}
}
在上面这个示例中 是一种动物 Dog 是子类(孩子), Animal是父类(父亲) 在 java 中我们把这个 是一种 的概念使用 extends关键字来表示

因为Dog(子类)继承Animal(父类),所以Dog 也就有了 Animal 的方法,创建了一个新的Dog(子类)的时候,就能直接调用父类的方法了.

  • 如果没有继承,a.eat() 会报编译错误,因为 Dog 没有这个方法
  • 但是 Dog 是 Animal 的子类,有extends继承,Dog 自动拥有 Animal 的方法,a.eat()有输出 这个就是继承.

多态

还是这个例子 但是这次我们要使用多态了

  • Dog 和 Cat 都有自己的方法,我们看看会发生什么
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Animal { // 父类 Animal
    public void eat() {
    System.out.println("Animal eat something");
    }
    }

    class Dog extends Animal { // 子类 Dog
    public void eat() {
    System.out.println("Dog eat something");
    }
    }

    class Cat extends Animal { // 子类 Cat
    public void eat() {
    System.out.println("Cat eat something");
    }
    }

    public class Main {
    public static void main(String[] args) {
    Animal a = new Dog(); // <- 注意这里改变了
    Animal b = new Cat();
    a.eat(); // 输出 Dog eat something
    b.eat(); // 输出 Cat eat something
    }
    }
    我们发现 a,b 这两个对象都用相同的引用 Animal,但是他们调用 eat 方法的时候表现却不同 输出变成了Dog eat somethingCat eat something 这就是多态 多态 就是同一引用的多种表现状态

首先说明Animal a = new Dog();这一句是什么意思,为什么 a 这个对象的类型是 Animal 却用 Dog() 来创建? - 实际创建的是 Dog 对象(决定了实际行为),但用 Animal 类型的引用来操作它(决定了能调用哪些方法) 详见[[【JAVA】Animal a = new Dog();到底是什么意思]]

为什么会产生多态? a 的类型是 Animal,但调用 eat() 时,JVM 在运行时发现 a 实际指向的是 Dog 对象,于是执行 Dogeat(),输出Dog eat something 详见[[【JAVA】动态绑定与静态绑定|【JAVA】动态绑定与静态绑定]]

引申: @Override 重写 重写是指:子类重新定义父类已经有的方法,方法名、参数、返回类型都要一样,但方法体(里面的代码)不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}

class Dog extends Animal {
@Override
public void eat() { // 方法签名和父类完全一样
System.out.println("狗在啃骨头"); // 但实现不同
}
}

class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
}

说明

  • 重写的前提是继承。没有继承关系,就没有重写。
  • 方法签名(方法名 + 参数列表)必须一致,否则就变成”重载”了(这是另一个概念) >重写是多态的基础,多态是重写的效果

向上转型(Upcasting)

子类对象赋值给父类引用,就是向上转型

如下继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}

class Dog extends Animal {
public void eat() {
System.out.println("狗在啃骨头");
}

public void bark() {
System.out.println("汪汪汪");
}
}
1
2
3
Animal a = new Dog();   // 向上转型,自动完成,不需要强制
a.eat(); // 输出:狗在啃骨头
// a.bark(); // 编译错误!父类引用看不到子类特有的方法

几个要点:

  • 安全的,自动进行。因为狗”本来就是”动物,所以把它当动物看待没有任何风险
  • 看得见的方法变少了a 的类型是 Animal,编译器只允许你调用 Animal 类里有的方法。bark()Dog 独有的,所以调不到。
  • 但实际执行的还是子类的方法a.eat() 调用的是 Dogeat(),这就是多态

几种方式: 1. 直接赋值

1
Animal a = new Dog();
2. 方法传参
1
2
3
4
5
6
public void fun1(Animal a) {

}

Dog a = new Dog();
fun1(a);
3. 返回值
1
2
3
4
5
public Animal fun2() {
Dog a = new Dog();
return a;
}
Animal b = fun2();


向下转型(Downcasting)

父类引用强制转回子类引用,就是向下转型。

1
2
3
Animal a = new Dog();    // 先向上转型
Dog d = (Dog) a; // 向下转型,必须强制转换
d.bark(); // 输出:汪汪汪,现在能调用 Dog 特有的方法了

几个要点:

  • 必须显式强制转换,写 (Dog)

  • 有风险。如果对象本来不是 Dog,运行时会抛 ClassCastException

    1
    2
    Animal a = new Animal();   // 创建一个 a 是 Animal对象,不是 Dog 对象
    Dog d = (Dog) a; // 编译能过,运行时崩溃!

  • 转之前用 instanceof 检查更安全

    1
    2
    3
    4
    if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.bark();
    }


总结

继承

  • 子类自动获得父类的方法和属性。Dog extends Animal,所以 Dog 天生就有 eat() 方法

多态

  • 父类引用调用方法时,实际执行的是子类重写后的版本

向上转型

  • 子类对象赋值给父类引用

向下转型

  • 父类引用强制转回子类引用