前言
初学 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 类型的引用,当引用不同的对象的时候所展示的形态是各不相同的)
✏️ 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
}
}
这里还涉及到一个特别有意思的重点【动态绑定机制】
,下篇文章看
今天即是中秋节,也是教师节!祝各位一帆风顺、双喜临门、万事顺心!
🎁 如文章有错误,请不吝赐教!