多态的概念:多态其实是同一个对象在不同时刻体现出来的不同状态
多态分为三种:
- 具体类多态(几乎没有):
class Fu { }
class Zi extends Fu { }Fu f = new Zi();
- 抽象类多态(常用):
abstract class Fu { }
class Zi extends Fu { }Fu f = new Zi();
请看 Java多态实例详解三步走(二)【抽象类多态】 - 接口类多态(最常用):
interface class Fu { }
class Zi implements Fu { }Fu f = new Zi();
请看 Java多态实例详解三步走(三)【接口多态】
本文讲解为具体类多态,也是基础。
多态的前提:
1、有继承或者实现关系。
2、有方法重写(因为多态就是靠方法重写来体现不同状态的)。
3、要有父类或者父接口引用指向子类对象。
格式:父 f = new 子()
多态中的成员访问特点:
1、成员变量
编译看左边(父类),运行看左边(子类)。
2、构造方法
创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
3、成员方法
编译看左边(父类),运行看右边(子类)
(因为成员方法存在方法重写,所以成员方法访问的时候访问的是子类)。
4、静态方法
编译看左边(父类),运行看左边(子类)
(静态和类相关,算不上重写,所以,访问还是左边)
多态的好处:
1、提高了代码的维护性(由继承来保证)
2、提高了代码的扩展性(由多态来保证)
对于下面的代码,是没有用多态来写的,如果创建一个新的子类,就要在工具类中多增加一个方法,如果创建很多个,那么同样的代码要重复好多遍,如果用多态,就可以保证在多增加一个类的同时,工具类中不用再增加一个方法。这就提高了代码的扩展性。
代码1:
public class Main {
public static void main(String[] args) {
cat c = new cat();
dog d = new dog();
AnimalTool.useCat(c);
AnimalTool.useDog(d);
}
}
//创建工具类
class AnimalTool {
private AnimalTool() {}
//调用猫的功能
public static void useCat(cat c) {
c.eat();
c.sleep();
}
//调用狗的功能
public static void useDog(dog d) {
d.eat();
d.sleep();
}
//如果我创建一个新的子类,就要在这里多增加一个方法,如果创建很多个,那么同样的代码要重复好多遍
}
//创建父类
class Animal {
public void eat() {
System.out.println("eat");
}
public void sleep() {
System.out.println("sleep");
}
}
//创建子类
class cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void sleep() {
System.out.println("猫卧着睡");
}
}
//创建子类
class dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void sleep() {
System.out.println("狗趴着睡");
}
}
如果用多态,就会节省很多代码。因为只需要修改AnimalTool类,所以只给出了AnimalTool修改后的代码。
代码2:
class AnimalTool {
private AnimalTool() {}
//运用多态就能很好的解决代码重复的问题
public static void useAnimal(Animal a) {
a.eat();
a.sleep();
}
}
多态的弊端:不能使用子类的特有功能。
如果想要解决就涉及到对象的转型问题
向上转型:Fu f = new Zi();
向下转型:Zi z = (Zi)f; //要求f必须是能够转换为Zi类的
例如下面的代码:
public class Main {
public static void main(String[] args) {
Animal a = new Dog();//向上转型
a.eat();
//这里调用eat方法是没有问题的
//需注意的是,这里调用的eat方法是Dog中的方法,而不是Animal中的eat方法。
//也就是说,这里调用的是重写后的eat方法
//所以这里输出的应该是 狗吃东西
//这里需要注意,此时输出成员变量flag,输出的是父类中的值
System.out.println(a.flag);//输出的值为78
//调用Dog中的特有方法会报错,可以把注释去掉试一试
//a.lookDoor();
//出现以上两种情况是因为,a 是 Animal 类型的。
//这就对应了上边说的,编译看左边,运行看右边。
//但是,如果我现在就想用Dog中的lookDoor方法,怎么办
//那就把父类的引用强制转换为子类的引用(向下转型)。
Dog d = (Dog)a;
//这时再使用lookDoor方法就没有问题了
d.lookDoor();
//可能有的小伙伴要说了,我直接Dog d = new Dog() 不就行了。
//这样做也可以,但是会浪费内存
a = new Cat()//我把 a 的地址换为指向Cat,内存中就是猫的
a.eat();//输出 猫吃东西
//所以,我将a转化为Cat是没问题的
Cat c = (Cat)a;
// Dog dd = (Dog)a;
//因为此时内存中是猫,所以将此时的a转化为Dog是会报错
//ClassCastException(类型转换异常),一般在向下转型时出现
//以上也就解释了在向下转型时,要求f必须是能够转换为Zi类的这个条件。
}
}
class Animal {
int flag = 78;
public void eat() {}
}
class Dog extends Animal {
int flag = 23;
public void eat() {
System.out.println(“狗吃东西”);
}
public void lookDoor() {
System.out.println(“狗看门”);
}
}
class Cat extends Animal {
int flag = 99;
public void eat() {
System.out.println(“猫吃东西”);
}
public void playGame() {
System.out.println(“猫玩游戏”);
}
}