10. 继承与多态

本文较干,记得备水!

为什么要继承?

Java中使用使用类区队现实世界中的东西进行描述,但是现实世界错综复杂,事物与事物之间都会存在着一些关联,那在设计程序就是需要考虑。
比如:猫和狗,它们都是动物
而我们在学过类与对象以后就可以设计出:

class Dog {
    public String name;
    public int age;
    public double weight;

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

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

class Cat {
    public String name;
    public int age;

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

    public void miaomiao() {
        System.out.println(name + "正在喵喵叫~");
    }
}

然而我们通过对比不难发现,其中有些代码是一样的

那么我们可以简化一下代码么?答案:是可以的!!

面向对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用

接下来就是我们要讲的继承了

//创建一个Animal类
//将Dog类与Cat类中共有的属性放入Animal类中
//再使用extends关键字,将父类Animal对子类Dog、Cat进行继承

class Animal {             //父类
    public String name;
    public int age;

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

class Dog extends Animal{           //子类
    public double weight;

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

class Cat extends Animal{       //子类
    public void miaomiao() {
        System.out.println(name + "正在喵喵叫~");
    }
}

上述代码中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

而在内存当中他是如何存储的呢?请看下图

此图为本人理解的存储方式,若与实际不符,希望有朋友可以指点一二!!!
非常感谢!!!

在这里插入图片描述

class A {
    int a = 1;
    int b = 2;
}

class B extends A{
    int c = 3;
    int d = 4;
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
}

在这里插入图片描述

这里可以看见B继承A,b.可以访问到类A和类B中的成员变量

super关键字

super 代表的是子类从父类那里继承过来的空间和地址

说的通俗点
可以用this. 去访问自己本身的东西
可以用super. 去访问父类的东西

子类与父类不存在同名

class A {
    int a = 1;
    int b = 2;
}

class B extends A{
    int c = 3;
    int d = 4;

    public void test() {
        System.out.println(this.a);
        System.out.println(this.b);
        System.out.println(this.c);
        System.out.println(this.d);
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.test();
    }
}

在这里插入图片描述

子类与父类有同名

class A {
    int a = 1;
    int b = 2;
}

class B extends A{
    int a = 3;
    int d = 4;

    public void test() {
        System.out.println(super.a);
        System.out.println(super.b);
        System.out.println(this.a);
        System.out.println(this.d);
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
        b.test();
    }
}

在这里插入图片描述

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

成员方法的访问规则与成员变量类似, 这里就不做过多的赘述了

注意:方法的重载不是必须在一个类当中

super和this

super 和 this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \color{red}{------------------------------------}

  • 相同点:
  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
  • 不同点:
  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用

  2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性

  3. this是非静态成员方法的一个隐藏参数,super不是隐藏的参数

  4. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造
    方法中出现

  5. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \color{red}{------------------------------------}

再谈初始化

思考:这串代码的执行结果是什么呢?

class A{
    public A() {
        System.out.println("类A:构造方法执行");
    }

    {
        System.out.println("类A:实例代码块执行");
    }

    static {
        System.out.println("类A:静态代码块执行");
    }
}

class B extends A{
    public B() {
        System.out.println("类B:构造方法执行");
    }

    {
        System.out.println("类B:实例代码块执行");
    }

    static {
        System.out.println("类B:静态代码块执行");
    }
}

public class Test {
    public static void main(String[] args) {
        new B();
    }
}

结果:

类A:静态代码块执行
类B:静态代码块执行
类A:实例代码块执行
类A:构造方法执行
类B:实例代码块执行
类B:构造方法执行

得出个人结论:先有父, 再有子,static在最前

通过分析执行结果,得出以下结论:
1、父类静态代码块优先于子类静态代码块执行,且是最早执行
2、父类实例代码块和父类构造方法紧接着执行
3、子类的实例代码块和子类构造方法紧接着再执行
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

继承方式

一般我们不希望出现超过三层的继承关系
如果想从语法上进行限制继承, 就可以使用 final 关键字

在这里插入图片描述
如果我想多继承,那么该如何实现呢?

为了解决多继承的问题,我们可以使用接口,后续介绍

多态

多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状
态。例如动物吃东西,表现在狗身上是吃狗粮,表现在猫身上是吃猫粮。

多态实现条件

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法

向上转型

从小范围向大范围的转换
方法一:

class Animal{
    public String name;

    public void eat() {
        System.out.println(name+"正在吃!");
    }
}
class Bird extends Animal{
    public String wing;

    public void flying() {
        System.out.println(name + "正在飞!");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird();
    }
}

在这里插入图片描述

向上转型以后,只能调用父类的成员方法及成员变量
在这里插入图片描述

方法的重写(又叫覆写或覆盖)

需满足条件

  1. 方法的返回值相同
  2. 方法的方法名相同
  3. 方法的参数列表【个数,类型,顺序都相同】
  4. static 的方法不能被重写
  5. 子类的访问修饰符,需要大于等于父类的访问修饰限定符

当这些条件都满足以后就是所谓的方法重写了

class Animal{
    public String name;

    public void eat() {
        System.out.println(name+"正在吃!");
    }
}
class Bird extends Animal{
    public String wing;

    public void eat() {
        System.out.println(name+"正在吃鸟粮!");
    }

