多态 一

再论向上转型

(1)多态:也称作动态绑定,后期绑定,和运行时绑定
(2)忘记对象类型:再添加新的导出类时,则需要更多的代码,忘记对象类型,则编写的代码只与基类打交道。

(一)转机

编译器如何知道基类引用指向哪一个子类对象呢,实际上,编译器无法得知,需要理解绑定 这个话题。

1.方法调用绑定

(1)定义:将一个方法调用同一个方法主体关联起来称作绑定。
(2)分类:

  • 前期绑定:若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。(例如:C)
  • 后期绑定:在运行时根据对象的类型进行绑定。
  • 如果一种语言想要实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型。也就是说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。即:不管怎样,都必须在对象中安置某种“类型信息”。
  • Java中,除了static和final(private属于final方法)方法之外,其他所有的方法都是后期绑定,即:不需判定是否要后期绑定,它会自动发生。
  • 将方法声明为final的原因:1.防止其他人覆盖该方法(2)有效“关闭”动态绑定,告诉编译器不需要对其进行动态绑定。但对整体性能没有什么改观。

2.产生正确的行为

注意:不仅适用于图形化程序设计

import java.util.Random;

class Shape {  //建立了一个公用接口:具备描绘和擦除的功能。导出类覆盖,实现单独行为。
	public void draw() {
	}

	public void erass() {
	}
}

class Circle extends Shape {
	public void draw() {
		System.out.println("draw circle");
	}

	public void erase() {
		System.out.println(" erase circle");
	}
}

class Square extends Shape {
	public void draw() {
		System.out.println("draw square");
	}

	public void erase() {
		System.out.println("erase square");
	}
}

class Triangle extends Shape {
	public void draw() {
		System.out.println("draw Triangle");
	}

	public void erase() {
		System.out.println("erase Triangle");
	}
}

// A "factory" that randomly creates shapes
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();
		}
		for (Shape shp : s) {  //对draw()方法的调用都是通过动态绑定进行的。
			shp.draw();
		}
	}
}


结果为:

draw Triangle
draw Triangle
draw square
draw Triangle
draw square
draw Triangle
draw square
draw Triangle
draw circle

3. 可扩展性

(1)可以对系统添加任意多的类型,而只与接口通信,这样的接口是可扩展的。
(2)我们所做的代码修改,不会对程序中其他不应受到影响的部分产生破坏。即:多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

enum Note {
	ONE, TWO, THREE, FOUR;
}

class Intrucment {
	void play(Note n) {
		System.out.println("Intrucment");
	}

	String what() {
		return "Intrucment";
	}

	void adjust() {
		System.out.println("Intrucment xx");
	}
}

class Wind extends Intrucment {
	void play(Note n) {
		System.out.println(" Wind");
	}

	String what() {
		return " Wind";
	}

	void adjust() {
		System.out.println(" Wind xx");
	}
}

class Perussion extends Intrucment {
	void play(Note n) {
		System.out.println("Perussion");
	}

	String what() {
		return "Perussion";
	}

	void adjust() {
		System.out.println("Perussion xx");
	}
}

class Stringed extends Intrucment {
	void play(Note n) {
		System.out.println("Stringed");
	}

	String what() {
		return "Stringed";
	}

	void adjust() {
		System.out.println("Stringed xx");
	}
}

class Brass extends Wind {
	void play(Note n) {
		System.out.println(" Brass");
	}

	void adjust() {
		System.out.println(" Brass xx");
	}
}

class Woodwind extends Wind {
	void play(Note n) {
		System.out.println(" Woodwind");
	}

	String what() {
		return " Woodwind";
	}
}

public class Music3 {
	public static void tune(Intrucment i) {
		i.play(Note.ONE);
	}

	public static void tuneAll(Intrucment[] e) {
		for (Intrucment i : e) {
			i.play(Note.TWO);
		}
	}

	public static void main(String[] args) {
		Intrucment[] orchestra = { new Wind(), new Perussion(), new Stringed(), new Brass() };
		tuneAll(orchestra);

	}

}

