域与静态方法:
- 只有普通方法的调用可以是多态的,域的访问操作不是多态的。如下例:
//: polymorphism/FieldAccess.java // Direct field access is determined at compile time. 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 *///:~
在上例可以看到,在虽然导出类重新定义了filed属性,但是在向上转型后,输出的仍然是基类的filed。如果想调用基类的field,就必须通过super.field。所以这就说明域不是多态的。其实在实践中,这种状况是不会发生的。首先,你通常会把所有的域都设置成private,因此不能直接访问它们,只能通过调用方法来使用。另外,你可能不会对基类中的域和导出类中的域赋予同样的名字,因为这种做法容易令人混淆。
-
如果某个方法是静态的,它的行为就不具有多态性。如下例:
//: polymorphism/StaticPolymorphism.java // Static methods are not polymorphic. 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()); } } /* Output: Base staticGet() Derived dynamicGet() *///:~
静态方法是与类,而并非是单个的对象相关联的。
构造器与多态:
组合,继承以及多态在构造顺序上是什么样的呢?首先,调用基类构造器,这个步骤会不断的反复递归下去。然后,按声明顺序调用成员的初始化方法。最后,调用导出类构造器主体。具体可看下例:
//: polymorphism/Sandwich.java
// Order of constructor calls.
package polymorphism;
import static net.mindview.util.Print.*;
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()
*///:~
继承和清理:
之前我们说过,创建的时候是先创建基类对象。而清理则相反,应该首先对导出类进行清理,然后才是基类。这是因为导出类的清理可能会调用基类中的某些方法,所以需要使基类中的构件仍起作用。下例可以用来展示构造和清理的顺序:
//: polymorphism/Frog.java
// Cleanup and inheritance.
package polymorphism;
import static net.mindview.util.Print.*;
class Characteristic {
private String s;
Characteristic(String s) {
this.s = s;
print("Creating Characteristic " + s);
}
protected void dispose() {
print("disposing Characteristic " + s);
}
}
class Description {
private String s;
Description(String s) {
this.s = s;
print("Creating Description " + s);
}
protected void dispose() {
print("disposing Description " + s);
}
}
class LivingCreature {
private Characteristic p =
new Characteristic("is alive");
private Description t =
new Description("Basic Living Creature");
LivingCreature() {
print("LivingCreature()");
}
protected void dispose() {
print("LivingCreature dispose");
t.dispose();
p.dispose();
}
}
class Animal extends LivingCreature {
private Characteristic p =
new Characteristic("has heart");
private Description t =
new Description("Animal not Vegetable");
Animal() { print("Animal()"); }
protected void dispose() {
print("Animal dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
class Amphibian extends Animal {
private Characteristic p =
new Characteristic("can live in water");
private Description t =
new Description("Both water and land");
Amphibian() {
print("Amphibian()");
}
protected void dispose() {
print("Amphibian dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
public class Frog extends Amphibian {
private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");
public Frog() { print("Frog()"); }
protected void dispose() {
print("Frog dispose");
t.dispose();
p.dispose();
super.dispose();
}
public static void main(String[] args) {
Frog frog = new Frog();
print("Bye!");
frog.dispose();
}
} /* Output:
Creating Characteristic is alive
Creating Description Basic Living Creature
LivingCreature()
Creating Characteristic has heart
Creating Description Animal not Vegetable
Animal()
Creating Characteristic can live in water
Creating Description Both water and land
Amphibian()
Creating Characteristic Croaks
Creating Description Eats Bugs
Frog()
Bye!
Frog dispose
disposing Description Eats Bugs
disposing Characteristic Croaks
Amphibian dispose
disposing Description Both water and land
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Creature
disposing Characteristic is alive
*///:~
注意构建的时候先构建基类对象,构建对象之前需要先对类中定义的域进行初始化。清理则是先清理导出类。
编写构造器时一条有效的准则:
用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属于final方法),这些方法不能被覆盖。
向下转型与运行时类型识别:
运行期间对类型进行检查的行为称为“运行时类型识别”(RTTI),具体请看下例:
//: polymorphism/RTTI.java
// Downcasting & Runtime type information (RTTI).
// {ThrowsException}
class Useful {
public void f() {}
public void g() {}
}
class MoreUseful extends Useful {
public void f() {}
public void g() {}
public void u() {}
public void v() {}
public void w() {}
}
public class RTTI {
public static void main(String[] args) {
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f();
x[1].g();
// Compile time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown
}
} ///:~
在代码最后两行是将Usefull对象强制向下转型。x[1]由于定义时就是MoreUseful类型对象,所以可以转型成功。而x[0]构建时是Useful类型,所以无法向下转型成功,会抛出ClassCastException异常。RTTI的内容不仅仅包括转型处理。例如它还提供一种方法,使你可以在试图向下转型之前,查看你所要处理的类型。这一部门在第14章会有介绍。