多态应该是Java的三大特征中最难理解的一点了,如果这篇博客中有什么错误不足的话,希望大家可以在评论区指出。
1、认识多态
多态性是OOP中的一个重要特性,主要是用来实现动态联编的,换句话说,就是程序的最终状态只有在执行过程中才被决定而非在编译期间就决定了。这对于大型系统来说能提高系统的灵活性和扩展性。
多态可以让我们不用关心某个对象到底是什么具体类型,就可以使用该对象的某些方法,从而实现更加灵活的编程,提高系统的可扩展性。
允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
相同类域的不同对象,调用相同的方法,执行结果是不同的
1.一个对象的实际类型是确定的
1 例如: new Student(); new Person();等
2.可以指向对象的引用的类型有很多
一个对象的实现类型虽然是确定的,但是这个对象所属的类型可能有很多种。
例如: Student继承了Person类
1 Student s1 = new Student(); 2 Person s2 = new Student(); 3 Object s3 = new Student();
因为Person和Object都是Student的父类型
注:一个对象的实际类型是确定,但是可以指向这个对象的引用的类型,却是可以是这对象实际类型的任意父类型。
1. 一个父类引用可以指向它的任何一个子类对象 例如:
1 Object o = new AnyClass(); 2 Person p = null; 3 p = new Student(); 4 p = new Teacher(); 5 p = new Person();
2. 多态中的方法调用
public class Person{ public void run(){} } public class Student extends Person{ }
3. 调用到的run方法,是Student从Person继承过来的run方法
1 main: 2 Person p = new Student(); 3 p.run();
注:子类继承父类,调用a方法,如果a方法在子类中没有重写,那么就是调用的是子类继承父类的a方法, 如果重写了,那么调用的就是重写之后的方法。
注:一个变量x,调用一个方法test,编译器是否能让其编译通过,主要是看声明变量x的类型中有没有定义 test方法,如果有则编译通过,如果没有则编译报错.而不是看x所指向的对象中有没有test方法.
原理:编译看左边,运行不一定看右边。
子类引用和父类引用指向对象的区别
1 Student s = new Student(); 2 Person p = new Student();
变量s能调用的方法是Student中有的方法(包括继承过来的),变量p能调用的方法是Person中有的方法(包括继承过来的)。
但是变量p是父类型的,p不仅可以指向Student对象,还可以指向Teacher类型对象等,但是变量s只能指 Studnet类型对象,及Student子类型对象。变量p能指向对象的范围是比变量s大的。
Object类型的变量o,能指向所有对象,它的范围最大,但是使用变量o能调用到的方法也是最少的,只能调用 到Object中的声明的方法,因为变量o声明的类型就是Object.
注:java中的方法调用,是运行时动态和对象绑定的,不到运行的时候,是不知道到底哪个方法被调用的
2、重写、重载和多态的关系
重载是编译时多态:
调用重载的方法,在编译期间就要确定调用的方法是谁,如果不能确定则编译报错
重写是运行时多态
调用重写的方法,在运行期间才能确定这个方法到底是哪个对象中的。这个取决于调用方法的引用,在运行 期间所指向的对象是谁,这个引用指向哪个对象那么调用的就是哪个对象中的方法。(java中的方法调用, 是运行时动态和对象绑定的)
3、多态的注意事项
1. 多态是方法的多态,属性没有多态性。
2. 编写程序时,如果想调用运行时类型的方法,只能进行类型转换。不然通不过编译器的检查。但是 如果两个没有关联的类进行强制转换,会报:ClassCastException。 比如:本来是狗,我把它转成 猫。就会报这个异常。
3. 多态的存在要有3个必要条件:要有继承,要有方法重写,父类引用指向子类对象
4、多态存在的条件
1. 有继承关系
2. 子类重写父类方法
3. 父类引用指向子类对象
补充一下第二点,既然多态存在必须要有“子类重写父类方法”这一条件,那么以下三种类型的方法是没有办法表现出多态特性的(因为不能被重写):
1. static方法,因为被static修饰的方法是属于类的,而不是属于实例的
2. final方法,因为被final修饰的方法无法被子类重写
3. private方法和protected方法,前者是因为被private修饰的方法对子类不可见,后者是因为尽管被 protected修饰的方法可以被子类见到,也可以被子类重写,但是它是无法被外部所引用的,一个 不能被外部引用的方法,怎么能谈多态呢
5、方法绑定
执行调用方法时,系统根据相关信息,能够执行内存地址中代表该方法的代码。分为静态绑定和动态绑定。
静态绑定:
在编译期完成,可以提高代码执行速度。
动态绑定:
通过对象调用的方法,采用动态绑定机制。这虽然让我们编程灵活,但是降低了代码的执行速度。这也 是JAVA比C/C++速度慢的主要因素之一。JAVA中除了final类、final方、static方法,所有方法都是JVM在 运行期才进行动态绑定的。
多态:如果编译时类型和运行时类型不一致,就会造成多态。
6、instanceof和类型转换
1. instanceof
1 public class Person{ 2 public void run(){} 3 } 4 public class Student extends Person{ 5 } 6 public class Teacher extends Person{ 7 }
1 main: 2 Object o = new Student(); 3 System.out.println(o instanceof Student);//true 4 System.out.println(o instanceof Person);//true 5 System.out.println(o instanceof Object);//true 6 System.out.println(o instanceof Teacher);//false 7 System.out.println(o instanceof String);//false 8 --------------------------- 9 Person o = new Student(); 10 System.out.println(o instanceof Student);//true 11 System.out.println(o instanceof Person);//true 12 System.out.println(o instanceof Object);//true 13 System.out.println(o instanceof Teacher);//false 14 //编译报错 15 System.out.println(o instanceof String); 16 --------------------------- 17 Student o = new Student(); 18 System.out.println(o instanceof Student);//true 19 System.out.println(o instanceof Person);//true 20 System.out.println(o instanceof Object);//true 21 //编译报错 22 System.out.println(o instanceof Teacher); 23 //编译报错 24 System.out.println(o instanceof String);
System.out.println(x instanceof Y);
该代码能否编译通过,主要是看声明变量x的类型和Y是否存在子父类的关系.
有"子父类关"系就编译通过, 没有子父类关系就是编译报错. 之后学习到的接口类型和这个是有点区别的。
—————————————————————————————————————————————————
System.out.println(x instanceof Y);
输出结果是true还是false,主要是看变量x所指向的对象实际类型是不是Y类型的"子类型".
1 main: 2 Object o = new Person(); 3 System.out.println(o instanceof Student);//false 4 System.out.println(o instanceof Person);//true 5 System.out.println(o instanceof Object);//true 6 System.out.println(o instanceof Teacher);//false 7 System.out.println(o instanceof String);//false
2. 类型转换
1 public class Person{ 2 public void run(){} 3 } 4 public class Student extends Person{ 5 public void go(){} 6 } 7 public class Teacher extends Person{ 8 }
【为什么要类型转换】
【总结】
1、父类引用可以指向子类对象,子类引用不能指向父类对象。
2、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转型。
如Father father = new Son();
3、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转型。
如father就是一个指向子类对象的父类引用,把father赋给子类引用son 即Son son =(Son) father;
其中father前面的(Son)必须添加,进行强制转换。
4、upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法,子类方法有效
5、向上转型的作用,减少重复代码,父类为参数,调有时用子类作为参数,就是利用了向上转型。这样 使代码变得简洁。体现了JAVA的抽象编程思想。