多态
多态分离了做什么和怎么做,从另外一个角度将接口和实现分离开了。
方法调用绑定
将一个方法调用和一个方法主体关联起来被称之为绑定。如果在程序执行之前进行绑定(编译器和连接程序实现),叫做前期绑定。而在运行时根据对象的类型进行绑定叫做后期绑定(也叫动态绑定或运行时绑定)。编译器是不知道对象的具体类型的,但是方法调用机制能找到正确的方法体进行调用。而这种机制的实现依赖于在对象中安置“类型信息”。
在Java里面除了static和final方法以外,都是后期绑定。方法声明为final与其说是禁止别人覆写该方法,还不如说是关闭了动态绑定。
域和静态方法里面的多态
对于静态方法由于是和类对象相关联的,并不是和单个对象相关联,所以不会产生多态。
如果直接访问某个域,这个访问会将在编译器进行解析。例如:
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
// 在编译器就直接拿到了域里面的值,而调用方法会产生多态
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());
}
}
class Super {
public int field = 0;
public int getField() {
return field;
}
}
class Sub extends Super {
// 这里的Sub对象其实含有两个field域,所以必须显式
// 使用Super.field才能访问父类的域
public int field = 1;
public int getField() {
return field;
}
public int getSuperField() {
return super.field;
}
}
/*output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*/
构造器和多态
实际上构造器是static方法,不过是隐式的声明,并不具有多态性。
对于一个复杂对象调用构造器会遵循下面的顺序:
- 调用父类的构造器,一直向上
- 按照声明顺序调用成员的初始化方法,如果没有创建对象就没有
- 调用该类构造器主体
协变返回类型
在子类的重写方法中可以返回父类方法的返回类型的某种导出类型(子类)
继承设计通用准则
使用继承来表达行为间的差异,并用字段表达状态上的变化,字段使用组合。即包含的字段指向一个父类,通过改变这个引用指向不同的子类对象来改变组合对象的行为模式。代码如下:
public class InheritanceDesign {
public static void main(String[] args) {
EA aEa = new EA();
aEa.func2();
aEa.change(new B());
aEa.func2();
}
}
class A {
void func() {System.out.println("A");}
}
class B extends A {
@Override
void func() {System.out.println("B");}
}
class C extends A {
@Override
void func() {System.out.println("C");}
}
class D extends A {
@Override
void func() {System.out.println("D");}
}
class EA {
// 组合
A a = new A();
// 改变父类的指向,传入不同的子类对象
// 从而实现行为的变化
void change(A a) {
this.a = a;
}
void func2() {
a.func();
}
}
向下转型和运行时类型识别
向上转型–丢失类型信息(安全,基于is-a)
向下转型–获取类型信息(不安全,父类不知道实际指向的是哪种子类对象)
基于上文,Java在进行类型转换的时候会有一个“运行时类型识别(RTTI)”机制。