    public void flying() {
        System.out.println(name + "正在飞!");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird();
        System.out.println(animal.name);
        animal.eat();
    }
}

在这里插入图片描述

如果方法不想被重写还是去加 final 密封方法
在这里插入图片描述

动态绑定(运行时绑定)

在代码编译的时候,还是调用的父类自己的
但是在运行的时候,程序发生了动态绑定 ---->调用子类自己的

静态绑定

在发生以下代码时已经发生了静态绑定
在这里插入图片描述

    public static void add(int a){
    }

    public static void add(int a,int b){
    }


    public static void main(String[] args) {
        add(1,2);
        add(1);
    }

还有向上转型的方法
方法二:

    public static void func(Animal animal) {

    }

    public static void main(String[] args) {
        Bird bird = new Bird();
        func(bird);
    }

在这里插入图片描述
方法三:

    public static Animal func(){
        return new Bird();
    }

本来要返回 Animal 类型的,现在却返回了 Brid 类型的

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

多态

多态的概念:

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状态

总的来说:同一件事情,发生在不同对象身上,就会产生不同的结果

多态实现条件

在java中要实现多态,必须要满足如下几个条件,缺一不可:

  1. 必须在继承体系下
  2. 子类必须要对父类中方法进行重写
  3. 通过父类的引用调用重写的方法

多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法。

多说无益,下面让我们来用代码去演示

class Animal{
    public String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name+"正在吃!");
    }
}
class Bird extends Animal {
    public String wing;
    public int age;

    public Bird(String name,int age) {
        super(name);
    }


    public void eat() {
        System.out.println(name+"正在吃鸟粮!");
    }

    public void flying() {
        System.out.println(name + "正在飞!");
    }
}

class Dog extends Animal{
    public int age;

    public Dog(String name,int age) {
        super(name);
    }

    public void eat() {
        System.out.println(name+"正在吃狗粮!");
    }
}

public class Test {

    public static void func(Animal animal) {
        animal.eat();
    }

    public static void main(String[] args) {
        Bird bird = new Bird("小鸟",3);
        Dog dog = new Dog("小狗",4);
        func(bird);
        func(dog);
    }

在这里插入图片描述

向下转型

一般情况下不使用(非常不安全) \color{red}{一般情况下不使用(非常不安全)} 一般情况下不使用(非常不安全)

父类类型给子类类型

在这里插入图片描述

    public static void main(String[] args) {
        Animal animal = new Bird("小鸟",3);
        Bird bird = (Bird) animal;
        bird.flying();
    }

那么为什么说非常不安全呢?
下面来看这段代码
在这里插入图片描述

    public static void main3(String[] args) {
        Animal animal = new Dog("狗子",4);
        Bird bird = (Bird) animal;
        ((Bird) animal).flying();
    }

在编译器并没有报错但是当去尝试运行对的时候就报错了
在这里插入图片描述
众所周知,狗子并不能飞,所以向下转型相当于埋地雷,不知道什么时候会自己踩上,所以非常不推荐去用

instanceof

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入了 instanceof ,如果该表达式为true,则可以安全转换。

在这里插入图片描述
其实就是判断 animal 所创建的对象和 instanceof 所指的对象是不是同一个

    public static void main4(String[] args) {
        Animal animal = new Dog("狗子",4);
        //判断 animal 是不是引用了Bird这个对象
        if (animal instanceof Bird) {
            // 不是所有的动物都是鸟
            Bird bird = (Bird) animal;
            bird.flying();
        }
    }

    public static void main(String[] args) {
        Animal animal = new Bird("小鸟",3);
        if (animal instanceof Bird) {
            Bird bird = (Bird) animal;
            bird.flying();
        }
    }

向上转型和向下转型的优缺点

向上转型的优点:让代码实现更简单灵活。

向上转型的缺陷:不能调用到子类特有的方法。

回顾练习

画图形

class Shape{
    public void draw() {
        System.out.println("画图形");
    }
}

class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
}

class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("○");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("△");
    }
}

public class Test {
    public static void drawMap() {
        Rect rect = new Rect();
        Cycle cycle = new Cycle();
        Triangle triangle = new Triangle();

        // 发生了向上转型
        Shape[] shapes = {rect,triangle,cycle,triangle};
        // 当前shape引用的对象重写draw方法就调用,没有就调用Shape自己的
        for (Shape shape:shapes) {
            shape.draw();
        }
    }

    public static void main(String[] args) {
        drawMap();
    }
}

在这里插入图片描述

使用多态的好处

  1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else

什么叫 “圈复杂度” ?
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如
果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.
如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .

  1. 可扩展能力更强

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

今天的分享就到此结束啦!!🥰🥰🥰
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \color{blue}{-------------------------------------}
呼~ 终于码完了~~

创作于2022年10月6日22:42:38

− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \color{blue}{-------------------------------------}

添加于2022年10月15日16:06:00

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new LinkedList<>();
        List<Integer> list3 = new Stack<>();

        System.out.println("=====================");

        ArrayList<Integer> list11 = new ArrayList<>();
        LinkedList<Integer> list22 = new LinkedList<>();
        Stack<Integer> list33 = new Stack<>();
    }

这些代码有什么区别呢?
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的阿漓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值