目录
1、什么是多态
字面意思,“多种形态”,同一个行为具有不同的表现形式,同一个接口使用不同的实例而执行不同的操作,同一个操作在不同对象上会产生不同的结果。
2、多态存在的三个必要条件
继承、重写、父类引用指向子类的对象
3、多态的类型
①向上转型(upcasting)的多态:是一种自动类型转换,是将子类型转换为父类型
②向下转型(downcasting)的多态:是一种强制类型转换,需要手动的通过强制类型转换符进行实现,将父类型转换成为子类型。
⚠注意,无论是哪种类型的多态,两个类型之间一定要存在继承关系,否则编译阶段无法通过。无继承就无多态。
4、多态的实现
语法结构:父类名 引用=new 子类名();
5、多态执行原理分析
因为多态的存在,整个程序会有两种状态 ①编译期的形态 ②运行期的形态,可以称之为多态语法机制。
当在编译的时候,编译器会把它看作自动类型转换,只认它为父类型,是一种向上转型,能够自动类型转换完好的完成赋值操作,不会报错,这是编译期的形态。
但是运行的时候,在其底层引用指向的始终是一个子类型的内存单元,所以主体还是子类型的对象,这是一个运行期的形态。
注意因为以上的原因,这个引用只能访问即父类和子类都有的方法(子类重写过的或者从父类那边继承过来的),不能访问到子类中独有的自己的方法。 如果访问父类中没有的方法,编译器太笨了,它只认父类型,在父类中找不到那个方法会进行报错,即使其底层真正的对象是子类型的。
例如:people父类:
public class people {
private String id="130634";
private String name;
public String getId() {return id;}
public void setId(String id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public void sys(){
System.out.println("父类独有的方法");
}
}
China子类
public class China extends people{
public China(){
super();
}
public void syso(){
System.out.println("China子类中独有的方法");
}
}
测试类:
public class test {
int i = 0;
public static void main(String[] args) {
people people=new China();
people.syso(); //此行报错,因为sys()是子类独有的方法,编译器只认为引用是父
//类的引用,在父类中找不到这个方法,就会报错。
}
}
看输出结果:
java: 找不到符号
符号: 方法 syso()
位置: 类型为people的变量 people
但是访问方法 sys( )是可以顺利访问的,因为编译器在父类中可以找到这个方法,在运行的时候,底层是子类对象,子类因为继承了父类,所以也继承了父类中的该方法,编译和运行都能找到这个方法,所以可以顺利执行。
如果找到了那个方法,编译就会通过,这个过程我们称之为静态绑定、编译阶段绑定,只有静态绑定成功后才会有运行阶段。
上文中提到的,父类引用调用子类中独有的方法就会导致编译器在父类的class文件中找不到那个方法,编译报错,导致静态绑定失败,所以也无法进入运行阶段。
在运行阶段,运行方法的时候,实际上调用的还是子类型内存单元中的方法,这称为动态的绑定。
public class test {
int i = 0;
public static void main(String[] args) {
people people=new China();
people.sys();
}
}
看一下输出结果:
父类独有的方法
Process finished with exit code 0
6、如何通过父类引用调用子类独有方法?
通过向下转型实现,将父类的引用强制转型为子类型,就可以正确的实现静态绑定和动态绑定,完整的实现运行。
例如依旧是上边的people-China的例子,当people想要调用子类中独有的方法sys()的时候,静态绑定失败,编译器只认识父类型,这时候如果通过强制类型转换,对其进行转型,如下:
public class test {
int i = 0;
public static void main(String[] args) {
people people=new China();
China china=(China)people; //将引用进行类型的转换
china.syso(); //然后再进行方法的调用
}
}
这时,再进行代码的执行,可以得到正确的输出结果:很明显成功的调用了China子类中的独有的方法。
China子类中独有的方法
Process finished with exit code 0
但是在使用的时候要注意强转的对象以及接收对象,例如:
父类:Animal类
子类:Cat类、Dog类
有Animal a=new Dog(); //①
Cat cat=(Cat)a; //②
这种在编译阶段是不会出现问题的,因为①是向上转型,编译正确,而②的强转,赋值符号左右两侧的类型相匹配,不存在什么问题,是向下转型。
但是在运行阶段会出现问题,因为运行时以上的代码实际上可以理解为:
Cat cat=(Cat)new Dog();
将两个不相关的子类之间建立一个关系,这种是不成立的,因为多态的前提是两个类之间存在继承关系。
这种代码写法的运行结果会报如下错误:类型转换异常,这种异常总是在向下转型的时候会发生这种异常。
Exception in thread "main" java.lang.ClassCastException: China cannot be cast to American at test.main(test.java:5) Process finished with exit code 1
所以其实不合理的向下转型的使用是存在一定的风险的,并且这种异常只有在强制类型转换的时候会发生。 即使编译的时候不会出现问题,在运行的时候也是会出现问题的,向上的转型不会出现相关的问题。
7、instanceof关键字
如何解决上述的问题?使用instanceof关键字可以避免以上问题的发生。
语法结构:引用 instnceof 数据类型名
运算结果:boolean型
A引用 instanceof B类型
true:A引用指向的对象是B类型的对象
false:A引用指向的对象不是B类型的对象
作用:用于在转型的时候,去进行类型的判断,避免出现不合理的向下转型的情况。
以上面的例子(Animal-Dog-Cat)为例:
有Animal a=new Dog(); //①
Cat cat=(Cat)a; //②
当进行②操作的时候,先对引用指向的对象类型进行判断,如果是Dog类型的就定义Dog类型的引用接收强转为Dog类型的引用a,同理Cat,这样就不会导致向下转型时,使类型不匹配。
如下所示:
if(a instanceof Cat){
Cat cat=(Cat)a;
}else if(a instanceof Dog){
Dog dog=(Dog)a;
}
8、多态的作用
那么多态到底有什么作用?以(people-China-American)例子为例
人 和美国和中国交朋友
主体:人、美国、中国
事件:①人请求交朋友 ②美国和中国答应交朋友
people类:
public class people {
private String id="130634";
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//和美国交朋友
public void makeFriend(American american){
american.OK();
}
//和中国交朋友
public void makeFriend(China china){
china.OK();
}
}
America:
public class America{
public void OK(){
System.out.println("美国人:好的,我们交朋友");
}
}
China:
public class China{
public void OK(){
System.out.println("中国人:好的,我们交朋友");
}
}
测试类:
public class test {
int i = 0;
public static void main(String[] args) {
people people=new people();
American american=new American();
China china=new China();
people.makeFriend(china);
people.makeFriend(american);
}
}
运行结果:
中国人:好的,我们交朋友
美国人:好的,我们交朋友
Process finished with exit code 0
整体代码的运行并不存在什么问题, 但是如果“人”交朋友的实体数目多了以后,要修改很多代码
例如,人和巴西交朋友
首先添加巴西类:
public class Brazil{
public void OK(){
System.out.println("巴西人:好的,我们交朋友");
}
}
然后在people类中添加方法:
public void makeFriend(Brazil brazil){
brazil.OK();
}
测试类中也要添加如下代码:
Brazil brazil=new Brazil();
people.makeFriend(brazil);
运行结果:
中国人:好的,我们交朋友
美国人:好的,我们交朋友
巴西人:好的,我们交朋友
Process finished with exit code 0
运行结果不存在问题
可能觉得代码修改也不是很复杂,但是要知道在实际的项目中,代码量是很大的,而且代码之间功能环环相扣,很有可能更改了代码之后,需要修改的地方有很多,这样代码的升级和修改其实是不方便的,也会更容易导致代码错误,这样便出现了“多态”这个概念。
如果将America、China、Brazil这些实体类统一的去继承一个父类,在这些实体类对象的地方统一用该父类型的引用去替代,这样在进行代码的添加的时候,代码会更加的简洁方便。
如下:创建一个父类Country
public class Country {
public void OK(){
}
}
America、China、Brazil三个类均都继承Country类,重写了OK方法
修改people类中的makeFriend方法
/*
//和美国交朋友
public void makeFriend(American american){
american.OK();
}
//和中国交朋友
public void makeFriend(China china){
china.OK();
}
*/
public void makeFriend(Country country){
country.OK();
}
然后修改测试类:
public class test {
int i = 0;
public static void main(String[] args) {
people people=new people();
/*American american=new American();
China china=new China();
Brazil brazil=new Brazil();
people.makeFriend(china);
people.makeFriend(american);
people.makeFriend(brazil);*/
Country country=new American();
people.makeFriend(country);
country=new China();
people.makeFriend(country);
country=new Brazil();
people.makeFriend(country);
}
}
很明显这样代码的修改和升级变得相对简单, people类几乎无需进行改动,测试类中对于参数的传递更加的简便。
这就是多态作用的体现。
降低程序的耦合度,提高程序的扩展能力,能使用多态尽可能的使用多态,使父类引用指向子类对象,便于代码的处理和修改,便于软件的升级
程序员要面向抽象层级进行编程,而不是面向具体的对象,对于猫和狗来说,抽象的一层就是pet类,用pet类统一接收,利用多态实现不同的效果和输出结果。