Java编程思想,读书笔记六(第8章 多态)

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u010882234/article/details/79214359

       第8章  多态

       在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。多态通关分离做什么和怎么做,从另一个角度将接口和实现分离开来。多态不但能改变代码的组织结构和可读性,还能够创建可扩展的程序--无论在项目最初创建时还是在需要增加新功能时都可以“生长”的程序。“封装”通关合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。而多态的作用则是消除类型之间的耦合关系。

       多态,也称为动态绑定、后期绑定或运行时绑定。将一个方法调用同一个方法主题关联起来被称作绑定。后期绑定含义就是在运行时根据对象的类型进行绑定。Java中除了static方法和final方法(private方法属于final方法)之外,其他方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑定--它会自动发生。final修饰方法的作用也是有效的“关闭”动态绑定,这样编译器就可以调用final方法生成更有效的代码,但大多数情况下对程序的整体性能并没有什么改观。所以,应谨慎使用final,最好根据设计决定是否使用,而不是出于试图提高性能的目的。

       一旦知道Java中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的子类都可以正确运行。或者换一种说法,发送消息给某个对象,让该对象去断定应该做什么事。基类可以建立公共接口,子类通过覆盖接口方法来完成具体实现,这样不同的子类对象去调用基类公共接口方法时,编译器就会选择与子类相符的特定方法去执行。只与基类接口通信,这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而添加一些新功能。那些操纵基类接口的方法不需要任何改动就可以应用于新类。这正是我们所期望多态所具有的特性。我们所做的代码修改,不会对程序中其他不受影响的部分产生破坏。换句话说,多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。

      实例代码如下:我们建立个基类Hero(表示英雄),有些基本属性,我们建立4个技能方法;建立个子类Garen(表示盖伦),覆盖基类的方法。我们建立Fighting类,用组合把Hero作为该类的属性字段,建立attack(Hero hero)方法,调用Hero的方法。我们再建立个Test类,实验一下方法调用。

package polymorphism;

public class Hero {
	private String name;//姓名
	private String title;//头衔、称号	
	private HeroType type;//类型,0WARRIOR战士,1ASSASSIN刺客,2TANK坦克,3ARCHER射手,4MAGE法师
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}	
	public HeroType getType() {
		return type;
	}
	public void setType(HeroType type) {
		this.type = type;
	}
	//建立4个方法,作为英雄的主动技能,可以攻击时使用
	public void skillQ() {
		System.out.print("使用技能Q;");
	}
	public void skillW() {
		System.out.print("使用技能W;");
	}
	public void skillE() {
		System.out.print("使用技能E;");
	}
	public void skillR() {
		System.out.println("使用技能R;");
	}	
	public Hero() {}
	public Hero(HeroType type) {
		this.type = type;
		type.role();
	}
}
package polymorphism;

public enum HeroType {
	WARRIOR{
		void role() {System.out.println("光荣的战士,所向披靡!");}
	},ASSASSIN {
		void role() {System.out.println("阴影中的刺客,致命一击!");}
	},TANK {
		void role() {System.out.println("厚重的坦克,一步不退!");}
	},ARCHER {
		void role() {System.out.println("精准的射手,箭无虚发!");}
	},MAGE {
		void role() {System.out.println("神秘的法师,开始攻击!");}
	};
	abstract void role();
}
package polymorphism;

public class Garen extends Hero{
	private String nickname;

	public String getNickname() {
		return nickname;
	}

	public void setNickname(String nickname) {
		this.nickname = nickname;
	}
	
	@Override
	public void skillQ() {
		System.out.print("盖伦放出致命打击技能,造成沉默效果;");
	}
	@Override
	public void skillW() {
		System.out.print("盖伦放出勇气技能,减少来自敌方的伤害;");
	}
	@Override
	public void skillE() {
		System.out.print("盖伦放出审判技能,对复数敌人造成伤害;");
	}
	@Override
	public void skillR() {
		System.out.println("盖伦放出德玛西亚的正义技能,对单个敌人造成成吨伤害,成功干掉了敌人!");
	}
	public Garen() {}
	public Garen(HeroType type) {
		this.setType(type);
		type.role();
	}
}
package polymorphism;

/**
 * 攻击类,英雄主动释放技能
 * @author ylyan
 *
 */
public class Fighting {
	private Hero hero;
	
	public Hero getHero() {
		return hero;
	}

	public void setHero(Hero hero) {
		this.hero = hero;
	}

	//英雄使用依次技能Q、W、E、R
	public void attack(Hero hero) {
		hero.skillQ();
		hero.skillW();
		hero.skillE();
		hero.skillR();
	}
}
package polymorphism;

public class Test {
	
