1. 多态概念
一个例子:
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
}
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
public class Wind extends Instrument {
// Redefine interface method:
@Override public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
Wind 是一种 Instrument;因此,Wind 继承 Instrument。
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
*/
tune方法传入了一个Wind类型的引用,tune方法参数的类型的Instrument类型,这种情况属于向上转型,因此不需要做类型转化
如果没有多态的概念,那么当有多种乐器时,我们就需要为每种乐器都编写一个tune方法,非常繁琐,由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 tune() 方法。
class Stringed extends Instrument {
@Override public void play(Note n) {
System.out.println("Stringed.play() " + n);
}
}
class Brass extends Instrument {
@Override public void play(Note n) {
System.out.println("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
}
/* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*/
一个问题: tune方法中的形参类型是Instrument类型, 传入Wind引用时调用的为什么是Wind类中重写的方法?
方法调用绑定:
- 将一个方法调用和一个方法主体关联起来称作绑定
- 若绑定发生在程序运行前(如果有的话,由编译器和链接器实现),叫做前期绑定
前期绑定存在的问题: 以上面的代码为例,编译器只知道Instrument引用类型,具体调用的是Wind的tune方法还是Brass的tune方法,这就不得而知了。
后期绑定:在运行时根据对象的类型进行绑定。后期绑定也称为动态绑定或运行时绑定
Java 中除了 static 和 final 方法(fianl方法不能被子类重写)(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();
}
}
class Derived extends PrivateOverride {
public void f() { System.out.println("public f()"); }
}
/* Output:
private f()
*/
期望输出是 public f(),然而 private 方法可以当作是 final 的,对于派生类来说是隐蔽的。因此,这里 Derived 的 f() 是一个全新的方法,最终调用的是基类的f()
非 private 方法才能被重写,但是得小心重写 private 方法的现象,编译器不报错
属性与静态方法:
- 若父类有一个属性name, 子类也有一个属性name, 进行如下调用
Father f=new Child(); System.out.println(f.name);
此时打印的是父类中的name值 - 静态方法只和类关联:
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
@Override public String dynamicGet() {
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(StaticSuper.staticGet());
System.out.println(sup.dynamicGet());
}
}
/* Output:
Base staticGet()
Derived dynamicGet()
*/
2. 构造器和多态
- 在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用父类的无参构造器。如果父类没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)
构造器内部多态方法的行为:
在父类的构造器内部调用子类重写的方法
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);
}
}
/* Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*/
调用的是子类的draw方法符合预期,但是打印出的radius的值是0而不是1,这一点不符合预期,这一点的原因在于初始化加载的以下顺序:
- 加载类: 给变量分配空间,设置默认值
- 调用基类构造器
- 按照声明的顺序显示给变量赋值(这些赋值的顺序可以看成提取到构造器内部)
- 调用子类的构造器
所以在父类的构造器中调用子类的draw方法时,子类的radius值还没进行显示赋值,因此使用的是默认值
3. 方法重写的原则
- 两同: 方法名相同,形参列表相同
- 两小: 子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常更小或相等
- 一大:子类方法的访问权限应比父类方法的访问权限更大或相等
- 重写的方法要么都是实例方法,要么都是静态方法
- final修饰的方法不能被重写
- private的方法在子类中的“重写”实际上是定义了一个新的方法