,运行根据对象类型进行绑定=“后期绑定”。
上面这段话先放一边,看起来有点专业化看不太懂,下面先放上一段代码:
class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
}
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("Brass.play() " + n);
}
}
public class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.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);
}
}
输出结果:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Wind,Stringed,Brass都继承与Instrument ,这种写法更清晰能看到Wind传入Wind并使用自己的方法更容易符合我们人的思想对不对,但是这样写现在是3个导出类(继承Instrument 这三个类),那万一上百个,是不是要写上百个tune()方法是不是不太合理。
那现在我们把改成这样
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
Wind,Stringed,Brass三个类型的参数是不是可以传到上面的这个方法,就不需要写三个方法了。但是这样的话,编译器又不是我们人脑,它怎么知道你传这个参数我到底给你调用那个方法。这就涉及到方法调用绑定。
程序运行前绑定=“前期绑定”这是默认方式,而想解决上面我们那种疑问就需要到“后期绑定”又叫运行时绑定,动态绑定。
运行根据对象类型进行绑定=“后期绑定”
public class Shape {
public void draw() {}
public void erase() {}
} ///:~
public class Circle extends Shape {
public void draw() { print("Circle.draw()"); }
public void erase() { print("Circle.erase()"); }
} ///:~
public class Square extends Shape {
public void draw() { print("Square.draw()"); }
public void erase() { print("Square.erase()"); }
} ///:~
public class Triangle extends Shape {
public void draw() { print("Triangle.draw()"); }
public void erase() { print("Triangle.erase()"); }
} ///:~
public class RandomShapeGenerator {
private Random rand = new Random(47);
public Shape next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
} ///:~
public class Shapes {
private static RandomShapeGenerator gen =
new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[9];
// Fill up the array with shapes:
for(int i = 0; i < s.length; i++)
s[i] = gen.next();
// Make polymorphic method calls:
for(Shape shp : s)
shp.draw();
}
} /* Output:
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
*///:~
像上面这样写RandomShapeGenerator 就是一个工厂分配给你是Circle、Triangle还是Square,这就是工厂模式。
而return处就发生了向上转型,return返回之后就是Sharp s = new Circle/Triangle/Square,而产生向上转型,那么根据后期绑定,就可以执行到不同的draw()方法,也就没了我刚开始那样的Circle/Triangle/Square的draw()方法我要三个,这样就省去一部分的代码对不对。(其实这里我还是没理解后期绑定底层是如何运作的,书中也没写,后续看到我会补充)
而到这里可能大家还是一知半解,就省去一部分代码,多态到底好在哪。
将上面Music2代码改动下
public class Music2 {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
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);
}
乍一看好像没什么,不就是缩减成一个方法了吗。但是看到里面tuneALL()里面用到 for(Instrument i : e) tune(i) 这里Instrument是不是父类类型,所以可以适用于Wind/Stringed/Percussion三种类型,而如果没有后期绑定也就是多态,上面这个方法按照我们最开始那样是不是要写3个tune()方法外加3个for(Wind/Stringed/Percussion i : e) tune(i)加起来6个方法,而且假设我又增加一个类型继承Instrument,我还要用到tuneALL(),我是不是又要改动代码,费时费力又不讨好。那有了多态,你属于Instrument类型,根据后期绑定帮你找到你对应的方法执行,是不是又省去代码,又不用修改代码,不会影响到你其他的代码上!而这才是多态的关键之处。
但是多态还是有缺陷,缺陷一、非private修饰的方法才能被重载。
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()
*///:~
按照这个逻辑在我们的个人想法中,应该输出public f(),但是private修饰的发f()没办法在Derived 中重载,后期绑定无法找到对应的Derived 的方法执行,所以只有输出基类(PrivateOverride )中的发f()方法。
缺陷一、域和静态方法。
运行根据对象类型进行绑定=“后期绑定”,那这两个都是在运行前做好的,肯定没办法多态咯。
构造器与多态(刚开始我的疑问,怎么突然冒出来个构造器,就是因为多态是根据运行时对象来的,构造器可以当做是static来看的,所以在多态之前,我们有必要了解下构造器):
一、顺序(看起来有点懵,其实也是跟之前构造器说的那块,先最底层然后变量定义,到构造器,没啥区别)
二、继承和清理(这里就举个例子,在构造一个对象时其实就是从最底层到最外层对吧,那举个例子,我们穿衣服就是先穿内衣到外套,那我们脱下来也是先外套再内衣对吧,所以在IO的时候我们关闭流,其实也就是这个道理)
垃圾回收让我们省去了释放对象,而当我们要有一些需求时,导出类(继承别的类)重写了基类(被继承类)的清理方法,记得需要super中的清理方法,不然的话,基类就不会清理,这点需要特殊注意。而在此基础上,我们可以根据多态提高的好处,在清理时设置static修饰变量,因为static上面说了是跟类级别,而多态是根据对象运行时,那就可以用来计数防止溢出(这个看起来有点难懂配合书中的代码看吧,)。
三、构造器内部的多态方法的行为(这里根据下面代码解释下,刚开始我也有点懵。我们知道多态是在运行根据对象类型进行绑定,那当你启动下面这个程序时,按照我们之前说的,那就是先执行基类的变量定义,到构造器,这时候还没有执行到main里面的new RoundGlyph(5),没有触发多态,所以这时候编译器会给变量先赋值二进制的0,对象就是null。所以我们正常的理解应该只有输出RoundGlyph.RoundGlyph(), radius = 5,但是多了一句RoundGlyph.draw(), radius = 0,理解这个可以对我们有时候有些莫名其妙的问题有一点危机意识)
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
*///:~
协议返回类型WheatMill重写process方法返回值Wheat类型继承Grain所以可以这样写,也是重写,java5之前不行。
基类与导出类拥有完全相同的接口方法称为纯继承,而继承往往是导出类“is-like-a”像一个基类,是基类的扩展,导出类有基类的所有非private方法,我们在上面可以知道导出类是可以向上转型,而向下转型则需要强转并且转型成功才可以调用导出类的方法,否则就报错ClassCastExceotion。
最后总结(再次偷懒):
.