多态
陷阱:重写私有方法
因为private方法相当于final方法,是不能被重写的,若像以下代码那样重写:
public class PrivateOverride {
private void f() {
System.out.println("private f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
public Derived extends PrivateOverride {
public void f() {
System.out.println("public f()");
}
}
输出:
private f()
因为private方法不能被重写,Derived中的 f( ) 相当于重新声明了一个叫做 f 的方法,而 PrivateOverride po = new Derived();这种向上转型中的多态只会作用于可以被重写的方法上,所以调用的是父类的 f( ) 方法
陷阱:向上转型中的属性访问
class Super {
public int field = 0;
public int getField() {
return field;
}
}
class Sub extends Super {
public int field = 1;
@Override
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())
}
}
输出
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
可以看到,向上转型的对象在访问属性时,不会出现多态的现象,实际上会通过子类和父类不同的字段名来避免这种错误。
类构造器
对象初始化顺序
- 在分配内存的阶段,先为变量初始化默认值
- 之后先初始化最顶层父类,依次往下
- 每初始化一个类的对象,先按照声明顺序初始化变量,在调用构造函数
构造器内部调用对象方法
在构造器中调用对象方法,因为有多态的存在,可能会出现神奇bug,见例子代码。
class Glyph {
void draw() {
System.out.println("Glyph.draw()");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}
@Override
void draw() {
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
输出:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
可以看到,虽然在父类构造函数调用了draw方法,且符合多态结果(动态绑定到子类),但打印出的 radius 确实 0,这是因为在调用draw方法时,radius在子类中还没有被初始化为1,只有自己的默认值0。
尽量不要在构造函数里调用非final方法(或private)。
协变返回类型
class Grain {
@Override
public String toString() {
return "Grain";
}
}
class Wheat extends Grain {
@Override
public String toString() {
return "Wheat";
}
}
class Mill {
Grain process() {
return new Grain();
}
}
class WheatMill extends Mill {
@Override
Wheat process() {
return new Wheat();
}
}
见代码,Java5之后,被重写的方法的返回类型可以被改变成原返回类型的子类(16行和23行),这种叫协变返回类型。
向下转型
当把一个父类强制转型为子类时,若这个对象是父类引用子类对象,则可以正常执行新的自类扩展方法,因为java会在运行时检查类型(被称作运行时类型信息)。若这个对象是父类引用父类的对象,则调用子类方法会直接抛运行时异常。
class Useful {
public void f() {
System.out.println("userful f");
}
public void g() {
System.out.println("userful g");
}
}
class MoreUseful extends Useful {
@Override
public void f() {
System.out.println("more f");
}
@Override
public void g() {
System.out.println("more g");
}
public void u() {
System.out.println("more u");
}
}
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
}
}
输出:
userful f
more g
more u
可以看到,x[0]不能向下转型,x[1]可以正确的向下转型