多态
要实现多态,必须满足三个前提条件:
- 继承:子类继承父类
- 重写:子类重写父类的方法
- 父类引用指向子类对象
示例
class Animal {
public void makeSound() {
System.out.println("动物发出叫声");
}
}
// 继承并重写方法
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("wangwang");
}
}
// 继承并重写方法
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("miaomiao");
}
}下面来看怎么用
class Animal {
public void makeSound() {
System.out.println("动物发出叫声");
}
}
// 继承并重写方法
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("wangwang");
}
public void say() {
}
}
// 继承并重写方法
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("miaomiao");
}
}
public class Main {
public static void main(String[] args) {
// 父类引用 a1 指向子类 Dog 的实例
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.makeSound(); // wangwang
a2.makeSound(); // miaomiao
// a1.say() // error
}
}编译时,编译器只知道a1是一个Animal,所以只能调用Animal里的方法
运行时,JVM会去探测a1实际指向的内存地址,并指向Dog里重写的方法
如果没有多态的写法
每增加一种动物,都要专门写一个方法,代码会变得很臃肿
class Feeder {
// 喂狗
public void feed(Dog dog) {
System.out.print("准备食物 -> ");
dog.makeSound();
}
// 喂猫
public void feed(Cat cat) {
System.out.print("准备食物 -> ");
cat.makeSound();
}
// 以后每多一个动物(比如猪、鸭、羊),你都得在这里加一个 feed() 方法...
}有多态的写法
class Feeder {
// 只需要写一个方法,接收通用的 Animal 即可!
public void feed(Animal animal) {
System.out.print("准备食物 -> ");
animal.makeSound(); // 多态在这里生效!
}
}public class Test {
public static void main(String[] args) {
Feeder feeder = new Feeder();
Animal myDog = new Dog();
Animal myCat = new Cat();
feeder.feed(myDog); // 输出:准备食物 -> 汪汪汪!
feeder.feed(myCat); // 输出:准备食物 -> 喵喵喵~
}
}之后再想加一个类,只要继承Animal类,重写makeSound方法,就可以用Feeder类来喂了
注意
- 成员变量没有多态
如果父类和子类有同名的成员变量,通过父类引用去访问时,访问的依然是父类的变量
- 多态下不能直接调用子类特有的方法
如果 Dog 类里面写了一个特有的方法 public void fetchBall()(捡球),那么 Animal dog = new Dog(); 是无法直接调用 dog.fetchBall() 的。因为在编译期,编译器只看左边的 Animal,而 Animal 里没有 fetchBall。
解决方法:使用向下转型(强制类型转换)之前先用instanceof判断
class Animal {
public void makeSound() {
System.out.println("动物发出叫声");
}
}
// 继承并重写方法
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("wangwang");
}
public void say() {
System.out.println("real dog");
}
}
// 继承并重写方法
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("miaomiao");
}
}
public class Main {
public static void main(String[] args) {
// 父类引用 a1 指向子类 Dog 的实例
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.makeSound(); // wangwang
a2.makeSound(); // miaomiao
if (a1 instanceof Dog) {
Dog realDog = (Dog) a1;
realDog.say(); // 成功调用
}
}
}重载
在同一个类,方便用户使用,提供了同名但参数不同的多个方法(比如多种加法器)
方法名必须相同,参数列表(个数、类型、顺序至少一个不同)必须不同
示例:
public class Calculator {
// 两个整数相加
public int add(int a, int b) {
return a + b;
}
// 三个整数相加(参数个数不同)
public int add(int a, int b, int c) {
return a + b + c;
}
// 两个双精度浮点数相加(参数类型不同)
public double add(double a, double b) {
return a + b;
}
}重载(Overload)=编译时多态(静态绑定)
重载在代码编译阶段就已经决定好了
当你写下 calculator.add(10, 20) 时,编译器在编译成字节码(.class 文件)的那一刻,就已经通过参数类型和个数,锁定了该去调用 add(int, int) 这个具体的方法。
- 特点:不需要运行程序,看一眼参数就能确定
重写(Override)=运行时多态(动态绑定)
当你写下Printer p = new ColorPrinter(); p.print(); 时,编译器在编译阶段只知道 p 是 Printer 类型,它不知道(也无法确定)你在运行期间会塞给它一个什么子类对象。 只有当程序真正跑起来,JVM 执行到这一行时,它才会去内存里探测:“哦,原来 p 指向的真实对象是ColorPrinter 呀!” 进而调用子类重写后的方法。
- 特点:编译器无法决定,必须在运行期动态寻找