多态
为了理解多态,我们首先新建三个类——Mammal(哺乳类),Whale(鲸鱼类),Earth(地球类)。
public class Mammal{
public void move(){
System.out.println("奔跑");
}
}
public class Whale extends Mammal{
@Override
public void move(){
System.out.println("用鳍游动");
}
}
public class Earth {
public static void main(String[] args) {
Whale whale = new Whale();
whale.move();////当子类对象调用重写后的方法时,调用的是子类重写后的方法
}
}
对于Earth类中的whale对象而言,当子类对象调用重写之后的方法时,调用的是子类重写之后的方法,显而易见
public class Earth {
public static void main(String[] args) {
Whale whale = new Whale();
whale.move();////当子类对象调用重写后的方法时,调用的是子类重写后的方法
double price = 9;
Mammal mammal = new Whale();//自动类型转换:父类类型的变量指向其子类创建的对象 对象上转型 new Whale()上转型对象
}
}
首先让我们来看double price=9;这行代码,我们知道double是双精度浮点型,9是整形值,毫无疑问小于double的范围,这个时候系统自动的把9的范围上调,变成double类型的值。
同理——
•父类类型(比如Mammal)的变量(比如mammal1)指向子类创建的对象,使用该变量调用父类中一个被子类重写的方法(比如move方法),则父类中的方法呈现出不同的行为特征,这就是多态。
•Java引用变量有两种类型,分别是编译时类型和运行时类型:编译时类型由声明该变量时使用的类型决定;运行时类型由实际赋给该变量的对象。如果编译时类型和运行时类型不一致,就可能出现所谓多态。
其中mammal被称为上转型对象。
public class Earth {
public static void main(String[] args) {
Whale whale = new Whale();
whale.move();////当子类对象调用重写后的方法时,调用的是子类重写后的方法
double price = 9;
Mammal mammal = new Whale();//自动类型转换:父类类型的变量指向其子类创建的对象 对象上转型 new Whale()上转型对象
mammal.move();//两个状态:表面调用的是父类的方法;但是执行时由于mammal存的是子类创建的对象,所以执行时执行的是子类中的方法
//如果编译时类型和运行时类型相同,则一定不会出现多态;√
//如果编译时类型与运行时类型不相同,则一定出现多态x
}
}
如果子类Whale中新增了breath方法,则在Earth类之中添加mammal.breath();会报错,因为——
上转型对象不能调用子类新增的方法 和新增加的属性。
•上例分析:当把子类创建的对象直接赋给父类引用类型时,例如上例Test main方法中“Mammal mammal1 = new Whale();”, mammal1引用变量的编译时类型是Mammal,运行时类型是Whale,当程序运行时,该引用变量mammal1调用父类中被子类重写的方法时,其方法行为表现的是子类重写该方法后的行为特征,而不是父类方法的行为特征。
总结,在编译时只会加载父类,运行则是运行子类,在子类之中新增的变量和方法父类之中显然没有,因此加载时就会出现错误,因为只加载父类,无法找到新增的子类变量和方法,甚至包括子类之中本来就有的方法和变量,如果父类之中没有相同名称的同样存在,也会报错,因为代码的左边是编译时类型只会加载父类,不会动子类。
•上转对象调用父类方法,如果该方法已被子类重写,则表现子类重写后的行为特征,否则表现父类的行为特征。
•使用上转型对象调用成员变量,无论该成员变量是否已经被子类覆盖,使用的都是父类中的成员变量:
•如果编译时类型和运行时类型不一致时未必会出现所谓多态,如下例子:
下转型对象
//问题:在上转型对象中如何实现调用子类新增的方法 和新增加的属性 对象下转型
//int price = (int)9.0
// Whale whale = (Whale)mammal;
和强制类型转换类似,我们可以将mammal强制转换成Whale类。
但是——下转型的前提是先出现上转型对象。
Mammal mammal = new Mammal();//下转型的前提是先出现上转型对象
// Whale whale = (Whale)mammal;
// System.out.println(new Whale().weight);
// Mammal mammal = new Whale();
// System.out.println(mammal.weight);
//在多态的前提下,父类中被子类重写的方法没有必要有方法体
Mammal mammal = new Whale();//自动类型转换:父类类型的变量指向其子类创建的对象 对象上转型 new Whale()上转型对象
mammal.move();
•可以将上转型对象再强制转换为创建该对象的子类类型的对象,即将上转型对象还原为子类对象,对应于数据类型转换中的强制类型转换。
•还原后的对象又具备了子类所有属性和功能,即可以操作子类中继承或新增的成员变量,可以调用子类中继承或新增的方法。
•注意:不可以将父类创建的对象通过强制类型转换赋值给子类声明的变量。
发散思考
如果子类对于父类的方法重写,我们有需要的是子类,那么父类的方法体是否还有存在的必要?
即我们只需要whale类的move方法,mammal的move方法我们根本用不上,那么显然,mammal的move方法体有没有都一个样。
即在mammal类中舍弃方法体——
public abstract void move(){
}
这时,必须要在move方法前加上abstract修饰词,表明这时一个抽象方法。
如果一个类里面只要有一个抽象方法,那么这个类就是抽象类。
public abstract class Mammal {
public abstract void move(){
}
}