结果为:

Wind
Perussion
Stringed
 Brass

4. 缺陷:“覆盖”私有方法

如下:

public class PrivateOverride {
	private void f() {// private 方法默认是final方法,对子类屏蔽
		System.out.println("private f()");
	}

	public static void main(String[] args) {
		PrivateOverride po = new Derived();
		po.f();
	}
}

class Derived extends PrivateOverride {
	public void f() { // 导出类中,对于基类中的private方法,最好采用不同的名字
		System.out.println("public f()");
	}
}

结果为:

private f()

注意:

  • private 方法默认是final方法,对子类屏蔽,因此没有被重载。
  • 结论:只有非private方法才可以被覆盖。
  • 导出类中,对于基类中的private方法,最好采用不同的名字。

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;
	}
}

public class FieldAccess {
	public static void main(String[] args) {
		Super sup = new Sub();// 向上转型、任何域访问操作都将由编译器解析,因此不是多态的。

		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 dynamcGet() {
		return "Base **";
	}
}

class StaticSub extends StaticSuper {
	public static String staticGet() {
		return "Derived staticGet()";
	}

	public String dynamcGet() {
		return "Derived **";
	}
}

public class StaticPolymorism {
	public static void main(String[] args) {
		StaticSuper sup = new StaticSub();
		System.out.println(sup.dynamcGet());
		System.out.println(sup.staticGet());
	}
}

结果为:

Derived **
Base staticGet()

(二)构造器和多态

构造器不具备多态性。但有必要理解构造器怎样通过多态在复杂的层次结构中运作的。

1.构造器的调用顺序

class Meal {
	Meal() {
		System.out.println("meal()");
	}
}

class Bread {
	Bread() {
		System.out.println("bread()");
	}
}

class Cheese {
	Cheese() {
		System.out.println("Cheese()");
	}
}

class Lettuce {
	Lettuce() {
		System.out.println("Lettuce");
	}
}

class Lunch {
	Lunch() {
		System.out.println("Lunch()");
	}
}

class PortableLunch extends Lunch {
	PortableLunch() {
		System.out.println("PortableLunch()");
	}
}

public class Sandwich extends PortableLunch {
	private Bread b = new Bread();
	private Cheese c = new Cheese();
	private Lettuce l = new Lettuce();

	public Sandwich() {
		System.out.println("Sandwich");
	}

	public static void main(String[] args) {
		new Sandwich();
	}

}

结果为:

Lunch()
PortableLunch()
bread()
Cheese()
Lettuce
Sandwich

即:

  • 调用基类构造器
  • 按照声明顺序调用成员的初始化方法
  • 调用导出类构造器的主体

2. 继承与清理

  • 覆盖导出类的dispose方法时,务必调用基类的dispose方法,否则,基类的清理动作不会发生。
  • 销毁的顺序应该和初始化顺序相反:导出类->基类
class Characteristic {
	private String s;

	Characteristic(String s) {
		this.s = s;
		System.out.println("creating Characteristic" + s);
	}

	protected void dispose() {
		System.out.println("disposing Characteristic " + s);
	}
}

class Description {
	private String s;

	Description(String s) {
		this.s = s;
		System.out.println("creating Description " + s);
	}

	protected void dispose() {
		System.out.println("disposing Description " + s);
	}
}

class LivingCreature {
	private Characteristic p = new Characteristic("is alive");
	private Description t = new Description("basic Living Creature");

	LivingCreature() {
		System.out.println("LivingCreature()");
	}

	protected void dispose() {
		System.out.println("LivingCreature dispose");
		t.dispose();
		p.dispose();
	}
}

class Animal extends LivingCreature {
	private Characteristic p = new Characteristic("has heart");
	private Description t = new Description("Animal not Vegetable");

	Animal() {
		System.out.println("Animal");
	}

	protected void dispose() {
		System.out.println("Animal dispose");
		t.dispose();
		p.dispose();
		super.dispose();
	}

}

class Amphibian extends Animal {
	private Characteristic p = new Characteristic("can live in water");
	private Description t = new Description("both water anf land");