	public static void main(String[] args) {
		
		Fighting fighting = new Fighting();
		
		Hero hero = new Hero();
		fighting.attack(hero);
		
		Garen garen = new Garen(HeroType.WARRIOR);
		fighting.attack(garen);
		
	}
}
运行结果:
使用技能Q;使用技能W;使用技能E;使用技能R;
光荣的战士,所向披靡!
盖伦放出致命打击技能,造成沉默效果;盖伦放出勇气技能,减少来自敌方的伤害;盖伦放出审判技能,对复数敌人造成伤害;盖伦放出德玛西亚的正义技能,对单个敌人造成成吨伤害,成功干掉了敌人!

      可以看出,在Test中Fighting对象的attack()方法会根据实际的参数类型去调用方法,这些就是运行时绑定。这时如果需要添加一个新的英雄艾希(Ashe),就不需要对原有的代码做太多变动,就可以实现。可以建立子类Ashe继承Hero,然后在使用Fighting对象的attack()方法时,传入Ashe的对象即可。

package polymorphism;

public class Ashe extends Hero{
	@Override
	public void skillQ() {
		System.out.print("艾希放出射手的专注技能,造成减速效果;");
	}
	@Override
	public void skillW() {
		System.out.print("艾希放出万箭齐发技能,对复数敌人造成伤害;");
	}
	@Override
	public void skillE() {
		System.out.print("艾希放出鹰击长空技能,照亮了战争阴影地区;");
	}
	@Override
	public void skillR() {
		System.out.println("艾希放出魔法水晶箭技能,冰冻住复数敌人,并成功干掉了其中一个!");
	}
	public Ashe() {}
	public Ashe(HeroType type) {
		this.setType(type);
		type.role();
	}
}
package polymorphism;

public class Test {
	
	public static void main(String[] args) {
		
		Fighting fighting = new Fighting();
		
		Hero hero = new Hero();
		//调用基类中的方法
		fighting.attack(hero);
		
		Garen garen = new Garen(HeroType.WARRIOR);
		//调用子类Garen中方法
		fighting.attack(garen);
		
		Ashe ashe = new Ashe(HeroType.ARCHER);
		//调用子类Ashe中的方法
		fighting.attack(ashe);
		
	}
}
运行结果:
使用技能Q;使用技能W;使用技能E;使用技能R;
光荣的战士,所向披靡!
盖伦放出致命打击技能,造成沉默效果;盖伦放出勇气技能,减少来自敌方的伤害;盖伦放出审判技能,对复数敌人造成伤害;盖伦放出德玛西亚的正义技能,对单个敌人造成成吨伤害,成功干掉了敌人!
精准的射手,箭无虚发!
艾希放出射手的专注技能,造成减速效果;艾希放出万箭齐发技能,对复数敌人造成伤害;艾希放出鹰击长空技能,照亮了战争阴影地区;艾希放出魔法水晶箭技能,冰冻住复数敌人,并成功干掉了其中一个!
      从这里可以看出,多态对软件的可扩充性有很大的作用。

      多态的缺陷。一是“覆盖”私有方法。private方法被自动认为是final方法,而且对子类是屏蔽的。只有非private的方法才可以被覆盖,在子类中对于基类的private方法,最好采用不同的名字。二是域(字段)与静态方法无法实现多态,只有普通的方法调用可以是多态的。因为任何域访问操作都将由编译器解析,而静态方法是直接与类关联(可以直接通过类名调用,与对象无关),所以无法实现多态。

      构造器和多态。尽管构造器不具备多态性(它们实际上是隐式声明的static方法),但还是非常有必要理解构造器通过多态在复杂的结构层次中运作。调用构造器的顺序:1,在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零;2,调用基类构造器,这个过程会不断的反复递归下去;3,按声明顺序调用成员的初始化方法;4,调用子类构造器的主体。这样有一个优点,至少所有的东西都至少初始化为零(或者是某些特殊数据类型中与“零”等价的值),而不仅仅是留作垃圾。其中,通过“组合”而嵌入一个类内部的对象引用,其值是null。在编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法”。在构造器里唯一能够安全调用的那些方法使基类中的final方法(包括private方法,它们自动属于final方法)。

      继承与清理。通过组合和继承方法来创建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理事。如果确实遇到清理的问题,可以自己定义清理方法。并且由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在子类覆盖清理方法。关于子类的此覆盖方法,务必记住调用基类的清理方法,否则子类的清理动作就不会发生。

       协变返回类型,它表示在子类中的被覆盖的方法可以返回基类方法的返回类型的某种子类类型。早期Java版本(Java SE5以前)仅允许返回基类类型,协变返回类型,允许返回更具体的子类类型。

       学习了多态后,看起来似乎所有的东西都可以被继承。事实上,当我们建立新类时,应首先选择“组合”,组合更加灵活,因为它可以动态选择类型,因此也就选择了行为(方法);相反,继承在编译时就需要知道确切类型。组合时,类的对象引用,在运行时可以和另一个不同的对象重新绑定起来,这样我们在运行期间获得了动态灵活性(也称作状态模式,State)。一条通用的准则是:“用继承表达行为间的差异,并用字段表示状态上的变化”。

        在Java语言里,所有的转型都会得到检查。在运行期间会检查是否是我们希望的类型,否则会返回ClassCastException(类转型异常)。这种在运行期间对类型进行检查的行为称为“运行时类型识别”(RTTI)。

       下一篇:Java编程思想,读书笔记七(第9章 接口) - CSDN博客  

猜你喜欢

转载自blog.csdn.net/u010882234/article/details/79214359