一.多态的基础语法
1.什么是多态?
多种形态,多种状态,编译和运行有两个不同的状态。
编译期叫做静态绑定
运行期叫做动态绑定
Animal a=new Cat();
//编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法
//找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关
//真正执行的时候会自动调用“堆内存中真实对象”的相关方法。
a.move();
多态的典型代码:父类型的引用指向子类型的对象。
2.向上转型和向下转型的概念。
向上转型:子---->父(upcasting)
又被称为自动类型转换:Animal a=new Cat();
向下转型:父---->子(downcasting)
又被称为强制类型转换:(Cat)c=(Cat) a;
需要添加强制类型转换符。
不管是向上转型还是向下转型,首先它们之间必须有继承关系。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
Instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型
养成成好习惯,向下转型之前一定要使用
Instanceof运算符进行判断。
3. 什么时候必须进行向下转型
调用子类对象特有的方法时。
以下是在例子中去介绍多态:
/*
多态的基础语法:
1.学习多态基础语法之前,我们需要普及两个概念:
第一个:向上转型
子---->父(自动类型转换)
第二个:向下转型
父---->子(强制类型转换,需要加强制类型转换符)
注意:
java中允许向上转型,也允许向下转型。
*****(五颗星) 无论是向上转型,还是向下转型,两种类型之间必须要有继承关系。
以后在工作中,和别人聊天的时候,要专业一些,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,
因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上转型和向下转型。
2.多态指的是:
父类型引用指向子类型对象。
包括编译阶段和运行阶段。
编译阶段:绑定父类的方法
运行阶段:动态绑定子类型对象的方法。
多种形态。
3.个别同学不要搞错了:
类名.
引用.
只要你想“点”,“点”前面要么是一个类名,要么是一个引用。
4.什么时候必须使用“向下转型”?
不要随便做强制类型转换。
当你需要访问的是子类对象中特有的方法。
此时必须进行向下转型。
*/
public class Test01 {
public static void main(String[] args) {
Animal a1=new Animal();
a1.move();//The animal is moving
Cat c1=new Cat();
c1.move();//the cat is on the catwalk
Bird b1=new Bird();
b1.move();//the bird is flying
//代码能不能这样写呢?
/*
1.Animal和Cat之间有继承关系吗?有的。
2.Animal是父类,Cat是子类。
3.Cat is an animal,这句话能不能说通?能
4.经过测试得知java中支持这样的一个语法:
父类型的引用允许指向子类型的对象。
Animal a2=new Cat();
a2就是父类型的引用。
new Cat()是一个子类型的对象。
允许a2这个父类型引用指向子类型的对象。
*/
Animal a2=new Cat();
a2.move();
Animal a3=new Bird();
a3.move();
/*
什么是多态?
多种形态,多种状态。
分析:a2.move();
java程序分为编译阶段和运行阶段。
先来分析编译阶段:
对于编译器来说,编译器只知道a2类型是Animal,
所以编译器在检查语法错误的时候,会去Animal.class
字节码文件中找move()方法。找到了后,绑定上move()方法
编译通过,静态绑定成功。(编译阶段属于静态绑定。)
再来分析运行阶段:
运行阶段的时候,实际上在堆内存中创建的java对象是
Cat对象,所以运行阶段会动态执行Cat对象的move()方法。
这个过程属于运行阶段绑定。(运行阶段绑定属于动态绑定。)
多态表示多种形态:
编译的时候一种形态。
运行的时候是另一种形态。
*/
//-----------------------------------------------------------------------------------------------
Animal a5=new Cat();//底层对象是一只猫。
//a5.catchMouse();
//分析这个程序能否编译和运行
//分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
//可以得知上面的程序是有问题的。
//因为编译器只知道a5的类型是Animal,去Animal.class文件中没有找到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类型)
第三:instance运算符的运算结果只能是: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);
System.out.println(a6 instanceof Bird);
if(a6 instanceof Cat){//如果a6是一只Cat
Cat y=(Cat)a6;//再进行强制类型转换
y.catchMouse();
}
}
}
二.多态在开发当中的应用:
1.多态在开发当中有什么作用?
非常重要:五颗星。。。。。
多态在开发中的作用是:
降低程序的耦合度,提高程序的扩展力。
public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。
Public class Master{
Public void feed(Pet pet){}
}
以上的代码表示:Master和Dog以及Cat的关系就脱离了,
Master关注的是Pet类。这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。
面向对象的三大特征
封装,继承,多态
真是一环扣一环
有了封装,有了这种整体的概念后
对象和对象之间产生了继承。
有了继承之后,才有了方法的覆盖和多态。
这里提到了一个软件开发原则:
七大原则中最基本的原则:OCP(对扩展开发,对修改关闭)
目的是:降低程序耦合度,提高程序扩展力。
面向抽象编程,不建议面向具体编程。
2.解释之前遗留的问题:
注意2: 私有方法无法覆盖
注意4:方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。
这是因为方法覆盖通常和多态联系起来。
总结两句话:
私有不能覆盖。
静态不谈覆盖。
在方法覆盖中,关于方法的返回值类型。
什么条件满足之后,会构成方法的覆盖呢?
1.方法覆盖必须要求两个类之间具有继承关系。
2.父类中的方法和子类重写之后的方法:
具有相同的方法名,相同的形式参数列表,相同的返回值类型。
学习了多态机制之后:
“相同的返回值类型”可以修改一下吗?
对于返回值类型是基本数据类型来说,必需一致。