- 最近编程时候,遇见了一个问题,当时怎么都想不通。最后,谁知是多态。所以要静心研究下多态。
多态的定义
- 多态一般分为两种:
- 编译时的多态:
- 方法的重载
- 运行时的多态:
- 继承
- 实现接口
- 编译时的多态:
- 我们所说的多态一般是运行时的多态。
- 要使用多态,在声明对象时就应该遵循一条法则:声明的总是父类类型或接口类型,创建的是实际类型。
- 面向对象的三大特征: 封装 继承 多态
- 多态通过分离 做什么 和 怎么做 ,从另外的一个角度将接口和实现分离开来。
- 封装通过合并特征和行为来创建新的数据类型。“实现隐藏”则是通过将细节“私有化”把接口和实现分离开。
- 多态的作用是消除类型之间的耦合关系。
- 多态也叫作动态绑定,后期绑定,运行时绑定。
- 首先是一个多态的例子:
class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
}
public class Wind extends Instrument {
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
public class Music {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute); // Upcasting
}
}
// Output:
//Wind.play() MIDDLE_C
方法调用绑定
将一个方法同一个方法主体关联起来叫做 绑定。如果程序执行前进行绑定的话,叫做前期绑定。(这一般是面向过程语言的绑定过程)
后期绑定:运行时根据对象的类型进行绑定。
- 后期绑定在不同的语言中事先有所不同,但是想象一下就会得知,不知怎样必须在对象中安置某种类型信息。
- java中出了 final 和 static 方法之外,所有的方法都是后期绑定。
- 声明为final方法的好处:防止其他人覆盖该方法。但是更重要的一点是:这样做可以有效地关闭动态绑定。
多态的“缺陷”
- private方法被自动的认为是final方法,对于导出类是屏蔽的。
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()
- 只有普通的方法调用可以是多态的。域(变量)和静态的方法也不具有多态性质。
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
*///:~
构造器和多态
- 对于导出类的构造器,在第一行必须调用 基类 的构造器,否则编译器会报错。当然,如果基类有无参的构造器,则可以不用显式的调用,因为编译器会帮助我们隐式调用。
- 首先将分配给对象的空间初始化为二进制的0
- 按声明顺序调用成员的初始化方法
- 然后调用该类的构造器
- 根据以上规则,当我们手动写dispose()方法的时候,我们销毁的顺序应该是和初始化的顺序是相反的,因为这样可以避免某个子类对象依赖其他的对象。
- 如果遇到类之间有共享变量的情况,那么销毁的顺序就变得复杂了,一般采用计数器的方法,记录着引用的个数。
- 在构造器内唯一安全调用的那些方法是final方法,也适用于private方法,因为他们自动属于final方法。
协变返回类型
- 协变返回类型就是如果方法要求返回一个对象,则我们可以返回这个对象的子类。
向下转型
- 向上转型我们会丢失具体的类型信息
- 在java中,所有的转型都会得到检查。这种运行期间对类型进行检查的行为称作“运行时的类型识别”。