IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 多态?那不有手就行 -> 正文阅读

[Java知识库]多态?那不有手就行

向上转型

在上一篇 包和继承 当中我们写继承的关系的时候,写了这样的代码:

Bird bird = new Bird("圆圆"); 

当然,我们也可以写成这样:

Bird bird = new Bird("圆圆"); 
Animal bird2 = bird; 

或者下面这样:

Animal bird2 = new Bird("圆圆"); 

此时 bird2 就是一个父类(Animal)的引用,指向一个子类(Bird)的实例,这就是向上转型。就像可以把:你给笔记本电脑充电了吗?说成:你给电脑充电了吗? 因为笔记本电脑也是电脑。

为什么叫向上转型:因为在程序设计当中,有很多场景。为了表示这种关系,就可以把他们的关系图画出来,父类通常在子类的上方。所以就叫做向上转型,表示往父类的方向转。

向上转型–方法传参

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println(this.name + "正在吃" + food);
    }
}
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void fly() {
        System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
    }
}
public class Test4 {
    public static void feed(Animal animal) {
        animal.eat("谷子");
    }

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

在这里插入图片描述
这里的圆圆就是发生了向上转型,通过方法传参,拼接了吃谷子。

向上转型–方法返回

public class Test {
    public static void main(String[] args) {
        Animal animal = findMyAnimal();
    }
    public static Animal findMyAnimal() {
        Bird bird = new Bird("圆圆");
        return bird;
    }
} 

在这里方法 findMyAnimal 返回的就是一个 Animal 类型的引用, 但是实际上对应到 Bird 的实例。

**当然也可以通过直接进行赋值完成向上转型。**就是最上面的那种赋值方法。

动态绑定

动态绑定出现在子类和父类有相同方法名的时候。代码如下:

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println("这里是动物");
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void eat(String food) {
        System.out.println("这里是小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
}
public class Test4 {
    public static void main(String[] args) {
        Animal animal1 = new Animal("圆圆");
        animal1.eat("谷子");
        Animal animal2 = new Bird("扁扁");
        animal2.eat("谷子");
    }
}

结果如下:
在这里插入图片描述
通过输出我们可以发现:

animal1 和 animal2 虽然都是 Animal 类型的引用, 但是animal1 指向 Animal 类型的实例。
animal2 指向 Bird 类型的实例. 针对 animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而 animal2.eat() 实际调用了子类的方法。

所以,我们可以知道:在 Java 当中调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定。

方法重写

就像刚刚写的 eat 方法,子类实现了父类的同名方法,并且参数的类型和个数完全相同,就称之为:覆写/重写/覆盖

重写的注意

  1. 重写和重载完全不一样.。
  2. 普通方法可以重写, static 修饰的静态方法不能重写。
  3. 重写中子类的方法的访问权限不能低于父类的方法访问权限。
  4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)。

重写方法的权限

如果我们把子类当中的权限改成比父类小的话,就会报错。代码如下:

class Animal {
    public String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println("这里是动物");
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    private void eat(String food) {
        System.out.println("这里是小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
}
public class Test4 {
    public static void main(String[] args) {
        Animal animal1 = new Animal("圆圆");
        animal1.eat("谷子");
        Animal animal2 = new Bird("扁扁");
        animal2.eat("谷子");
    }
}

在这里插入图片描述
就会导致无法完成重写,所以我们在写的时候一定要注意权限的范围。

重写和重载的区别

如下图所示:
在这里插入图片描述

理解多态

学了向上转型,动态绑定,方法重写之后,我们就可以使用多态的形式来设计程序了,写一些弗雷可以兼容子类的代码。例如,打印多种形状:

class Shape {
    public void draw(){
        System.out.println("Shape::draw()");
    }
}
class Rect extends Shape{
    public void draw() {
        System.out.println("?");
    }
}
class Flower extends Shape{
    //alt + insert 快捷键输出重写的方法
    @Override
    public void draw() {
        System.out.println("?");
    }
}
class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("🔺");
    }
}
class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("●");
    }
}
public class Test {
    // 打印单个图形
    public static void drawShape(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
        Shape shape1 = new Flower();
        Shape shape2 = new Cycle();
        Shape shape3 = new Rect();
        drawMap(shape1);
        drawMap(shape2);
        drawMap(shape3);
    }
}

