第八章:多态

第八章:多态

基类 base = new 子类 ==> base引用指向子类对象,时基类类型

class Instrument {
  public void play(Note n) {
    print("Instrument.play()");
  }
}

public class Wind extends Instrument {
  // Redefine interface method:
  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
*///:~
一、转机

如上例子,多态情况下编译器如何知道调用哪个方法?

Instrument引用调用,通过后期绑定实现,利用方法调用机制找到正确的方法。

1、方法绑定

将一个方法调用同方法主体关联起来叫做绑定

前期绑定:①程序执行前绑定(编译器和连接器实现) ②private(类私有)、static(随类加载)、final(关闭动态绑定)修饰的方法属于前期绑定,其他属于后期绑定,它自动发生。

后期绑定(动态绑定):①根据某种方法调用机制,判断对象的类型执行相应的方法;编译器不知道对象的类型,方法调用机制找到正确的方法

class A {
	protected void f() {  //调用第二个方法
		System.out.println("A.f()"); 
		this.g(); 
	}
	protected void g() {
		System.out.println("A.g()"); 
	}
}

class B extends A {
	@Override protected void g() {
		System.out.println("B.g()");	//子类覆盖父类方法
	}
}

public class Ex10 {
	public static void main(String[] args) {
		B b = new B();
		//自动升级为基类以调用基类方法A.f()
		//通过多态性,它将调用派生类方法B.g():
		b.f();
	}
}
结果:
A.f()
B.g()

2、扩展性
3、缺陷,覆盖私有方法:private

基类私有方法private修饰(可认为final修饰,不可覆盖,也有隐士的final意识),对于导出类是屏蔽的。所以子类的public同名方法是新方法并不是覆盖

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()
*///:~
4、缺陷:域或静态方法

静态是随着类的加载,与类相关联,不具有多态性;而多态是动态绑定

class Super {
  public int field = 0;//相同名字的成员变量,在各自对象的域中
  public int getField() { return field; }
  public static String dynamicGet() {
    return "Super dynamicGet()";
  }
}

class Sub extends Super {
  public int field = 1; //相同名字的成员变量,在各自对象的域中
  public int getField() { return field; }
  public int getSuperField() { return super.field; }
  public static String dynamicGet() {
    return "Sub dynamicGet()";
  }
}

public class FieldAccess {
  public static void main(String[] args) {
    Super sup = new Sub(); // Upcast 向上转型
    System.out.println(sup.dynamicGet()); //  静态方法,调用基类中
    System.out.println("sup.field = " + sup.field +", sup.getField() = " + sup.getField());
    Sub sub = new Sub();
    System.out.println(sub.dynamicGet()); //  静态方法,调用本类
    System.out.println("sub.field = " +sub.field + ", sub.getField() = " +
      sub.getField() +", sub.getSuperField() = " +sub.getSuperField());
      
  }
} /* Output:
Super dynamicGet()
sup.field = 0, sup.getField() = 1
Sub dynamicGet()
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
*///:~

二、构造器与多态
1、构造器的调用顺序

1、导出类按照继承体系树向上链接,找到基类,然后逆序初始化构造直到导出类构造,这样也是为了保证导出类调用基类的数据完整性;

2、导出类构造中有句默认的super(); ,如果基类中没有默认的构造将会报错

3、构造其实是一个static方法,只不过是隐式的

class Meal {
  private static String s = "Meal_test成员变量";
  Meal() { print("Meal()构造方法"+s); }
  static{
    System.out.println("Meal()_静态代码块"+s);
  }
  {
    System.out.println("Meal()_构造代码块"+s);
  }
}

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();
  }
} /* Output:
Meal()_静态代码块Meal_test成员变量
Meal()_构造代码块Meal_test成员变量
Meal()构造方法Meal_test成员变量
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
*///:~
总结:执行顺序是先静态域、静态代码块、基类成员变量初始化、构造代码块、基类构造初始化、子类成员变量初始化、构造代码块、子类构造初始化
2、继承与清理

引用计数 清理垃圾

class Shared {
  private int refcount = 0;  // refcount=5,由于同一对象成员变量调用自增方法
  private static long counter = 0;//追中Shared实例数量
  private final long id = counter++; //counter=0,使用final修饰,不希望生命周期内改变
  public Shared() {
    print("Creating " + this);
  }
  public void addRef() { refcount++; }//追踪引用数,清理时方便知道没有引用
  protected void dispose() {
    if(--refcount == 0)
      print("Disposing " + this);
  }
  public String toString() { return "Shared " + id; }
}

class Composing {
  private Shared shared;
  private static long counter = 0;
  private final long id = counter++;
  public Composing(Shared shared) {
    print("Creating " + this);
    this.shared = shared;
    this.shared.addRef();
  }
  protected void dispose() {
    print("disposing " + this);
    shared.dispose();
  }
  public String toString() { return "Composing " + id; }
}

public class ReferenceCounting {
  public static void main(String[] args) {
    Shared shared = new Shared(); // refcount=5,由于同一对象成员变量调用自增方法
    Composing[] composing = { new Composing(shared),
      new Composing(shared), new Composing(shared),
      new Composing(shared), new Composing(shared) };
    for(Composing c : composing)	//按照数组顺序输出,所以id是从0开始
      c.dispose();
  }
} /* Output:
Creating Shared 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
Disposing Shared 0
*///:~

3、构造器内部的多态行为

基类构造器内部调用一个动态绑定的方法(被覆盖),导致导出类未初始化,出现意外

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
*///:~

三、关于继承和组合设计

1、如果向上转型使用继承,不然使用组合

2、由于继承的多态性,需要动态确定类型是基类还是导出类,方法调用机制才好正确调用方法。(动态绑定)

1、类型转换

编译时父类有该方法,才能调用子类覆盖的方法,不然编译时异常

类型转换必须确定类型(instanceof),不然出现类型转换异常(ClassCastException)

class Useful {
  public void f() {}
  public void g() {}
}

class MoreUseful extends Useful {
  public void f() {}
  public void g() {}
  public void u() {}
  public void v() {}
  public void w() {}
}	

public class RTTI {
  public static void main(String[] args) {
    Useful[] x = {
      new Useful(),
      new MoreUseful()
    };
    x[0].f();
    x[1].g();
    // Compile time: method not found in Useful:  编译时错误
    //! x[1].u();
    ((MoreUseful)x[1]).u(); // Downcast/RTTI  没有确定类型就强制转换,类型转换异常
    ((MoreUseful)x[0]).u(); // Exception thrown
  }
} ///:~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值