为什么需要多态?
从设计方面考虑:由于现实世界中存在许多泛化的概念,比如经理说员工们去工作,而不是说技术部门的员工开始做技术类工作,销售部门的员工开始做销售的工作。为了使得计算机能够很好的表达这种概念并且能够体现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);
}
}
}