多态
多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态的前提:必须存在继承或者实现关系。
多态要注意的细节:
- 多态情况下,子父类存在同名的成员变量时,访问的是父类的成员变量。
- 多态情况下,子父类存在同名的非静态的成员函数时,访问的是子类的成员函数。
- 多态情况下,子父类存在同名的静态的成员函数时,访问的是父类的成员函数。
- 多态情况下,不能访问子类特有的成员。
总结:多态情况下,子父类存在同名的成员时,访问的都是父类成员,除了在同名非静态函数时才是访问子类的。
多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。那么在java中多态是如何体现的呢?
在java中允许这样的两种语法出现,一种是向上转型(Upcasting),一种是向下转型(Downcasting),向上转型是指子类型转换为父类型,向下转型是指父类型转换为子类型。
\
// 动物类:父类
class Animal{
// 移动的方法
public void move(){
System.out.println("动物在移动!!!");
}
}
// 鸟儿类,子类
class Bird extends Animal{
// 重写父类的move方法
public void move(){
System.out.println("鸟儿在飞翔!!!");
}
// 也有自己特有的方法
public void sing(){
System.out.println("鸟儿在歌唱!!!");
}
}
// 猫类,子类
class Cat extends Animal{
// 对move方法进行重写
public void move(){
System.out.println("cat走猫步!");
}
// 猫除了move之外,应该有自己特有的行为,例如抓老鼠。
// 这个行为是子类型对象特有的方法。
public void catchMouse(){
System.out.println("猫正在抓老鼠!!!!");
}
}
public class Test{
public static void main(String[] args){
Animal a1 = new Animal();
a1.move(); //动物在移动!!!
Cat c1 = new Cat();
c1.move(); //cat走猫步!
Bird b1 = new Bird();
b1.move(); //鸟儿在飞翔!!!
// 代码可以这样写吗?
/*
1、Animal和Cat之间有继承关系吗?有的。
2、Animal是父类,Cat是子类。
3、Cat is a Animal,这句话能不能说通?能。
4、经过测试得知java中支持这样的一个语法:
父类型的引用允许指向子类型的对象。
Animal a2 = new Cat();
a2就是父类型的引用。
new Cat()是一个子类型的对象。
允许a2这个父类型引用指向子类型的对象。
*/
Animal a2 = new Cat();
Animal a3 = new Bird();
// 没有继承关系的两个类型之间存在转型吗?
// 错误: 不兼容的类型: Dog无法转换为Animal
// Animal a4 = new Dog();
// 调用a2的move()方法
/*
什么是多态?
多种形态,多种状态。
分析:a2.move();
java程序分为编译阶段和运行阶段。
先来分析编译阶段:
对于编译器来说,编译器只知道a2的类型是Animal,
所以编译器在检查语法的时候,会去Animal.class
字节码文件中找move()方法,找到了,绑定上move()
方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
再来分析运行阶段:
运行阶段的时候,实际上在堆内存中创建的java对象是
Cat对象,所以move的时候,真正参与move的对象是一只猫,
所以运行阶段会动态执行Cat对象的move()方法。这个过程
属于运行阶段绑定。(运行阶段绑定属于动态绑定。)
多态表示多种形态:
编译的时候一种形态。
运行的时候另一种形态。
*/
a2.move(); //cat走猫步!
// 调用a3的move()方法
a3.move(); //鸟儿在飞翔!!!
// ======================================================================
Animal a5 = new Cat(); // 底层对象是一只猫。
// 分析这个程序能否编译和运行呢?
// 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
// 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
// 错误: 找不到符号
// why??? 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法
// 结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
//a5.catchMouse();
// 假设代码写到了这里,我非要调用catchMouse()方法怎么办?
// 这个时候就必须使用“向下转型”了。(强制类型转换)
// 以下这行代码为啥没报错????
// 因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错。
Cat x = (Cat)a5;
x.catchMouse(); //猫正在抓老鼠!!!!
// 向下转型有风险吗?
Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
/*
分析以下程序,编译报错还是运行报错???
编译器检测到a6这个引用是Animal类型,
而Animal和Cat之间存在继承关系,所以可以向下转型。
编译没毛病。
运行阶段,堆内存实际创建的对象是:Bird对象。
在实际运行过程中,拿着Bird对象转换成Cat对象
就不行了。因为Bird和Cat之间没有继承关系。
运行是出现异常,这个异常和空指针异常一样非常重要,也非常经典:
java.lang.ClassCastException:类型转换异常。
java.lang.NullPointerException:空指针异常。这个也非常重要。
*/
//Cat y = (Cat)a6;
//y.catchMouse();
// 怎么避免ClassCastException异常的发生???
/*
新的内容,运算符:
instanceof (运行阶段动态判断)
第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
第二:instanceof的语法:
(引用 instanceof 类型)
第三:instanceof运算符的运算结果只能是:true/false
第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
假设(c instanceof Cat)为true表示:
c引用指向的堆内存中的java对象是一个Cat。
假设(c instanceof Cat)为false表示:
c引用指向的堆内存中的java对象不是一个Cat。
任何时候,任何地点,对类型进行向下转型时,一定要使用
instanceof 运算符进行判断。(java规范中要求的。)
这样可以很好的避免:ClassCastException
*/
System.out.println(a6 instanceof Cat); //false
if(a6 instanceof Cat){
// 如果a6是一只Cat
Cat y = (Cat)a6; // 再进行强制类型转换
y.catchMouse();
}
}
}
}
向上转型:子--->父 (upcasting)
又被称为自动类型转换:Animal a = new Cat();
向下转型:父--->子 (downcasting)
又被称为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象
是否为某一种类型。
养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。
不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。