在面向对象的程序设计语言中,多态是数据抽象和继承值后的第三种基本特征
封装通过合并特征和行为来创建新的数据类型,实现隐藏则通过将细节私有化把接口和实现分离开,而多态的作用则是消除类型之间的耦合关系。
1 再论向上转型
对象既可以作为它本身的类型使用也可以作为它的父类的类型使用。这种把某个对象的引用视为对其基类的引用的做法被称为向上转型。因为子类继承自父类,具有父类的所有方法,所以父类可以进行的操作子类一定可以进行并且不需要任何的类型转化。
1 忘记对象类型
在进行多态时,我们可以先选择性的忘记对象类型问题,将导出类的对象看作基类的对象来使用,可以减少许多不必要的代码。
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~
这段代码与下边的代码效果基本是相同的
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
} /* Output:
Wind.play() MIDDLE_C
*///:~
这说明:使用多态时可以很大程度的减少代码的数量,免去了为每个导出类都编写一个相关方法代码。
public class Test {
public static void main(String[] args) {
B b = new B();
C c = new C();
D d = new D();
ride(b);
ride(c);
ride(d);
}
public static void ride(A a){
a.x();
}
}
class A{
public void x(){
System.out.println("A.x");
}
}
class B extends A {
public void x(){
System.out.println("B.x");
}
}
class C extends A {
public void x(){
System.out.println("C.x");
}
}
class D extends A {
public void x(){
System.out.println("D.x");
}
} //输出结果B.x C.x D.x
2 转机
编译器是如何在接受一个父类对象的情况下准确的运行对应的子类方法的。
1 方法调用绑定
前期绑定:在程序执行前进行绑定。
而如果使用前期绑定,编译器将不知道调用哪个方法,所以多态使用后期绑定。
后期绑定:在运行时根据对象的类型进行绑定。必须在对象内安置某种“类型信息”。
Java中除了static和final方法外,其他方法都是动态绑定。final方法可以关闭动态绑定。
public class Test {
public static void main(String[] args) {
A b = new B();
b.s();
}
}
class A{
public void s(){this.x();}
public void x(){
System.out.println("A.x");
}
}
class B extends A {
public void x(){
System.out.println("B.x");
}
}
//B.x
public class Test {
public static void main(String[] args) {
Test b = new B();
b.x();
}
private void x(){
System.out.println("A.x");
}
}
class B extends Test {
public void x(){
System.out.println("B.x");
}
}//因为父类的方法是private的,所以无法进行覆盖。向上转型后调用的是父类的方法。
class Super {
public int field = 0;
public int getField() { return field; }
}
class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
} /* Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*///:~
当将Sub转型为Super时,对于域的访问都由编译器解析,因此不是多态的,Sub实际上包含两个field域。
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
如果某个方法是静态的就不具备多态。
class Meal {
Meal() { print("Meal()"); }
}
class Bread {
Bread() { print("Bread()"); }
}
class Cheese {
Cheese() { print("Cheese()"); }
}
class Lettuce {
Lettuce() { print("Lettuce()"); }
}
class Lunch extends Meal {
Lunch() { print("Lunch()"); }
}
class PortableLunch extends Lunch {
PortableLunch() { print("PortableLunch()");}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() { print("Sandwich()"); }
public static void main(String[] args) {
new Sandwich();
}
} /* Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~
构造器的调用:首先在main创建一个对象时,先调用父类的构造器,之后按声明顺序调用成员的初始化方法,最后调用导出类构造器的主体。
协变返回值类型
class Grain {
public String toString() { return "Grain"; }
}
class Wheat extends Grain {
public String toString() { return "Wheat"; }
}
class Mill {
Grain process() { return new Grain(); }
}
class WheatMill extends Mill {
Wheat process() { return new Wheat(); }
}
public class CovariantReturn {
public static void main(String[] args) {
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}
Java se5开始允许覆写版本返回值为原类的导出类。