前言
多态是同一个行为具有多个不同的表现形态或形式的能力
比如:
小阿giao,他是一名主播,同样也是一个人;
小阿giao是一个对象;
这个对象既有主播形态,也有人类形态;
即:一个对象,拥有多种形态,这就是对象的多态性
多态在代码中的体现
如何用代码来表现多态性?
其实就是一句话:父类引用指向子类对象
父类名称 对象名 = new 子类名称();
不一定非得是父类引用,还可以这样
接口名称 对象名 = new 实现类名称();
创建一个父类:
public class A {
public void method() {
System.out.println("父类方法");
}
}
创建一个子类继承父类并覆盖重写父类的method方法:
class B extends A {
@Override
public void method() {
System.out.println("子类方法");
}
}
使用多态写法:
class Main {
public static void main(String[] args) {
A a = new B();
a.method();
}
}
运行结果:
输出的是子类的方法,如果父类有的方法子类没有覆盖重写,那么就会向上查找,即子类没有,就找父类。
再新建一个子类(这里,我们不去覆盖重写):
class C extends A {
}
调用一下这个方法:
class CMain {
public static void main(String[] args) {
A a = new C();
a.method();
}
}
运行结果:
小结
直接通过对象名称访问成员变量,等号左边你new了谁,优先用谁的方法,如果没有,则向上找
多态中成员方法的使用特点
有一句口诀:编译看左边,运行看右边
代码:
新建一个父类,写两个方法eat和摸鱼,注意,摸鱼方法是父类特有的,子类不覆盖重写:
public class Animal {
public void eat() {
System.out.println("动物吃东西");
}
public void 摸鱼() {
System.out.println("动物在摸鱼");
}
}
新建一个子类继承父类并覆盖重写父类的eat方法,然后写一个特定的方法run:
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
public void run() {
System.out.println("猫在跑");
}
}
调用一下各个方法:
class AnimalMain {
public static void main(String[] args) {
Animal animal = new Cat();
animal.eat();
animal.摸鱼();
// animal.run(); // 编译错误
}
}
一行一行来解释:
- 第三行:多态写法,父类引用指向子类对象;
- 第四行:调用eat方法,因为eat方法父子类都有,所以优先使用子类的,输出:猫在吃鱼;
- 第五行:调用摸鱼方法,这是父类特有的方法,子类没有,所以向上找,输出:动物在摸鱼;
- 第六行:调用run()方法,这是子类的特有方法,口诀;编译看左边。左边是父类,父类中没有run()方法,所以编译报错(具体原因是:对象一旦向上转型为父类,那么就无法调用子类原本特有的内容,比如run方法)
成员变量与成员方法的对比
- 成员变量:编译看左边,运行看右边
- 成员方法:编译看左边,运行看右边
多态的好处
假设我们有三个类,都有work方法:
- 员工类(父类)
work(); // 抽象的
- 讲师类(子类)
work(); // 讲课
- 助教类(子类)
work(); // 辅导
如果不用多态写法,只用子类:
Teacher t = new Teacher();
t.work(); // 老师讲课
Assistant a = new Assistant();
a.work(); // 助教辅导
我们唯一要做的事情就是调用work方法,不关心你是讲课还是辅导
如果使用多态写法:对比一下上面的代码
Employee t = new Teacher();
t.work();
Employee a = new Assistant();
a.work();
好处就一目了然了:无论右边new的时候换成哪个子类对象,等号左边调用的方法都不会改变。
对象的向上转型
对象的向上转型其实上面已经写过了,其实就是多态写法。
格式:
父类名称 对象名 = new 子类名称();
Animal animal = new Cat();
含义:
右侧创建一个子类对象,把它当作父类来看;
创建了一只猫,当作动物来看待
注意:向上转型一定是安全的,但是也有个弊端,一旦对象向上转型为父类,那么就无法调用子类原本特有的内容(解决方案:向下转型)
对象的向下转型
对象的向下转型,其实就是一个还原的动作
格式:
子类名称 对象名 = (子类名称) 父类对象;
含义:
将父类对象还原为本来的对象
简单来说,本来它是猫,经过向上转型为动物,再向下转型还原成猫。
Animal animal = new Cat(); // 本来是猫,向上转型为动物
Cat cat = (Cat) animal; // 本来是猫,已经被当作是动物了,还原为猫
注意事项:
- 必须保证对象本来创建的时候就是猫,才能向下转型为猫;
- 如果对象创建的时候不是猫,现在非要向下转型为猫,你说是不是沙雕?爆粗:
ClassCastException
使用instanceof关键字进行类型判断
有一个问题:如何知道一个父类引用的对象,本来是什么子类?
简单来说,就是你怎么知道父类的引用指向的本来就是猫还是狗呢?
答案是:使用关键字 instanceof
格式:
对象名称 instanceof 子类名称
返回的是一个boolean类型:
class InstanceMain {
public static void main(String[] args) {
Animal animal = new Cat(); // 本来是猫
animal.eat(); // 猫吃鱼
// 如果希望使用子类特有的方法,需要向下转型
if (animal instanceof Cat) { // true
Cat cat = (Cat) animal;
cat.run(); // 猫在跑
}
}
}
运行结果:
注意:使用向下转型,一定要使用instanceof关键字进行判断,避免ClassCastException异常。