第15篇:Java 多态的详细介绍(参考官方教程)

简介: 初学 Java 的时候,博主觉得多态非常伤脑筋。今天这篇文章将通过官方教程、韩顺平老师、李明杰老师的讲解来学习多态。博主通过他们的视频来学习,但也有自己的想法,并不是照抄 PPT 哦!OK!Let's study!😊Java 有三大特性:封装、继承和多态(Polymorphism)

前言

初学 Java 的时候,博主觉得 多态非常伤脑筋。今天这篇文章将通过 官方教程韩顺平老师李明杰老师的讲解来学习多态。博主通过他们的视频来学习,但也有自己的想法,并不是照抄 PPT 哦! OK!Let's study!😊

Java 有三大特性:封装、继承和多态(Polymorphism


一、多态官方教程

✏️ The dictionary 📙 definition of polymorphism refers to a principle in biology(生物学) in which an organism(有机体) or species(物种) can have many different forms or stages.
在这里插入图片描述

📜 多态的字典定义是:在生物学上,一个有机体或物种可以有许多不同的 形式或阶段(形式或状态)。

✏️ This principle can also be applied to object-oriented programming(面向对象编程) and languages like the Java language.

📜 这个 原则(生物学的多态性)也可以应用于 面向对象编程和编程语言(比如 Java)

✏️ Subclasses of a class can define their own unique behaviors and yet share(共享) some of the same functionality(功能) of the parent class.

📜 一个类的子类可以定义它们自己独特的行为,并且共享一些与父类相同的功能

多态代码案例(不是官方案例,代码按照官方案例编写的)

public class Person {
    public String name;
    public int age;
    public String gender;

    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public void printDescription() {
        System.out.println("\n Person: " + name + "_" + age + "_" + gender);
    }
}

/**
 * Student 类继承 Person 类
 */
class Student extends Person {
    private double score;

    public Student(String name, int age, String gender, double score) {
        super(name, age, gender);
        this.score = score;
    }

    public void printDescription() {
        System.out.println("\n Student: " + name + "_" + age + "_" + gender + "_" + score);
    }
}

/**
 * Employee 类继承 Person 类
 */
class Employee extends Person {
    private int salary;

    public Employee(String name, int age, String gender, int salary) {
        super(name, age, gender);
        this.salary = salary;
    }

    public void printDescription() {
        System.out.println("\n Employee: " + name + "_" + age + "_" + gender + "_" + salary);
    }
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        Person person, student, employee;
        person = new Person("张浩男", 20, "男");
        student = new Student("庆医", 12, "男", 99.5);
        employee = new Employee("莫松", 21, "男", 12356);

        System.out.println("\nperson 的真正形态(类型):" + person.getClass());
        System.out.println("student 的真正形态(类型):" + student.getClass());
        System.out.println("employee 的真正形态(类型):" + employee.getClass());

        // Person: 张浩男_20_男
        person.printDescription();
        // Student: 庆医_12_男_99.5
        student.printDescription();
        // Employee: 莫松_21_男_12356
        employee.printDescription();
    }
}
📝 上面的代码中共有四个类:① Person;② Student(继承 Person 类);③ Employee(继承 Person 类);④ TestDemo 类(测试类)
📝 Student 类继承 Person 类,并添加了它独特的属性 score;printDescription 方法被重写,额外增加了打印的内容 score
📝 Employee 类继承 Person 类,并添加了它独特的属性 salary;printDescription 方法被重写,额外增加了打印的内容 salary
📝 TestDemo 类中定义了三个引用变量:person、student 和 employee( 注意:这三个引用变量都是 Person 类型的
📝 三个引用变量分别指向不同的对象:person 指向 Person 对象;student 指向 Student 对象;employee 指向 Employee 对象
📝 但最终打印的结果是:引用变量指向的对象所属于的类里面的 printDescription 的内容,而不是引用变量的类型 Person 类的 printDescription 的内容(这就是多态性:都是 Person 类型的引用,当引用不同的对象的时候所展示的形态是各不相同的)
1   9

✏️ The Java virtual machine (JVM) calls the appropriate method for the object that is referred to in each variable. It does not call the method that is defined by the variable's type.

📄 Java 虚拟机会调用被变量引用的 对象的方法,而不是调用变量的类型的方法。
📄 Java 虚拟机 (JVM) 为每个变量中引用的对象调用适当的方法。它不调用由变量类型定义的方法。

✏️ This behavior is referred to as virtual method invocation(虚方法调用) and demonstrates an aspect of the important polymorphism features in the Java language.

📄 这种行为被称为 虚拟方法调用,并展示了 Java 语言中多态特性的一个方面。

在这里插入图片描述
在这里插入图片描述


二、关于多态

✒️ 多态:Polymorphism(具有多种形态)
✒️ 多态:同一类型的引用变量(如 Person),当变量指向(引用)不同的对象后,变量的类型就转变为了指向的对象的类型

✒️ 多态的体现:父类(接口)类型的引用
指向子类对象
;② 调用子类重写的方法

多态案例:

public class Animal {
    public void speak() {
        System.out.println("Animal - speak");
    }
}

/**
 * Dog 类继承 Animal 类
 */
class Dog extends Animal {
    public void speak() {
        System.out.println("Dog - speak");
    }
}

/**
 * Cat 类继承 Animal 类
 */
class Cat extends Animal {
    public void speak() {
        System.out.println("Cat - speak");
    }
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        // Animal - speak
        animalSpeak(new Animal());
        // Dog - speak
        animalSpeak(new Dog());
        // Cat - speak
        animalSpeak(new Cat());
    }

    private static void animalSpeak(Animal animal) {
        animal.speak();
    }
}

