Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java基础(六)之多态

2017-06-10
nivelle

多态

指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。

多态通过分离做什么和怎么做,从另一个角度角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可拓展的程序———既无论在项目最初创建还是在需要添加新功能时都可以“生长”的程序。

多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一个基类导出而来的。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都可以通过同一个基类来调用。

多态存在的三个必要条件

  • 要有继承:在多态中必须存在有继承关系的子类和父类。

  • 要有重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

  • 父类引用指向子类对象:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。


多态的好处:

  1. 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
  2. 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
  3. 接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。如图8.3 所示。图中超类Shape规定了两个实现多态的接口方法,computeArea()以及computeVolume()。子类,如Circle和Sphere为了实现多态,完善或者覆盖这两个接口方法。
  4. 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
  5. 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。

Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。

方法调用绑定

将一个方法调用同一个方法主体关联起来被称为绑定。若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),前期绑定,只有一个基类引用,它无法知道调用那个方法才对。

后期绑定:动态绑定或运行绑定,在运行时能判断对象的类型,从而调用恰当的方法,也就是说,编译器一直不知道 对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随着编程语言的不同而有所不同,不管怎样必须在对象中安置某种“类型信息”。

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

对于面向对象,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编译之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

产生正确的行为

java中所有的方法都是通过动态绑定实现多态,所以我们可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。

多态使得我们所做的代码修改,不会对程序中其他不应该受到影响的部分产生破坏。换句话说:多态让“将改变的事物与未改变的事物分离开来”。

缺陷一:“覆盖”私有方法

public class PrivateOverride {
  private void f() {
    System.out.println("private f()");
  }

  public static void main(String[] args) {
    PrivateOverride po = new Derived();
    po.f();
  }
}

class Derived extends PrivateOverride {
  public void f() {
    System.out.println("public f()");
  }
}


结果:private f()

由于private方法被自动认为是final方法,而且对导出类屏蔽的。因此,在这种情况下,Drivied类中的f()方法就是一个全新的方法;既然基类中的f()方法在子类Drived中不可见,因此甚至也不能重载。

结论就是:只有非private方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切地说,在导出类中,对于基类中的private方法,最好采用不同的名字。

缺陷二:域与静态方法

只有普通的方法调用可以是多态的,如果某个方法是静态的,它的行为就不具有多态性。静态方法是与类,而并非与单个的对象相关联的。

构造器和多态

构造器不具有多态性(它们实际上是static方法,只不过该static声明式隐式的),但还是非常非常有必要理解构造器怎样通过多态在复杂的层次结构中运作。

构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上连接,以是每个基类的构造器都能得到调用。

导出类只能访问自己的成员,不能访问基类中的成员(基类成员通常是private类型),只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。


构造器调用要遵循下面的顺序:

  • 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类。
  • 按照声明顺序调用成员的初始化方法。
  • 调用导出类构造器的主体。

构造器内部的多态方法的行为

public class PolyConstructors {
public static void main(String[]args){
    new RoundGlyph(5);
  }
}

class Glyph{
  void draw(){System.out.println("glyph.draw()");}
  public Glyph() {
    System.out.println("glyph() before draw()");
    draw();
    System.out.println("glyph() after draw()");
  }
}

class RoundGlyph extends Glyph{
  private int radius =1;
  public RoundGlyph(int r) {
    radius = r;
    System.out.println("RoundGlyph.RoundGlyph(),radius="+radius);
  }
  void draw(){
    System.out.println("RoundGlyph.draw(),radius="+radius);  
  }
}


如果要调用构造器内部的一个动态绑定方法,就要用到那个方法被覆盖后的定义,然而这个调用的效果可能相当难于预料,因为被覆盖的方法在对象完全构造之前就会被调用。

初始化的实际过程是:

  • 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零
  • 如前所述那样调用基类的构造器。,此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用,由于步骤一的缘故,我们此时会发现radis的值为0)
  • 按照声明的顺序调用成员的初始化方法。
  • 调用导出类的构造器主体

协变返回类型

它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。

用继承进行设计

通用准则:用继承表达行为间的差异,并用字段表达状态上的变化。

向下转型与运行是类型识别

java语言中,所有转型都会得到检查,所以即使我们只是进行一次普通的加括弧形式的类型转换,在进入运行期时仍然会对其进行检查,以确保证它的确是我们希望的那种类型。如果不是,就会返回一个ClassCastException(类转型异常)。


评论