第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
    }
}

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


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

🎁 如文章有错误,请不吝赐教!
相关文章
|
28天前
|
算法 Java 程序员
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
53 9
|
22天前
|
Java 开发者 UED
【实战宝典】Java异常处理大师级教程:throws关键字,让异常声明成为你的专属标签!
【实战宝典】Java异常处理大师级教程:throws关键字,让异常声明成为你的专属标签!
36 3
|
28天前
|
Java 开发者
在Java面向对象编程的广阔海洋中,多态犹如一股深邃的潜流,它推动着代码从单一走向多元,从僵化迈向灵活。
在Java面向对象编程的广阔海洋中,多态犹如一股深邃的潜流,它推动着代码从单一走向多元,从僵化迈向灵活。
33 7
|
28天前
|
Java 开发者
那些年,我们一同踏入Java编程的大门,多态,这个充满魔法的名字,曾无数次点亮我们探索面向对象编程的热情。
那些年,我们一同踏入Java编程的大门,多态,这个充满魔法的名字,曾无数次点亮我们探索面向对象编程的热情。
40 5
|
28天前
|
Java 程序员
让我们一起探讨Java多态的奥秘,看看它是如何打破“一刀切”的局限,让我们的代码更加生动多彩
让我们一起探讨Java多态的奥秘,看看它是如何打破“一刀切”的局限,让我们的代码更加生动多彩
33 5
|
29天前
|
Java 程序员
Java中的继承和多态:理解面向对象编程的核心概念
【8月更文挑战第22天】在Java的世界中,继承和多态不仅仅是编程技巧,它们是构建可维护、可扩展软件架构的基石。通过本文,我们将深入探讨这两个概念,并揭示它们如何共同作用于面向对象编程(OOP)的实践之中。你将了解继承如何简化代码重用,以及多态如何为程序提供灵活性和扩展性。让我们启程,探索Java语言中这些强大特性的秘密。
|
6天前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
20 9
Java——类与对象(继承和多态)
|
30天前
|
Java 开发者
【Java基础面试十五】、 说一说你对多态的理解
这篇文章解释了多态性的概念:在Java中,子类对象可以赋给父类引用变量,运行时表现出子类的行为特征,从而允许同一个类型的变量在调用同一方法时展现出不同的行为,增强了程序的可扩展性和代码的简洁性。
【Java基础面试十五】、 说一说你对多态的理解
|
1月前
|
前端开发 Java Maven
【前端学java】全网最详细的maven安装与IDEA集成教程!
【8月更文挑战第12天】全网最详细的maven安装与IDEA集成教程!
66 2
【前端学java】全网最详细的maven安装与IDEA集成教程!
|
22天前
|
安全 Java 编译器
Java多态
Java多态
13 2