📝 JVM 会根据引用变量指向的具体对象调用对应的方法(调用对象中的方法,而不是引用变量类型的方法)
📝 这个行为叫做虚方法调用(virtual method invocation)
📝 类似 C++ 的虚函数调用

三、多态的体现方式

🔑 多态:方法或对象具有多种状态

(1) 方法的多态

方法的【重写】体现出方法的多态:

public class Father {
    public void makeMoney() {
        System.out.println("Father - makeMoney()");
    }
}

/**
 * Son 类继承 Father 类
 */
class Son extends Father {
    @Override
    public void makeMoney() {
        System.out.println("Son - makeMoney()");
    }
}


/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        Father father = new Father();
        Son son = new Son();
        // Father - makeMoney()
        father.makeMoney();
        // Son - makeMoney()
        son.makeMoney();
    }
}
📔 上面的代码:Son 类继承 Father 类,并重写了 Father 类中的 makeMoney 方法
📔 用 Father 对象和 Son 对象调用 makeMoney 方法
📔 虽然调用的都是 makeMoney 方法,但实际上调用的不是同一个方法
📔 这里就体现了方法的多态(多种形态)

方法的【重载】体现出方法的多态:

public class Calculator {
    public int sum(int n1, int n2) {
        return n1 + n2;
    }

    public int sum(int n1, int n2, int n3) {
        return n1 + n2 + n3;
    }
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        // 9
        System.out.println(calculator.sum(1, 8));
        // 18
        System.out.println(calculator.sum(1, 8, 9));
    }
}
📔 Calculator 类中的两个 sum 方法构成重载(因为它们的参数个数不同)
📔 同一个 Calculator 对象调用 sum 方法,当传入不同个数的参数(传两个或三个参数)的时候,返回的结果不一样
📔 调用的都是 Calculator 方法,但因为传入的参个数不同返回类型也不同,这里就体现了方法的多态

(2) 对象的多态

🌼 ① 一个对象的引用的编译类型和运行类型是可以不一致的

🌻 编译类型:使用 javac 命令把 java 文件编译为 class 文件的时候的类型
🌻运行类型:使用 java 命令运行 class 文件的时候

🌼 ② 编译类型在定义引用变量的时候就确定了,不会变化
🌼 ③ 运行类型是可以变化的
🌼 ④ 编译类型看赋值符号的左边;运行类型看赋值符号的右边

🌻 编译类型看赋值符号的左边;运行类型看赋值符号的右边只是一种记忆的方式,大部分时候可以按照这个规律判断。
🌻 实际上, 编译类型是引用变量【被定义的类型】; 运行类型是引用变量【实际指向的对象的类型】

对象多态案例:

public class Animal {
    public void speak() {
        System.out.println("Animal_speak()");
    }
}

class Fish extends Animal {
    @Override
    public void speak() {
        System.out.println("Fish_speak()");
    }
}

class Dragon extends Animal {
    @Override
    public void speak() {
        System.out.println("Dragon_speak()");
    }
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        // animal 的编译类型是【Animal】
        Animal animal;

