为什么须要多态?
从设计方面考虑:因为现实世界中存在很多泛化的概念,比方经理说员工们去工作。而不是说技术部门的员工開始做技术类工作,销售部门的员工開始做销售的工作。为了使得计算机可以非常好的表达这样的概念而且可以体现oop的思想(以问题来解决这个问题),引入多态以及继承的概念,通过方法重载和重写以及向上转型来表达这样的现实世界中的问题。
从开发方面考虑:假设让计算机真的去具体的表述技术部门的员工開始做技术类工作,销售部门的员工開始做销售的工作。
必定类型之间耦合度(依赖性)大,这将导致代码的组织方式过于繁琐。维护成本高。
而且当得到扩展时也极其不方便。
什么是多态?
多态是一种设计代码的思想,一般与继承结合使用。
多态描写叙述了一种核心概念的多种不同的表现形态。又分为编译时多态和执行时多态,对于编译时多态变现有重载。是对一种行为的多种不同表现方式。执行时多态变如今“新类是现有类的一种类型”这层关系上,通过将基类的引用指向导出类的对象以重写的方式来实现。
深入理解多态:
一、编译时多态(重载):在编译的时候编译器可以将函数名和參数名不同的重载方法在编译后通过不同的函数名区分开来。在调用时依据限制类型实现前期绑定还是后期绑定。
二、执行时多态(向上类型转型):在编译的时候,编译器并不知道也不去检查引用指向的那个类型,他只知道这个引用是什么类型。所以检查使用的方法是否存在以及调用是否正确不过通过这个基类中的方法来检查。假设这种方法能够是导出类接口的一部分。那么就会执行后期绑定,假设这种方法是private 或者static 那么运行前期绑定。特别的,对于这个引用訪问基类中某些特别的域那一定是前期绑定。
当方法运行后期绑定时,jvm会依据对象中安置的“类型信息”通过方法调用机制找到正确的方法并绑定调用。
三、在基类方法中隐式的使用多态:
class Parent{ Parent(){ } public void eat(){ play(); } public void play(){ System.out.println("Parent.play()"); } } class Child extends Parent{ public void play(){ System.out.println("Child.play()"); } } public class Demo{ public static void main(String[] args) { Parent c = new Child(); c.eat(); } }
c.eat();依照二方式确定调用那个方法之后,事实上编译器为eat方法隐士的传入了一个this指针指向了new Child(),可是this类型为Parent(由于调用了Parent的eat()),所以这里有一个隐式 的向上类型转化。在eat()内部有一个play()调用,编译器知道这是this.paly()的形式 ,要去推断这种方法该调用谁的play()。
多态有哪些优点?
1、多态消除了类型之间的耦合关系。使得只与核心概念基类相关联。
2、多态具有非常强的可扩展性。能够随时加入新的导出类。
3、多态改善代码的组织结构和可读性使得改变事物与不改变事物分离。
多态有什么缺点或者缺陷?
1、导出类中接口的扩展部分不能被基类訪问。向上转型导致某些方法的丢失。
2、基类私有方法得不到重写,所以仅仅能调用基类的这种方法。
3、域与静态方法不支持多态。
4、继承与多态使用,将类的调用卷入到继承的层次结构(耦合度)中去。在基类中使用了导出类的方法,可是这个导出类域没有初始值。
(具体见下一篇。继承与多态的初始化和清除)
实例:
class SuperParent { SuperParent(){ System.out.println("SuperParent:---> "); this.f(); } void f(){ System.out.println("SuperParent"); } } class Parent extends SuperParent{ Parent(){ System.out.println("Parent:--->"); this.f(); super.f(); } void f(){ System.out.println("Parent"); } } class Child extends Parent{ public Child() { System.out.println("Child:--->"); this.f(); super.f(); } void f(){ System.out.println("Child"); } } public class Demo{ public static void main(String[] args) { Child c = new Child(); } }
/*Output:
SuperParent:--->
Child
Parent:--->
Child
SuperParent
Child:--->
Child
Parent
*/
编译器编译SuperParent时,遇到this.f()调用。此时f函数是一个一般的方法,所以y这个this应该是invokevirtual,所以应该是在后期绑定的时候由jvm推断究竟调用那个f方法。
接着编译Parent方法。同理处理构造器中this.f( )。
可是对于super来说。这个对于编译器是确定的就是其基类,不会待执行的时候有什么后期绑定,所以应该是invokespecial的。同理分析Child。
多态的应用:
1. 多态用于形參类型的时候,能够接收很多其它类型的数据 。
避免了因基类导出的多个子类在调用各自的某一方法时要多写代码。更能体现出继承的关系,同一时候利用向上转型能够使得多个子类在调用方法功能同样行为不同的方法出现冗余,同一时候再加入新类。也变得灵活。提高的可扩展性。
2. 多态用于返回值类型的时候,能够返回很多其它类型的数据。
//图形类 abstract class MyShape{ public abstract void getArea(); public abstract void getLength(); } class Circle extends MyShape{ public static final double PI = 3.14; double r; public Circle(double r){ this.r =r ; } public void getArea(){ System.out.println("圆形的面积:"+ PI*r*r); } public void getLength(){ System.out.println("圆形的周长:"+ 2*PI*r); } } class Rect extends MyShape{ int width; int height; public Rect(int width , int height){ this.width = width; this.height = height; } public void getArea(){ System.out.println("矩形的面积:"+ width*height); } public void getLength(){ System.out.println("矩形的周长:"+ 2*(width+height)); } } class Demo12 { public static void main(String[] args) { /* //System.out.println("Hello World!"); Circle c = new Circle(4.0); print(c); Rect r = new Rect(3,4); print(r); */ MyShape m = getShape(0); //调用了使用多态的方法。定义的变量类型要与返回值类型一致。 m.getArea(); m.getLength(); } //需求1: 定义一个函数能够接收随意类型的图形对象,而且打印图形面积与周长。public static void print(MyShape s){ // MyShpe s = new Circle(4.0); s.getArea(); s.getLength(); } // 需求2: 定义一个函数能够返回随意类型的图形对象。
public static MyShape getShape(int i){ if (i==0){ return new Circle(4.0); }else{ return new Rect(3,4); } } }