多态在 Java 中指同一个行为具有不同表现形式或形态的能力。程序中定义的引用变量所指向的具体类型(一个父类引用可以指向多种不同的子类对象)和通过该引用发出的调用所指向的具体方法属于哪个类在编译时不能确定下来,只有在具体运行时才能真正确定。
多态存在的必要条件和体现
- 继承/实现关系(类继承/接口实现);
- 方法重写;
- 父类引用指向子类对象(上转型);
多态的注意事项
当以父类引用指向子类对象来使用多态时:
SuperClass s = new SubClass();
利用 s.变量名 或 s.方法名() 来访问类中的成员或方法时需要注意,若父类不存在该变量或方法,则编译错误;对于成员方法来说,若父类存在该方法,且子类重写了该方法,则调用子类重写的方法,若子类没有重写该方法,则调用父类该方法,也就是说成员方法编译看左边,执行看右边。而对于成员变量来说,由于成员变量不存在重写(子类重新声明该变量实际上效果是父类该字段在子类中被隐藏,多态访问的仍是父类本身所具有的变量),s.变量名 始终调用父类成员变量,也就是说成员变量编译看左边,执行也看左边。
多态的案例:
定义一个父类 Animal:
public class Animal {
public int age;
public void eat() {
System.out.println("Animal is eating");
}
public void sleep() {
System.out.println("Animal is sleeping");
}
}
定义子类 Cat:
public class Cat extends Animal{
public int age; // 隐藏字段
public double weight;
public Cat(int age, double weight) {
super(age);
this.weight = weight;
}
@Override
public void eat() {
System.out.println("Cat is eating");
}
}
测试类:
public class Demo {
public static void main(String[] args) {
Animal cat = new Cat(5, 4.5);
System.out.println("Age: " + cat.age); // 输出父类的 age
// System.out.println("Weight: " + cat.weight); 父类没有 weight 变量,编译错误
cat.eat(); // 调用子类重写的 eat()
cat.sleep(); // 调用父类 sleep()
}
}
输出:
0
Cat is eating
Animal is sleeping
多态的好处和弊端
仍旧考虑以上父类 Animal ,重新定义子类 Cat 以及子类 Dog:
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("Cat is eating");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
利用方法实现调用 Cat 以及 Dog 的成员方法,如下:
public class Demo {
public static void main(String[] args) {
animalEat(new Cat());
animalEat(new Dog());
}
public static void animalEat(Cat c) {
c.eat();
}
public static void animalEat(Dog d) {
d.eat();
}
}
输出:
Cat is eating
Dog is eating
上述代码中定义了两个非常相似的 animalEat() 方法,造成了代码的冗余,利用多态就可以消除这种冗余:
public class Demo {
public static void main(String[] args) {
animalEat(new Cat());
animalEat(new Dog());
}
public static void animalEat(Animal a) {
a.eat();
}
}
输出与之前一样:
Cat is eating
Dog is eating
可以想象,当存在大量子类时,多态可以减少大量的重复工作;此外,利用父类作为参数,使用具体子类参与操作,子类可以后期逐渐添加而不受数量限制,大大提升了程序的可扩展性。这种方法的弊端在于无法使用子类独有的功能。
向下转型
向上转型得到父类引用指向子类对象存在不能调用子类独有功能的问题。如果必须要使用该独有功能,但又不想再新建一个子类对象怎么办呢,毕竟上转型时实际上已经建立了一个子类实例,如果可以利用这个实例调用独有功能便可实现内存的节约。这是便需要向下转型,向下转型实际上就是将上转型父类引用强制转换为子类引用,这样以来便可以使用子类独有功能。
SuperClass s = new SubClass();
// 下转型
SubClass sub = (SubClass)s;
重载是否体现多态?-- No
方法重载是静态绑定的,而多态一定是动态绑定的,所以重载并不是多态的一种体现