继承是多态的前提条件。
多态
什么是多态:同类型对象表现出的不同形态,对象的多种形态。
多态的表现形式:<父类类型> <对象名称> = <子类对象>;
多态的前提:
- 与继承(面向对象)或者实现(接口)有关系;
- 存在父类引用指向子类对象;
- 有方法重写。
多态的举例
public class PolymorphismDemo {
public static void main(String[] args) {
Student s1 = new Student();
s1.setName("John");
s1.setAge(23);
Teacher t1 = new Teacher();
t1.setName("Jack");
t1.setAge(88);
Creation(s1);
Creation(t1);
}
public static void Creation(Person p) {
p.show();
}
}
class Person {
private String name;
private int age;
public Person() {
this("Default", 0);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show() {
System.out.println("Person Class" + this.name + this.age);
}
}
class Student extends Person {
@Override
public void show() {
System.out.println("Student Class" + super.getName() + super.getAge());
}
}
class Teacher extends Person {
@Override
public void show() {
System.out.println("Teacher Class" + super.getName() + super.getAge());
}
}

多态的好处
使用基类类型作为参数,可以接收所有派生类的对象,体现其扩展性和便利性。
多态调用成员的特点
多态调用成员变量
编译看左边,运行也看左边
public class PolymorphismDemo {
public static void main(String[] args) {
Animal a = new Cat();
System.out.println(a.animalType);
}
}
class Animal {
public String animalType = "Default Animal";
}
class Dog extends Animal {
public String animalType = "Dog";
}
class Cat extends Animal {
public String animalType = "Cat";
}

多态调用成员变量,编译看左边,运行也看左边。
编译:在System.out.println(a.animalType);这句代码中,使用javac编译代码的时候,会核查左边的父类有没有这个成员变量:如果有,则编译成功;反之,编译失败。
运行:java在运行代码的时候,实际获取的值是左边基类中成员变量的值。
作为举例,我们将上述代码中,基类中定义animalType变量的语句删除,如下图:


多态调用成员方法
编译看左边,运行看右边
public class PolymorphismDemo {
public static void main(String[] args) {
Animal a = new Cat();
a.showMyAnimal();
}
}
class Animal {
private String animalType = "Default Animal";
public void showMyAnimal() {
System.out.println("Animal Class -> Animal's type is " + this.animalType);
}
}
class Dog extends Animal {
private String animalType = "Dog";
@Override
public void showMyAnimal() {
System.out.println("Dog Class -> Animal's type is " + this.animalType);
}
}
class Cat extends Animal {
private String animalType = "Cat";
@Override
public void showMyAnimal() {
System.out.println("Cat Class -> Animal's type is " + this.animalType);
}
}

多态调用成员方法:编译看左边,运行看右边。
编译:在a.showMyAnimal();这句代码中,使用javac编译代码的时候,会核查左边的父类有没有这个成员方法:如果有,则编译成功;反之,编译失败。
运行:java在运行的时候,实际上运行的是派生类中重写的同名成员方法。
作为举例,我们将上述代码中,基类中定义showMyAnimal()变量的语句删除,如下图:


理解
无论是多态中调用成员变量或者成员方法,都是基类的对象作为调用的主体,所以无论是多态调用成员变量还是成员方法,都需要基类中存在对应的变量或者方法。
而多态调用成员变量的时候,派生类的构造时,会将基类的成员对象也继承下来(不管基类的对象是public、protected或是private,访问权限影响的是代码层面的直接访问,不影响内存分配),多态调用成员变量时,访问的一直是基类中定义的变量。
而多态调用成员方法的时候,如果派生类对方法进行重写,则在虚方法表中对应的方法引用会被替换为派生类的方法实现。所以多态在调用成员方法的时候,调用的是派生类中重写的方法,如果派生类未重写,则调用基类的方法(实际上是调用派生类虚方法表中记录的成员方法)。
成员变量没有多态性,而成员方法有多态性。
多态的优势和弊端
优势
结论
- 在多态的形式下,右边的对象可以实现解耦合,便于扩展和维护。
- 定义方法的时候,使用父类类型作为参数,则可以接收所有子类对象,体现多态的扩展性和便利性。
理解
我们可以这样理解:
public class PolymorphismDemo {
public static void main(String[] args) {
Animal a = new Cat();
a.play();
}
}
class Animal {
public void play() {
System.out.println("Animal Class -> Animal is playing.");
}
}
class Dog extends Animal {
@Override
public void play() {
System.out.println("Dog Class -> Dog is fetching a stick!");
}
}
class Cat extends Animal {
@Override
public void play() {
System.out.println("Cat Class -> Cat is chasing a ball of yarn!");
}
}
对于第一点,在多态的形式下,右边的对象可以实现解耦合,便于扩展和维护:
假设我们代码如上,在这里面,我们让Animal类的对象a在play(调用play()方法),而这个a实际是Cat类型,所以调用成员方法的时候,编译看左边,运行看右边,实际上运行的是Cat类中的成员方法play()。
如果我现在不希望猫在玩,而是狗在玩,只需要将Animal a = new Cat();修改为Animal a = new Dog();,即可完成修改。
如果不使用多态,则Cat c = new Cat();、Dog d = new Dog();,如果需要修改,则需要重写修改大量的具体实现代码,导致扩展性极低,系统耦合度极高,增加运维难度。
对于第二点,定义方法的时候,使用父类类型作为参数,则可以接收所有子类对象,体现多态的扩展性和便利性:
在示例代码块中Animal a = new Cat();,左边是抽象(Animal),右边是具体实现(Cat)。
如果我们需要新增更多的动物,在不使用多态的情况下,需要添加更多的类来接收动物,并且需要修改所有的抽象类中的逻辑以适应新增的动物;而使用父类作为参数类型,也就是使用多态的情况下,需要添加更多动物,不需要修改现有的方法定义,只需创建新的子类即可。
弊端
结论
- 在多态的形式下,无法调用子类独有方法。
- 定义方法的时候,使用父类类型作为参数,则可以接收所有子类对象,体现多态的扩展性和便利性。
理解
我们可以这样理解:
public class PolymorphismDemo {
public static void main(String[] args) {
Animal a = new Cat();
// a.catCatchMouse();
Cat c = (Cat) a;
c.catCatchMouse();
}
}
class Animal {
public void eat() {
System.out.println("Animal Class -> Animal is eating.");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog Class -> Dog is eating meat.");
}
public void dogDuardHouse() {
System.out.println("Dog Class -> Dog is guarding the house!");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat Class -> Cat is eating fish.");
}
public void catCatchMouse() {
System.out.println("Cat Class -> Cat caught a mouse!");
}
}
在多态的形式下,无法调用子类独有方法:
因为调用成员方法的时候,编译时会判断父类是否有同名方法。而子类独有方法是不存在于父类的,所以编译会出错。在如上代码块中a.catCatchMouse();语句被注释了,因为如果编译这一句语句,则会判断父类是否有catCatchMouse()方法,而这个方法是子类独有的,就此产生报错。
如果希望可以调用子类独有的方法,可以考虑将变量强制类型转换为子类,如上面代码块中的Cat c = (Cat) a; c.catCatchMouse();,这样c就是Cat的类的对象,则可以调用独有的方法。
但是在进行强制类型转换的时候需要注意,不可以随意转换对象的类型,比如不能通过Dog类型的Animal变量强制转换为Cat类型,这样在编译的时候会直接报错。


如何避免错误转换:可以使用instanceof来判断,其语法是:<对象> instanceof <类名>,返回为布尔值。
public class PolymorphismDemo {
public static void main(String[] args) {
Animal a = new Dog();
if (a instanceof Dog) {
Dog d = (Dog) a;
} else if (a instanceof Cat) {
Cat c = (Cat) a;
} else {
Animal newAnimal = a;
}
}
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

JDK14+新特性
语法:
<对象> instanceof <类名> <新变量名>。public class PolymorphismDemo { public static void main(String[] args) { Animal a = new Dog(); if (a instanceof Dog d) {} } } class Animal {} class Dog extends Animal {} class Cat extends Animal {}逻辑如下:先判断传入值
a是否是Dog类型,如果是,则直接创建Dog类的对象d并直接接收来自a强制类型转换后的结果。如果否,则直接返回false。