【JavaSE】(5)继承和多态

时间:2024-11-18 18:54:45

目录

一、继承

1、继承的含义及作用

2、示例代码

3、子类对象的内存存储

4、super

5、父类和子类成员的访问顺序

6、重写

7、初始化成员变量

8、继承方式

9、final

10、protected

二、组合

三、多态

1、概念#

2、实现多态的条件

3、向上转型#

4、向下转型

5、动态绑定#

6、多态的优势

7、不要在构造方法中调用重写方法


一、继承

1、继承的含义及作用

        一类可能含有多个子类,如:动物的子类有,猫、狗……。这些子类具有共性,将共性提取出为父类,如:动物的共性是,有名字、年龄……

        继承则是子类(派生类)继承父类(基类/超类)的成员(共性)。

        继承实现了共性的抽取,代码复用

        继承就是 is-a 的关系:猫 is a 动物。

2、示例代码

        标志:extends

        父类:Animal

package inherit;

public class Animal {
    public String name;
    public int age;

    public void eat() {
        System.out.println(this.name+"在吃饭...");
    }
}

        子类:Cat、Dog

package inherit;

public class Cat extends Animal{

    public void mimi() {
        System.out.println(name+"咪咪叫.....");
    }
}
package inherit;

public class Dog extends Animal{

    public void bark() {
        System.out.println(name+"汪汪叫....");
    }

    public void wag() {
        System.out.println(name+"在摇尾巴...");
    }
}

3、子类对象的内存存储

        继承了父类的成员变量:

c559d2e0468c43e383926efed41ebb98.png

        如果,父类 Base 和子类 Derived 分别所包含的成员变量如下:

220017f818a74592afe231b8766cdfd9.png

78bb77d5057641fc95037bd9f5ac7922.png

        那么子类对象的内存存储:

c99df27d8a1b4a4b882e646dab29fd5c.png

4、super

  • super.data 访问父类的成员变量。
  • super.func() 访问父类的成员方法。
  • super() 调用父类的构造函数(必须在第一行,因此不能与 this() 共存)。
  • 不能在静态成员方法中使用(没对象,就没 this,就没 this 的 super)。

5、父类和子类成员的访问顺序

        成员不同:看子类->看父类->都没有则报错。

        成员相同:优先子类

        成员相同想访问父类的:用 super

6、重写

        父类和子类的成员方法:

96c70f1e0cf149eb916b9bb7fa7acf39.png

  • 重载(overload):方法名相同,参数列表不同,返回值不要求。
  • 重写(override):方法名相同,参数列表相同,返回值类型“相同”。

注意:

① 返回值类型可以不同,要求是存在父子关系。如:返回值类型是 Animal 和 Cat。

② 子类重写的方法的修饰词不能比父类的权限更低。如父类为 protected,子类就只能是 protected 或 public。

static、private、final修饰的方法,以及构造方法不能重写

@Override 注解可以检查子类中重写的方法在父类中是否存在。如:父类是 eat,子类写成 eat2 就会报错,没有注解就没提示,不改过来重写就没成功。

为什么需要重写:

        实际生活中,开发出新手机程序,不可能直接去掉旧版的方法(还有老用户),而是在子类中重写旧版方法。

7、初始化成员变量

        有父才有子,初始化子类成员前,一定要先初始化父类成员

        ① 构造函数初始化普通成员变量:

c5f0353f6ce648339033685b2578e12e.png

        注:若父类存在参数列表为空的构造函数(默认或者自己定义),编译器会自动在子类构造函数的第一行加 super(),即子类的构造函数中必须有 super()。但是 this() 没手动加,就没有,即不是必须有的。

        ② 代码块的执行顺序:

9ec6f51c4ab6452d9db99ba5417f12c7.png

b9ddece10efb475d97f3dd402d458b83.png

5bc97073cd0c4a6bbd86a4e350ce1bc9.png

        父类静态->子类静态->父类实例->父类构造->子类实例->子类构造

8、继承方式

  • 不支持多继承:即不支持一个类有多个父类。
  • 最好不超过三层继承关系。

9、final

  • 修饰变量:常量。

final 修饰数组对象的引用 array,array不能更改,但数组对象能更改。

bdc28e0c9ffe4d76af19dda344359b40.png

  • 修饰类:不能被继承。

如:Cat 这个类不能再被其他类继承。

6bab4b7e4a6143c2b208bea6373e9b86.png

  • 修饰方法:不能被重写。(后续学习)