        // animal 的运行类型是【Animal】
        animal = new Animal();
        // Animal_speak()
        animal.speak();

        // animal 的运行类型变成了【Fish】
        animal = new Fish();
        // Fish_speak()
        animal.speak();

        // animal 的运行类型变成了【Dragon】
        animal = new Dragon();
        // Dragon_speak()
        animal.speak();
    }
}

四、向上转型

🌼 多态的前提是:① 类之间存在继承关系;② 类与接口之间存在实现关系

🌼 向上转型:父类类型的h引用指向子类对象

🌻 引用可以调用 编译类型中所有的非 private 成员(引用可以调用那些方法看的是编译类型:赋值符号左边的类型)
🌻 引用无法调用 运行类型中特有的成员
🌻 最终运行效果看 运行类型的具体实现(运行类型有,用运行类型的;否则用编译类型的)

向上转型案例:

public class Animal {
    public void eat() {
        System.out.println("Animal_eat()");
    }

    public void drink() {
        System.out.println("Animal_drink()");
    }

    public void play() {
        System.out.println("Animal_play()");
    }

    public void happy() {
        System.out.println("Animal_happy()");
    }

    private void bitePeople() {
        System.out.println("Animal_bitePeople()");
    }
}

class Cat extends Animal {
    @Override
    public void play() {
        System.out.println("Cat_play()");
    }

    public void catchMouse() {
        System.out.println("Cat_catchMouse()");
    }
}


/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        // animal 是父类类型(Animal)的引用, 它指向了子类(Cat)对象
        // animal 的编译类型是 Animal; 运行类型是 Cat
        Animal animal = new Cat();
        /*
            ① 只能调用 Animal(编译类型)中有的 4 个方法, 其中 bitePeople 方
            法是私有的, 不能调用
            ② 运行类型 Cat 中的 catchMouse 方法无法调用
            ③ 若想调用 catchMouse 方法, 只能用 Cat 类型的引用指向 Cat 对象
              或向下转型
         */
        // Animal_eat()
        animal.eat();
        // Animal_drink()
        animal.drink();
        // Cat_play()【运行类型(Cat)中重写了 play 方法,就调用运行类型中的】
        animal.play();
        // Animal_happy()
        animal.happy();

        // Cat 类型的引用指向 Cat 对象
        Cat cat = new Cat();
        // Cat_catchMouse()
        cat.catchMouse();
    }
}

五、向下转型

通过下面的代码引出向下转型:

public class Animal {
    public void play() {
        System.out.println("Animal_play()");
    }

    public void happy() {
        System.out.println("Animal_happy()");
    }
}

class Cat extends Animal {
    @Override
    public void play() {
        System.out.println("Cat_play()");
    }

    public void catchMouse() {
        System.out.println("Cat_catchMouse()");
    }
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        // animal 的编译类型是 Animal; 运行类型是 Cat
        Animal animal = new Cat();
        // Cat_play()
        animal.play();
        // Animal_happy()
        animal.happy();
    }
}
✏️ 上面代码中:animal 的编译类型是 Animal,运行类型是 Cat
✏️ 只能调用 Animal 中有的方法(play 和 happy)
✏️ 无法调用 Cat 中独有的方法(catchMouse)
✏️ cat 引用指向的是 Cat 类型的对象,凭什么不能调用 catchMouse 方法呢?若想调用的话,咋个办呢?

📜 虽然 cat 引用指向的是 Cat 类型的对象,但它的编译类型是 Animal 类型。编译器只能识别编译类型中拥有的方法。
📜 若想通过 cat 引用调用 Cat 对象的方法(catchMouse ),可把 cat 引用向下转型为 Cat 类型

📰 cat 引用指向的对象是 Cat 类型,可以被 向下转换(强制类型转换)为 Cat 类型
📰 向下转换为 Cat 类型后,运行类型就是 Cat 类型了,就可以调用 Cat 中的 catchMouse 方法了
/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        // animal 的编译类型是 Animal; 运行类型是 Cat
        Animal animal = new Cat();
        // Cat_play()
        animal.play();

        Cat cat = (Cat) animal;
        // Cat_catchMouse()
        cat.catchMouse();

        // Cat_catchMouse()
        ((Cat) animal).catchMouse();
    }
}

☘️ 这个向下转换(强制类型转换)只是改变了引用的指向

---

六、多态中的属性问题

看下面的代码,思考打印结果:

public class Father {
    public int num = 1;
}

