8.1 再论向上转型
对象既可以作为本身的类型使用,也可以作为它的基类使用--向上转型。
向上转型可能会缩小接口,但不会比基类的接口更小。
8.2 绑定
8.2.1 方法调用绑定
问题:public void tune(Instrument i){
i.play();
}
编译器如何知道接受的Instrument对象是Wind对象,还是其他子类?
### 8.2.1 方法调用绑定
将一个方法调用和方法主体关联起来被称作绑定。
程序执行前绑定--前期绑定,面向对象过程的语言中默认的方式。
*后期绑定* --在运行时期根据对象的类型进行绑定。 也叫动态绑定和运行时绑定。
编译器不知道对象类型,必须有一种机制在运行时判断对象类型,从而调用恰当的方法。
java中除了static和final方法,其他所有方法都是后期绑定。
8.2.3 产生正确行为
Shape s = new Circle();
s.draw();
调用的不是Shape的draw(),而是通过动态绑定,调用Circle.draw();
8.2.4 缺陷:“覆盖”私有方法
```
import static tools.Print.print;
/**
* Created by YL on 2017/9/20.
*/
public class PrivateOvrride {
private void f(){print("Private f()");}
public static void main(String[] args) {
PrivateOvrride po = new Derived();
po.f();
}
}
class Derived extends PrivateOvrride{
public void f(){
print("public f()");
}
}
output:
private f()
```
PrivateOvrride的f()是private的,默认final,无法被覆盖,所以Derived的f()方法被认作一个全新的方法,覆盖失败。
po调用f()时,因动态绑定去调用Derived中覆盖了基类f()的方法,但Derived的f()方法时一个全新的方法,所以调用基类的f()。
8.2.5 缺陷:域与静态方法
并不是所有事物都是多态发生。只有普通方法调用是多态。
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;}// 子类隐含的super引用。
}
public class FieldAcess {
public static void main(String[] args) {
Super sup = new Sub();
// Sub对象转为Super引用时,如何域访问都将由编译器解析,不是多态。
// Sub实际包含两个称为field的域。自己的和从super继承的。
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());
}
}
//sup.field= 0.sup.getField()=1
//sub.field= 1. sub.getField()= 1.sub.getSuperField()= 0
看起来容易混淆。但在实际中,域通常设置为private,只能通过方法调用。不会对基类的域和导出域赋予相同的名字。
如果某个方法是静态的,它不具有多态性。因为它和类而不是单个对象关联。
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()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
//Base staticGet()
//Derived dynamicGet()
8.3 构造器和多态
构造默认是static的,通常不具有多态。
package Cha3.Ch8;
import static tools.Print.print;
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();
}
}
/*Meal()
Lunch()
PortableLunch
Cheese()
Cheese()
Lettuce()
SandWich()*/
explanation:
1) 首先调用基类构造器,这个步骤会不断反复递归下去,直到根,然后一层一层往回调用。
2)在每个类中,要先初始化变量,再调用构造器。
package Ch8;
import static tools.Print.print;
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(),raidus = "+radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
new Glyph();
}
}
/*Glyph() before draw()
//调用子类的draw,但值未初始化。
RoundGlyph.draw(),raidus = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius=5
Glyph() before draw()
Glyph.draw()
Glyph() after draw()
*/
基类构造方法调用了导出类的方法,但变量值还未初始化。
初始化的实际过程
1)在其他然后事物发生之前,将分配给对象的存储空间初始为二进制的零。
2)如前述那样调用基类构造器。
3)按照声明的顺序调用成员的初始化方法。
4)调用导出类的构造器主体。
8.4 协变返回类型
导出类中的被覆盖方法可以返回基类方法的返回类型的某种导出类型。