运行的结果如下:
在这里插入图片描述
这里类的代码是由类的实现者写的,main 函数是由类的调用者写的。当类的调用者在写 drawMap 方法的时候,就会通过父类去看一个调用那个子类的方法。

使用多态的好处

类调用者对类的使用成本进一步降低

  • 封装是为了让类的调用者不知道类的是实现细节。
  • 多态的话,会让类的调用者连类的类型都不知道,只需要知道有这个方法就行了。

所以,可以把多态看作是更进一步的封装。

能够降低代码量,避免使用大量的 if else

如果我们需要一次性打印很多形状,基于多态的话,就可以直接写成这样的代码:

public static void main(String[] args) {
    Rect rect = new Rect();
    Flower flower = new Flower();
    Triangle triangle = new Triangle();
    Shape[] shapes = {triangle,rect,triangle,rect,flower,new Cycle()};
    for (Shape s: shapes) {
        s.draw();
    }
}

在这里插入图片描述
运行结果就是这样,通过多态去调用,就不用使用 if else 了。

可扩展能力更强

要新增一种新的形状的话,直接在类里面重写父类的方法就可以了,就像这样:

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

向下转型

向下转型与向上转型恰好相反,是把父类对象转化为子类对象。相对来说不常见,但是也会用到。测试代码如下:

class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println("我是一只小动物");
        System.out.println(this.name + "正在吃" + food);
    }
}
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    public void eat(String food) {
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }

    public void fly() {
        System.out.println(this.name + "正在飞");
    }
}
public class test5 {
    public static void main(String[] args) {
        Animal animal = new Bird("圆圆");
        animal.eat("谷子");
    }
}

运行结果如下:
在这里插入图片描述
但是如果把行为改成 fly ,就会报错:
在这里插入图片描述
这里是因为 animal 的类型实际上是 Animal 类,编译器只知道有一个 eat 方法,没有 fly 方法,虽然 animal 实际引用的是一个 Bird 方法,但是编译器是以 Animal 的类型来寻找方法的。所以要使用 fly 方法的话,就要完成向下转型,这样写代码就可以了:

public static void main(String[] args) {
    Animal animal = new Bird("圆圆");
    Bird bird = (Bird)animal;
    bird.fly();
}

运行结果如下:
在这里插入图片描述
这样看似可以了,但是还会有问题,如果 animal 引用的对象是 cat 猫的话,就会报错,因为 Cat 是不能转化为 Bird 的:

public static void main(String[] args) {
    Animal animal = new Cat("小猫");
    Bird bird = (Bird)animal;
    bird.fly();
}

在这里插入图片描述
所以,为了向下转型更安全,我们可以先判断一下然后再转换,就像这样:

public static void main(String[] args) {
    Animal animal = new Cat("小猫");
    if (animal instanceof Bird) {
        Bird bird = (Bird)animal;
        bird.fly();
    }
}

这样就可以解决向下转型的问题了。

super 关键字

前面的代码调用的是子类的方法,如果要调用父类的方法就要用到 super 关键字。

super 表示获取到父类实例的引用

这里常用的有两种方法:

  1. 使用了 super 来调用父类的构造器:
public Bird(String name) {
    super(name);
}
  1. 使用 super 关键字来调用父类的普通方法:
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    @Override
    public void eat(String food) {
        //在这里让子类调用父类的接口
        super.eat(food);
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
}
public class test5 {
    public static void main(String[] args) {
            Bird bird = new Bird("xxn");
            bird.eat("ee");
    }
}

在这里插入图片描述
这里就完成了对父类方法的调用。

super 和 this 的区别

super 和 this 的区别如下图所示:
在这里插入图片描述

三级目录

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-14 01:50:01  更:2022-01-14 01:50:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:32:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码