- 多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。
- 封装通过合并特征和行为来创建新的数据类型。实现隐藏则通过将细节私有化把接口和实现分离开来。
1、再论向上转型
- 把对某个对象的引用视为对其基类的引用的做法称为向上转型。
Wind继承Instrument,Wind的对象可以用Instrument表示,因为Wind对象一定有所有Instrument里的成员与方法,但是Wind个性化的方法不能再这种引用表示中使用。
2、转机
方法调用绑定
- 将一个方法调用同一个方法主体关联起来被称作绑定。
- 后期绑定是运行时根据对象的类型进行绑定。
- Java中除了static方法和final方法,其他方法都是后期绑定。
- 在编译时,编译器不需要获得任何特殊信息就能进行正确的调用。
可扩展性
- 在一个设计良好的OOP程序中,大多数方法只与基类接口通信,程序是可扩展的,因为可以从通用的基类继承出新的数据类型。
- 多态是一项 将改变的事务与未变的事务分离开来。
缺陷:覆盖私有方法
- 其实没有覆盖,子类不能覆盖私有方法,这个f是个新方法。
public class PrivateOverride {
private void f() { print("private f()"); }
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() { print("public f()"); }
} /* Output:
private f()
*///:~
缺陷:域与静态方法
- 这里的sup.field是0,Sub实际上包含两个field域,一个是自己的,一个是继承的。
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
*///:~
3、构造器与多态
构造器的调用顺序
- 基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上连接,以使每个基类的构造都得到调用。
- 只有基类的构造器知道如何对自己的元素进行合理初始化
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()
*///:~
- 调用基类构造器。
- 按声明顺序调用成员的初始化方法。
- 调用导出类的构造器主体。
class Glyph {
void draw() { print("Glyph.draw()"); }
Glyph() {
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
print("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
print("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
} /* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*///:~
- 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
- 调用基类构造器。上例中radius有默认值,但是没初始化。
- 按照声明的顺序调用成员的初始化方法。
- 调用导出类的构造器主题。
- 用尽可能简单的方法使对象进入正常状态,如果可以,避免调用其他方法。在构造器内唯一能够安全调用的那些方法是基类中的final方法。
4、用继承进行设计
- 组合更加灵活,因为它可以动态选择类型。相反,继承在编译时就需要知道确切类型。
- 用继承表达行为间差异,并用字段表达状态上的变化。
- Java中,所有转型都会得到检查。保障向下转型的正确性。
网友评论