10、protected

        修饰类中成员,可以被同一包中的类访问、可以被不同包中的子类访问。

        子类会继承父类中的 private 成员变量,只不过不能访问。

二、组合

        组合就是 has-a 的关系,用一个类的实例作为另一个类的字段,传入的实例是动态的。如:车 has 轮胎、方向盘、喇叭......。

class Car{
    private Tire tire;          // 复用轮胎
    private Engine engine;      // 复用发动机
    private VehicleSystem vs;   // 复用车载系统
    ......
}

        组合和继承都可实现代码复用,同样可行的情况下,最好用组合(整体类与局部类之间相对独立、更灵活)。

三、多态

1、概念#

        同一个声明,不同的对象,实现不同的行为(多种形态,即多态)。如:动物(声明)的实例对象(不同对象),可以是猫(吃猫粮)、狗(吃狗粮)......。

2、实现多态的条件

  • 继承关系
  • 子类对父类方法实现重写(定义不同的行为)。
  • 用父类引用(向上转型)调用子类重写方法(实现同一声明)。

3、向上转型#

  • 用父类类型引用变量,引用子类类型对象。
Animal animal = new Dog("小七", 3);
  • 用父类类型引用形参,接受子类类型对象。
public void func(Animal animal){
    ......
}

public static void main(String args[]){
    Dog dog = new Dog("小七", 3);
    func(dog);
}
  • 用返回值类型为父类类型的方法,返回子类类型对象。
public Animal func(){
    Dog dog = new Dog("小七", 3);
    return dog;
}

        一个例子:toString。Object 是所有类的父类,Dog 与 Object 存在继承关系,println 的形参就属于上述的第二种向上转型(父类的引用),toString 定义在 Object 类中,Dog 类就可以重写 toString。Object 的引用调用 Dog 的重写方法,这样就实现了多态,同一个 println 方法根据不同的对象实现不同的打印。

ca11010c3eaf48fab6cfa45e713dfd90.png

注:向上转型,不能调用子类特有的方法

4、向下转型

        向上转型后,不能使用子类特有方法想使用,需要向下转型回来。

Animal animal = new Dog("小七", 3);
Dog dog = (Dog)animal; // 向下转型
dog.bark();

        但是不能乱转,乱转会报错,不安全,比如:

Animal animal = new Dog("小七", 3);
Cat cat = (Cat)animal; // 类型转换错误

        为了避免错误,使用 instanceof 检验:

Animal animal = new Dog("小七", 3);
if(animal instanceof Cat){
    Cat cat = (Cat)animal;
    cat.mew();
}
else
    System.out.println("不能转换");

5、动态绑定#

8d5312a3f9324137bc9687c7c9d85b14.png

        用 javap -c 字节码文件名 查看反汇编文件:

7a834c4d79bc489b8fcebc2d5fb98094.png

        动态绑定:编译时期调用的父类方法,运行时期根据具体对象调用对象的方法(灵活)。即在运行时期才知道方法的具体实现(哪个类当中的)。

        静态绑定在编译时期,根据方法名、方法的参数列表等,就确定方法的具体实现

6、多态的优势

        ① 减小“圈复杂度”,即循环、if-else 的个数。比如,有以下继承关系:

// 父类
public class Animal {

    public void eat() {
        System.out.println(" 正在吃饭....");
    }
}

// 子类 Dog
public class Dog extends Animal{

    @Override
    public void eat() {
        System.out.println(" 正在吃狗粮..");
    }
}

// 子类 Cat
public class Cat extends Animal{

    @Override
    public void eat() {
        System.out.println(" 正在吃猫粮...");
    }
}

        实现一个各种动物数组的循环 eat,if-else 版:

8e449ae3302d4101a5eb7642a98854d6.png

        多态版:

cc1d56bdc9ac40eca22e1f6a2d3dbc46.png

        子类越多,这种优势越明显。

        ② 扩展性强。一个类,不需要改动原有代码。比如:增加鸟类,用多态,实现鸟类即可;用 if-else,还得改原有代码的 if-else 部分。

注意:成员变量和构造方法,没有多态性

7、不要在构造方法中调用重写方法

// 父类
 class B {
    public B() {
        // do nothing
        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

        分析:创建子类 D 对象->执行父类构造函数->因为子类重写func,执行子类的func->打印 D.func() 0 (0 是 num 的默认值)->执行子类实例成员的初始化,此时才为1。

        为了避免这种不易发现的错误,不要在构造函数中调用重写方法。