多态的基础语法
Q:什么是多态?
A:多种形态,多种状态。
学习多态基础语法之前,我们需要普及两个概念。
注意---java中允许向上转型,也允许向下转型
无论是向上转型,还是向下转型,
两种类型之间必须有继承关系,没有继承关系就会报错。
1、向上转型
子--->父(自动类型转换)
2、向下转型
父--->子(强制类型转换,需要加强制类型转换符)
什么时候必须向下转型?
不能随意做强制类型转换,只有当你想访问的是子类对象中“特有”的方法,此时必须进行向下转型。
强制类型转换符:(子类名)
理解两种转型
向上转型:
父类 =new 子类;(表示子类对象被转化为了父类对象)
(1)子类中重载的方法会覆盖父类中的方法;(方法覆盖)这就是动态连接
(2)而子类中单独存在的方法无法调用; (方法丢失)
也就是说:向上转型时,子类只能重载并覆盖父类的方法,除此之外没有其他方法了(暂时先这么理解)。
向下转型:
向下转型的前提是必须先向上转型(但一定是要转回与之前一样的类型,不能转成了其他类型,这样子会报错)
(1)个人的理解就是为了解决向上转型时 ,子类中单独存在的方法会丢失的现象,经过又一次的向下转型转回来了,子类中那些单独存在的方法也就能用了;
(2)一定是要转回与之前一样的类型,不能转成了其他类型,这样子会报错。
多态指的是
父类型的引用指向子类型对象。
包括编译阶段和运行阶段,
编译阶段:静态绑定父类的方法;
运行阶段:动态绑定子类型的对象的方法。
多种形态。
点击查看代码
//动物类,父类
public class Animal {
public void move(){
System.out.println("动物在移动!!");
}
//鸟类,子类
public class Bird extends Animal{
//重写move()方法
public void move(){
System.out.println("鸟儿在飞翔!!");
}
public void sing(){
System.out.println("鸟儿在歌唱!!");
}
}
//猫类,子类
public class Cat extends Animal {
//重写move()方法
public void move(){
System.out.println("猫在走猫步!!");
}
//猫除了move之外,应该有自己特有的行为,例如--抓老鼠
//这个行为是子类特有的方法
public void catchMouse(){
System.out.println("猫在抓老鼠!!!");
}
}
点击查看代码
public class Test01 {
public static void main(String[] args) {
Animal a1 = new Animal();
Cat c1 = new Cat();
Bird b1 = new Bird();
a1.move();//动物在移动!!
c1.move(); //猫在走猫步!!
b1.move();//鸟儿在飞翔!!
代码可以这样写吗?
/**
* 1、Animal和Cat之间有继承关系吗?---有
* 2、Animal是父类,Cat是子类
* 3、Cat is a Animal,这句话能不能疏通?---能
* 4、经过测试得知,java中支持这样的一个语法
* 父类型的引用允许指向子类型的对象.
*/
//向上转型
Animal a2 = new Cat(); //a2就是父类型的引用,new Cat()是一个子类型对象,允许a2这个父类型引用指向子类型对象
Animal a3 = new Bird();
//调用a2的move()方法
/**
* 什么是多态?
* 多种形态,多种状态。
* 注意:为什么没有输出Animal中的“动物在移动”,而是输出子类中重写后的move()方法?
* (即: 编译阶段只知道a2的类型是Animal,编译通过;运行时是底层对象在移动,也就是Cat在移动。真正负责移动的是对象。)
* 分析:a2.move()
* java程序分为编译阶段和运行阶段
* 1、先来分析编译阶段--
* 对于编译器来说,编译器只知道a2的类型是Animal,
* 所以编译器在检查语法的时候,会去Animal.class字节码文件中找move()方法,
* 找到后,绑定move()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定)
* 2、再来分析运行阶段--
* 运行阶段,实际上在堆内存中,创建的java对象是Cat对象,
* 所以move的时候,真正参与move的对象是一只猫,
* 所以运行阶段会动态执行Cat对象的move方法。
* 这份过程属于动态绑定(运行阶段绑定属于动态绑定)
* 3、何来多态?
* 多态表示多种形态,编译的时候是一种形态,运行的时候是另一种形态。
* 因此叫多态。
* */
a2.move(); //猫在走猫步!
//调用a3的move()方法
a3.move(); //鸟儿在飞翔!!
前提:在猫类中写了独有的catchMouse()方法
Animal a5 = new Cat(); //底层对象是一只猫
//分析这个程序能否编译和运行?
//分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定
//只有编译通过的代码才能运行,没有编译通过,根本轮不到运行
//a5.catchMouse(); //编译器报错
//因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法中找,结果没有找到,静态绑定失败,编译报错,无法运行。
//编译器只看等号左边!!!!!!!!
/**
* Q:假设代码写到这里,非要调用catchMouse()方法怎么办?
* A:必须用到“向下转型”(强制类型转换)
* Q:以下这行代码为什么没有报错?
* A:a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系,所以没有报错*/
Cat x = (Cat)a5;
x.catchMouse();
//向下转型有风险吗?
Animal a6 = new Bird();
/**
* Q:分析以下程序,编译报错还是运行报错?
* A:编译器检测到a6这个引用是Animal类型,而Animal和Cat之间存在继承关系,所以可以向下转型。编译没有出错。
* 运行阶段,堆内存实际创建的对象是---Bird对象,
* 在运行过程中,拿着Bird对象去转换成Cat对象就不行了
* 因为Bird和Cat之间没有继承关系。
*
* 运行时出现异常,这个异常和空指针异常一样非常重要:
* java.lang.ClassCastException : 类型转换异常
* java.lang.NullPointException : 空指针异常
* Cat y = (Cat)a6;
* y.catchMouse();
*
* Q:怎么避免ClassCastException异常的发生??
* A: 利用运算符
* instanceof(运行阶段动态判断)
* 第一:instanceof可以在运行阶段动态判断引用指向的对象的类型;
* 第二:instanceof的语法
* (引用 instanceof 类型)
* 第三:instanceof运算符的运算结果只能是---true/false
* 第四:c是一个引用,c变量保存了内存地址,指向了堆中的对象。
* 假设(c instanceof Cat)为true表示:
* c引用指向的堆内存中的java对象是一个Cat。
* 假设(c instanceof Cat)为false表示:
* c引用指向的堆内存中的java对象不是一个Cat。
* 程序员要养成一个好习惯--
* 任何时候,任何地点,对程序向下转型的时候,一定要使用instanceof运算符进行判断。
* 这样可以很好的避免:ClassCastException
* */
点击查看代码
System.out.println(a6 instanceof Cat); //结果为false
if (a6 instanceof Cat) { //如果a6是一只Cat
Cat y = (Cat)a6; //再进行强制类型转换
y.catchMouse();
}
}
}
点击查看代码
public class Test02 {
public static void main(String[] args) {
Animal x = new Bird();
Animal y = new Cat();
if(x instanceof Bird){ //必须要通过instanceof判断
Bird b = (Bird)x;
b.sing();
} else if (x instanceof Cat) {
Cat c = (Cat)x;
c.catchMouse();
}
if(y instanceof Bird){
Bird b = (Bird)y;
b.sing();
} else if (y instanceof Cat) {
Cat c = (Cat)y;
c.catchMouse();
}
}
}