多态
一、向上转型与向下转型
(一)向上转型
//动物类,父类
public class Animal{
//移动的方法
public void move(){
System.out.println("动物在移动!");
}
}
//猫类,子类
public class Cat extends Animal{
//对move()方法进行重写
public void move(){
System.out.println("cat在走猫步");
}
}
//鸟类,子类
public class Bird extends Animal{
//对move()方法进行重写
public void move(){
System.out.println("鸟儿在飞翔");
}
}
//测试类
public class Test{
public static void main(String[] args) {
Animal a1 = new Animal();
a1.move(); // 动物在移动!
Cat a2 = new Cat();
a2.move(); // cat在走猫步
Bird a3 = new Bird();
a3.move(); // 鸟儿在飞翔
}
}
以上代码正常执行,并且正常运行出结果。现在试试测试类能不能这么写?
public class Test{
public static void main(String[] args) {
Animal a1 = new Animal();
a1.move(); // 动物在移动!
Animal a2 = new Cat();
a2.move(); // cat在走猫步
Animal a3 = new Bird();
a3.move(); // 鸟儿在飞翔
}
}
Animal a2 = new Cat();
a2就是父类型的引用
new Cat()是一个子类型的对象。
允许a2这个父类型引用指向子类型的对象。
这个语法被称为向上转型。
学习多态基础语法之前,先学习两个概念:
第一个:向上转型
? 子------->父(自动类型转换)
第二个:向下转型
? 父-------->子(强制类型转换,需要加强制类型转换符)
注意:无论向上转型还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器会报错。
java程序分为编译阶段和运行阶段。
编译阶段:
? 对于编译器来说,编译器只知道a2的类型是Animal,所以编译器在检查语法的时候,会去Animal.class字节码文件
? 中找move()方法,找到后绑定上move()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
运行阶段:
? 运行阶段的时候,实际上在堆内存中创建的java对象是Cat对象,所以move的时候,真正参与move的对象是一只猫,
? 所以运行阶段会动态执行Cat对象的move()方法。这个过程属于运行阶段绑定。(运行阶段绑定属于动态)
多态表示多种形态:
? 编译的时候一种形态。
? 运行的时候另一种形态。
多态指的是:
? 父类型引用指向子类型对象。包括编译阶段和运行阶段。
? 编译阶段:绑定父类的方法。
? 运行阶段:动态绑定子类型对象的方法。
(一)向下转型
不要随便做强制类型转换,当你需要访问的是子类对象中“特有”的方法,此时必须进行向下转型。
假设将以上代码中Cat类改为:
//猫类,子类
public class Cat extends Animal{
//对move()方法进行重写
public void move(){
System.out.println("cat在走猫步");
}
public void catchMouse(){
System.out.println("猫正在抓老鼠!");
}
}
测试类调用
public class Test{
public static void main(String[] args) {
Animal a1 = new Animal();
a1.move(); // 动物在移动!
Animal a2 = new Cat();
a2.move(); // cat在走猫步
Animal a3 = new Bird();
a3.move(); // 鸟儿在飞翔
Animal a4 = new Cat();//a4是Animal类型,转成Cat
Cat x = (Cat)a4;
x.catchMouse(); // 猫正在抓老鼠!
}
}
这里就是向下转型。
向下转型有风险吗?分析以下程序:
public class Test{
public static void main(String[] args) {
Animal a1 = new Animal();
a1.move(); // 动物在移动!
Animal a2 = new Cat();
a2.move(); // cat在走猫步
Animal a3 = new Bird();
a3.move(); // 鸟儿在飞翔
Animal a4 = new Cat();//a4是Animal类型,转成Cat
Cat x = (Cat)a4;
x.catchMouse(); // 猫正在抓老鼠!
Animal a5 = new Bird();
Cat y = (Cat)a5;
y.catchMouse();
}
}
在编译阶段,编译器检测到a5这个是Animal类型,而Animal与Cat之间也存在继承关系,所以向下转型编译上没有问题。
在运行阶段,堆内存实际创建的对象是:Bird对象。在实际运行过程中,拿着Bird对象转换成Cat对象就不行,因为它们
之间不存在继承关系。
会出现java.lang.ClassCastException:类型转换异常。
那么如何去避免ClassCastException异常的发生呢?
引入运算符instanceof
第一:instanceof可以再运行阶段动态判断引用指向的对象的类型。
第二:instanceof的语法
? (引用 instanceof 类型)
第三:insatanceof运算符的结果只能是:true 或者false。
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
? 假设(c instanceof Cat)为true表示:
? c引用指向的堆内存中的java对象是一个Cat。
? 假设(c instanceof Cat)为false表示:
? c引用指向的堆内存中的java对象是一个Cat。
程序员应该养成一个习惯,对类型进行向下转型时,一定要用instanceof运算符进行判断!!
所以将以上程序改为:
public class Test{
public static void main(String[] args) {
Animal a1 = new Animal();
a1.move(); // 动物在移动!
Animal a2 = new Cat();
a2.move(); // cat在走猫步
Animal a3 = new Bird();
a3.move(); // 鸟儿在飞翔
Animal a4 = new Cat();//a4是Animal类型,转成Cat
Cat x = (Cat)a4;
x.catchMouse(); // 猫正在抓老鼠!
Animal a5 = new Bird();
if(a5 instanceof Cat){
Cat y = (Cat)a5;
y.catchMouse();
}
}
}
多态在开发中的作用
有以下练习题:
编写程序模拟“主人”喂养“宠物”的场景
//宠物狗狗类
public class Dog{
//提供一个吃的方法
public void eat(){
System.out.println("狗狗喜欢啃骨头,吃的很香!");
}
}
//主人类
public class Master{
//喂养宠物狗狗
//这是最初开始的时候,主人只喜欢养Dog
public void feed(Dog d){
d.eat();
}
}
public class Test{
public static void main(String[] args){
//创建主人对象
Master ding = new Master();
//创建宠物对象
Dog d = new Dog();
//主人喂宠物
ding.feed(d);
}
}
这时候主人随着时间推移,主人又喜欢养猫咪,这就表示在实际开发中又产生了新的需求。
不使用多态的情况下,我们只能在Master类中添加一个新的方法。先添加一个猫咪类,然后由于新的需求产生,我们不得不对Master类进行修改。
**这样写代码的问题:**软件在扩展过程中,代码修改的越少越好,修改的越多,系统的稳定性越差,未知的风险就越多。
软件开发中有一个开闭原则:OCP(开闭原则) 对扩展开放,对修改关闭。
现在用多态进行代码编写。
//宠物狗狗类
public class Dog extends Pet{
//提供一个吃的方法
public void eat(){
System.out.println("狗狗喜欢啃骨头,吃的很香!");
}
}
//主人类
public class Master{
//喂养宠物狗狗
//这是最初开始的时候,主人只喜欢养Dog
public void feed(Pet pet){
pet.eat();
}
}
public class Test{
public static void main(String[] args){
//创建主人对象
Master ding = new Master();
//创建宠物对象
Dog d = new Dog();
//主人喂宠物
ding.feed(d);
//创建猫的对象
Cat c = new Cat();
ding.feed(c);
}
}
public class Cat extends Pet{
public void eat(){
System.out.println("猫猫喜欢吃鱼,吃的很香!");
}
}
//宠物父类
public class Pet{
public void eat(){}
}
就算日后主人又喜欢新的别的宠物,也不需要再对Master类进行修改,只需要按照需求进行增加即可。
|