	Amphibian() {
		System.out.println("Amphibian");
	}

	protected void dispose() {
		System.out.println("Amphibian dispose");
		t.dispose();
		p.dispose();
		super.dispose();
	}

}

public class Frog extends Amphibian {
	private Characteristic p = new Characteristic("Croaks");
	private Description t = new Description("eats bugs");

	Frog() {
		System.out.println("Frog");
	}

	protected void dispose() {
		System.out.println("Frog dispose");
		t.dispose();
		p.dispose();
		super.dispose();
	}

	public static void main(String[] args) {
		Frog f = new Frog();
		System.out.println("Bye!");
		f.dispose();
	}
}

结果为:

creating Characteristicis alive
creating Description basic Living Creature
LivingCreature()
creating Characteristichas heart
creating Description Animal not Vegetable
Animal
creating Characteristiccan live in water
creating Description both water anf land
Amphibian
creating CharacteristicCroaks
creating Description eats bugs
Frog
Bye!
Frog dispose
disposing Description eats bugs
disposing Characteristic Croaks
Amphibian dispose
disposing Description both water anf land
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description basic Living Creature
disposing Characteristic is alive

如果成员对象中存在于其他一个或多个对象共享的情况,则有必要使用引用计数来跟踪仍旧访问着的共享对象的对象数量了。
如下:

class Shared {
	private int recount = 0;
	private static long counter = 0;// long:防止溢出
	private final long id = counter++;// final:不希望它的值在对象生命周期中被改变

	public Shared() {
		System.out.println("Creating" + this);
	}

	public void addRef() {
		recount++;
	}

	protected void dispose() {
		if (--recount == 0) {
			System.out.println("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) {
		System.out.println("Creating" + this);
		this.shared = shared;
		this.shared.addRef();
	}

	protected void dispose() {
		System.out.println("disposing" + this);
		shared.dispose();
	}

	public String toString() {
		return "Composing" + id;
	}
}

public class ReferenceCounting {
	public static void main(String[] args) {
		Shared shared = new Shared();
		Composing[] composings = { new Composing(shared), new Composing(shared), new Composing(shared) };
		for (Composing c : composings) {
			c.dispose();
		}
	}
}

结果为:

CreatingShared0
CreatingComposing0
CreatingComposing1
CreatingComposing2
disposingComposing0
disposingComposing1
disposingComposing2
DisposingShared0

3. 构造器内部的多态方法的行为

如果在构造器的内部调用正在构造的对象某个动态绑定方法,会发生什么情况呢
注意:

  • 初始化的实际过程中:
    在其他事物发生之前,将分配给对象的存储空间初始化二进制的零,然后调用基类构造器等等行为
  • 如果要调用构造器内部的一个动态绑定的方法,就要用到那个方法被覆盖后的定义。
  • 编写构造器的一条有效准则:“用尽可能简单地方法使对象进入正常状态,避免调用其他方法”,唯一可以安全调用的是基类中的final方法,因为不会被覆盖。

如下:

class Glyph {

	Glyph() {
		System.out.println("Glyph() before draw()");
		draw();  //会调用子类的重写方法
		System.out.println("Glyph() after draw()");
	}

	void draw() {
		System.out.println("Glyph draw()");
	}
}

class RoundGlyph extends Glyph {
	private int radius = 1;

	RoundGlyph(int r) {
		radius = r;
		System.out.println(" RoundGlyph.draw(),radius=" + radius);
	}

	void draw() {
		System.out.println(" RoundGlyph.draw(),radius=" + radius);// 未被初始化

	}

}

public class PolyConstructors {
	public static void main(String[] args) {
		new RoundGlyph(5);
	}
}

结果为:

Glyph() before draw()
 RoundGlyph.draw(),radius=0
Glyph() after draw()
 RoundGlyph.draw(),radius=5

猜你喜欢

转载自blog.csdn.net/qq_42349617/article/details/86309666