共计 2777 个字符,预计需要花费 7 分钟才能阅读完成。
面向对象编程的语言里,多态是一个非常重要的特性,优点很多,类型解耦,灵活性,可替代性,接口性等。面向对象编程的三大特性:封装、继承,多态。封装和继承都很好理解,多态可能你每天都在用,但却不知道怎么解释它,当然,这也是新手从入门到放弃一个必经之路☺
一、多态概念
多态?顾名思义,就是一种行为具有多种形态。再具体一点,就是同一个接口,有多个不同的实现。
要完全理解Java里的多态,还需要知道两个概念:向上转型,向下转型。
二、向上转型
向上转型:子类型转化为父类型,自动类型转化。
定义父类
class Fu {
void SayHello() {
System.out.println("Fu Say Hello");
}
}
定义子类,重写父类方法
class Zi extends Fu {
@Override
void SayHello() {
System.out.println("Zi Say Hello");
}
}
测试类
public class Main {
public static void main(String[] args) {
Fu f = new Zi();
f.SayHello(); // 输出“Zi Say Hello”
}
}
Fu f = new Zi(); 父类型的子类实例,父类型引用指向子类型对象
f.SayHello(); 调用父类子类共有的方法
代码很简单,不过我们换个角度来分析它。
- 编译期:这时因为还没 new 出一个 Zi 类实例,所以编译器认为这是父类,去 Fu.class 中 SayHello 方法,发现有这个方法,所以编译通过,这叫 静态绑定
- 运行期:运行时,这个时候已经 new 出一个 Zi 类实例,所以 f.SayHello() 调用的是子类的 SayHello 方法,这叫 动态绑定
三、向下转型
向下转型:父类型转化为子类型,强制类型转化
给Zi类再增加一个特有的方法 Cry()
class Zi extends Fu {
void Cry() {
System.out.println("Zi crying ......");
}
}
测试代码
public class Main {
public static void main(String[] args) {
Fu f = new Zi();
f.Cry(); // ClassCastException
}
}
上面这个测试代码执行会报错,因为调用了子类特有的方法
- 编译期:还没 new 出 Zi 类实例,执行 f.Cry() 就会去 Fu.class 里查找 Cry 方法,没找到,编译器报 ClassCastException。
所以改一下
public class Main {
public static void main(String[] args) {
Fu f = new Zi();
((Zi) f).Cry();
}
}
((Zi) f).Cry(); 先做强制类型转化,再调用子类特有的方法
- 编译期:因为做了强制类型转化,所以编译器就会去 Zi.class 里找到 Cry 方法,这样就通过了编译
- 运行期:运行时,new 出了 Zi 类实例,调用 Zi 类 Cry 方法
更严谨一点的写法,在做强制类型转化前,都用instance判断一下是否存在继承关系
public class Main {
public static void main(String[] args) {
Fu f = new Zi();
if (f instanceof Zi) {
Zi z = (Zi) f;
z.Cry();
}
}
}
四、多态
多态产生的条件
- 继承
- 方法覆盖
- 向上转型
继承和方法覆盖这个不用说,都能记得住。重点来了,第三点,记住这句话 :【向上转型:父类型引用指向子类型对象】,这样使用父类型引用就能调用到子类型重写的方法了,就能产生多种形态了。
我们结合一个小业务来使用一下多态:主人喂食不同的宠物
定义抽象宠物类
public class Pet {
public void eat() {}
}
定义狗类,继承 Pet 类
public class Dog extends Pet {
public void eat() {
System.out.println("狗吃食了....");
}
}
定义猫类,继承 Pet 类
public class Cat extends Pet {
public void eat() {
System.out.println("猫吃食了....");
}
}
定义主人类
public class Master {
public void feed(Pet pet) {
pet.eat();
}
}
测试使用
public class Main {
public static void main(String[] args) {
Master master = new Master();
Cat cat = new Cat();
Dog dog = new Dog();
// 同一个类型,调用同一个方法,出现不同的形态
master.feed(cat); // "猫吃食了...."
master.feed(dog); // "狗吃食了...."
}
}
在上诉示例代码里,Master 类的 feed 方法传入类型为Pet类型的参数,而Pet类就是Dog类和Cat类的父类,使用时传入不同的子类,这样就可以调用到不同的子类方法。
这就是我们在上面说的向上转型,父类型引用指向子类型对象!!!
五、多态的弊端
不能使用子类特有的成员属性和子类特有的成员方法。
所以使用多态时,想用子类型特有的成员方法或属性怎么办呢?
那就要把父类型引用指向子类型的这个东西,再做一次向下转型。
改造一下上面的Cat类,增加特有的成员属性和成员方法
public class Cat extends Pet {
// 子类特有的成员变量
public String name;
public Cat() {
this.setName();
}
public void setName() {
this.name = "tom";
}
public void eat() {
System.out.println("猫吃食了....");
}
// 子类特有的方法
public static void catchMouse() {
System.out.println("猫抓到一只老鼠....");
}
}
测试使用
public class Main {
public static void main(String[] args) {
Pet pet = new Cat();
pet.eat();
// pet.catchMouse(); // 编译时找不到这个方法
// System.out.println("pet'name is " + pet.name); // 编译时找不到这个属性
// 向下转型为Cat类型
Cat pc = (Cat)pet;
// 可以访问子类特有的属性和方法了
pc.catchMouse();
System.out.println("pet'name is " + pc.name);
}
}
向下转型后,就能访问子类特有的属性和方法了,这样做有缺点,不过好处就是更灵活了,不需要在堆内存中再创建一个新的对象了。
所以别人问你多态是什么,知道该怎么回答了吧,别再傻傻的用多种形态来一句话概括了。