class Son extends Father {
    public int num = 2;
}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        Father father = new Son();
        System.out.println(father.num);

        Son son = new Son();
        System.out.println(son.num);
    }
}
☘️ 属性没有重写之说! 属性的值直接看编译类型
/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        /*
            father 的编译类型是 Father
            所以, father.num 访问得是 Father 类中的 num
         */
        Father father = new Son();
        // 1
        System.out.println(father.num);

        /*
            son 的编译类型是 Son
            所以, son.num 访问得是 Son 类中的 num
         */
        Son son = new Son();
        // 2
        System.out.println(son.num);
    }
}

七、instanceof 关键字

☘️ instanceof 关键字:判断引用运行类型是否是某类型或某类型的子类型

public class Father {

}

class Son extends Father {

}

class Flower {

}

/**
 * 测试类
 */
class TestDemo {
    public static void main(String[] args) {
        Father father;
        father = new Father();
        Son son = new Son();

        Flower flower = new Flower();

        System.out.println(father instanceof Father); // true
        System.out.println(son instanceof Father); // true
        System.out.println(flower instanceof Object); // true

        // 下面代码直接报错
        // System.out.println(flower instanceof Son);
    }
}

八、Exercise

找出下面代码的错误:

public class TestDemo {
    public static void main(String[] args) {
        double d = 13.4;
        long l = (long) d;
        System.out.println(l); // 13

        int i = 5;
        // ERROR
        boolean b = (boolean) i;

        Object obj = "hello"; // 向上转型
        String objStr = (String) obj; // 向下转型
        System.out.println(objStr); // hello

        Object objPri = new Integer(5); // OK (向上转型) 
        String str = (String) objPri; // ERROR (指向 Integer 的指针不能指向 String)

        // OK (向下转型)
        Integer str1 = (Integer) objPri;
    }
}

输出下面代码的输出结果:

public class Father {
    int count = 10;

    public void display() {
        System.out.println(this.count);
    }
}

/**
 * Son 类继承 Father 类
 */
class Son extends Father {
    int count = 20;

    @Override
    public void display() {
        System.out.println(this.count);
    }
}

class Main {
    public static void main(String[] args) {
        // s 的编译类型是 Son
        Son s = new Son();
        // s.count 访问的是 s 的编译类型里面的 count
        System.out.println(s.count); // 20
        s.display(); // 20

        // 父类类型的引用指向子类对象
        Father f = s;
        // f 和 s 指向的是同一个对象
        System.out.println(f == s); // true

        // f 的编译类型是 Father
        // f.count 访问得是 f 的编译类型里面的 count
        System.out.println(f.count); // 10
        f.display(); // 10
    }
}

这里还涉及到一个特别有意思的重点【动态绑定机制】,下篇文章看


今天是
今天即是中秋节,也是教师节!祝各位一帆风顺、双喜临门、万事顺心!

🎁 如文章有错误,请不吝赐教!
相关文章
|
9月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
1317 4
|
8月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
458 10
|
7月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
554 0
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
840 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
11月前
|
Java
银行转账p图软件,对公转账截图生成器,java版开发银行模拟器【仅供学习参考】
这是一套简单的银行账户管理系统代码,包含`BankAccount`和`BankSystem`两个核心类。`BankAccount`负责单个账户的管理
|
10月前
|
缓存 安全 Java
Java 并发新特性实战教程之核心特性详解与项目实战
本教程深入解析Java 8至Java 19并发编程新特性,涵盖CompletableFuture异步编程、StampedLock读写锁、Flow API响应式流、VarHandle内存访问及结构化并发等核心技术。结合电商订单处理、缓存系统、实时数据流、高性能计数器与用户资料聚合等实战案例,帮助开发者高效构建高并发、低延迟、易维护的Java应用。适合中高级Java开发者提升并发编程能力。
486 0
|
11月前
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
1203 2
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
14918 5
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
Java 编译器 程序员
java中重载和多态的区别
本文详细解析了面向对象编程中多态与重载的概念及其关系。多态是OOP的核心,分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要通过方法重载和运算符重载实现,如Java中的同名方法因参数不同而区分;运行时多态则依赖继承和方法重写,通过父类引用调用子类方法实现。重载是多态的一种形式,专注于方法签名的多样性,提升代码可读性。两者结合增强了程序灵活性与扩展性,帮助开发者更好地实现代码复用。
753 0