文章目录
多态polymorphism 通过 分离what 和 how (即分离了“是什么”和“如何做”),从另一个角度分离接口和实现。多态可改善代码的组织结构和可读性,还能创建 可扩展程序–> 在项目初期和添加新功能时都可以“ 生长”的程序。
封装: 通过合并特征和行为来创建新的数据类型。Implementation hiding(“隐藏实现”通过将细节私有化而将接口和实现分离开)。而多态,则是消除类型之间的耦合关系。
一、向上转型
skipped!
二、动态绑定
2.1 绑定
绑定是指: 将一个方法调用与一个“方法主体”关联起来。(as the original description: “Connecting a method call to a method body is called binding”)。说白了,就是确定是哪个对象(父还是子)来真正调用这个方法。
类似C++这样的语言,会在程序运行前就由编译器和连接器完成了绑定,这被称为“前期绑定”。
而Java采用的动态绑定,即在运行时根据对象的类型进行绑定。Java中除了static 和final方法(private方法是典型的 final方法),其余都是动态绑定。
这里我们理解了:为啥我们要将一个方法声明为final呢?一方面是防止其被覆盖,另一方面是因为能有效“关闭”动态绑定,告诉编译器不需要对其动态绑定,这样,编译器就能为final生成更有效的代码,不过,这点性能的提升,一般不值一提。
Polymorphism是提升代码拓展性的利器,用BruceEckel的话说:多态将“改变的事物”与“未变的事物”分离开的重要技术。
2.2 缺陷: 覆盖private方法
override 只针对非 private 方法,我们一般将子类中要override的方法名和父类的private方法名保持不一致。
public class PrivateOverride {
/*由于 私有 f()方法和 子类的f()方法同名,不能构成override ,
* 因此运行的仍然是 PrivateOverride#f() */
private void f() {
System.out.println("PrivateOverride.f");
}
public static void main(String[] args) {
PrivateOverride derived = new Derived();
derived.f();
}
}
class Derived extends PrivateOverride{
public void f() {
System.out.println("Derived.f");
}
}
2.3 缺陷:域与静态方法
我们需要注意到:
- override只针对非private的普通方法,而 域 和静态方法都是不能构成override的!在下例中,当Sub对象转型为Super引用时,任何域访问都是由编译器解析的,因此不是多态的!下例中,编译器为Super.field和Sub.field 分配了不同的存储空间,Sub实际上有两个被称为field的域:自有的和继承得到的。但是,在引用sub中的field时所产生的默认域并非Super版本的field域。因此为了得到Super.field,还必须显式指定super.field。
- 静态方法的行为是不会构成多态性的。
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
System.out.println(sup.field); //0
System.out.println(sup.getField()); //1
System.out.println("----------");
Sub sub = new Sub();
System.out.println(sub.field); //1
System.out.println(sub.getField()); //1
System.out.println(sub.getSuperField()); //0
}
}
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;
}
}
三、构造器和多态
3.1 构造器调用链
构造器其实是static方法(隐式的),不具备多态性。
基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次主键向上连接,以使每个基类的构造器都能被调用。
看下例中一个复杂类构造的顺序:
- 1、将SandWich中的的 b \c \ l 域初始化 为null
- 2、调用基类的构造器: 即 meal --> lunch --> portableLunch --> Sandwich的构造器
- 3、按声明顺序调成员的初始化方法 :即 Bread --> Cheese --> Lettuce的构造器
- 4、调用导出类构造器 :即 Sandwich 的构造器
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()
*///:~
3.2 构造器内部的多态方法的行为
可以参考参考3.1 中类初始化的说明,
需要特别指出的是:根据阿里的开发规约,我们应尽可能地让一个类简单初始化,禁止在类的构造器中调用其他方法。假如调用,也应该调用private或者final方法。
public class Glyph {
// 甲
void draw() {
System.out.println("Glyph.draw");
}
public Glyph() {
System.out.println(" Glyph before draw");
// 在 构造器 中调用了override 方法
draw();
System.out.println(" Glyph after draw ");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
public RoundGlyph(int radius) {
this.radius = radius;
System.out.println(" RoundGlyph. RoundGlyph() ,radius = " + radius);
}
// 乙
void draw() {
System.out.println(" RoundGlyph.draw + radius " + radius);
}
}
class ConstructorTest{
public static void main(String[] args) {
Glyph roundGlyph = new RoundGlyph(10);
}
}
/**
* 输出:
Glyph before draw
RoundGlyph.draw + radius 0 (注意: 这里调用的不是 甲,而是 乙)
Glyph after draw
RoundGlyph. RoundGlyph() ,radius = 10
**/
四、协变返回类型
协变类型就是说导出类中覆盖基类的方法时可以返回基类中被override方法返回值类型的子类。
五、用继承来设计
继承容易被误用,当我们不确定使用组合还是继承时,使用组合多半能“保底”。使用组合模式,能让你的类层次结构更加灵活,同时组合可以动态选择类型,因此也就动态选择了行为。
看下面这个简单的Demo:
Stage类是一个典型的组合模式,其内部持有了 Actor的引用作为域。
注意 "甲” 这一句:
change()方法将绑定的Actor类型实际给变换了,或者说,Stage对象的状态动态变化了,从而拥有了不同的行为。这其实就是一种简单的状态设计模式。
假如用继承来实现Stage类,那将无法完成这种状态上的轻松变换了。
在这个简单的例子里:
我们得到一个准则:用继承来表达行为的差异,用字段表达状态的变化。这个例子里:通过继承得到两个不同的类HappyActor \ SadActor ,而Stage通过组合使自身状态发生变化。
class Actor {
public void act() {}
}
class HappyActor extends Actor {
public void act() { print("HappyActor"); }
}
class SadActor extends Actor {
public void act() { print("SadActor"); }
}
class Stage {
private Actor actor = new HappyActor();
// 甲
public void change() { actor = new SadActor(); }
public void performPlay() { actor.act(); }
}
public class Transmogrify {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
} /* Output:
HappyActor
SadActor
*///:~
六、总结
skipped!