Java学习笔记——第六章 继承和多态

第六章 继承和多态

6.1 何为继承

6.1.1 继承共同行为

继承基本上是为避免多个重复定义共同行为(注意是基本上,滥用继承会导致程序维护上有问题,可结合随后的第七章接口和多态了解,加以区别)
我们把相同的程序代码提升为父类。然后在子类中将重复的代码复制过来,即使用新的关键词 extends,表示一种扩充行为,即子类 继承父类。例如:

public class Role{	//定义一个父类Role,包含子类的共同程序代码
	private String name;
	private int level;
	
	public int getLevel(){
		return level;
	}
 	...(省略)
}
public class SwordMan extends Role(){	//定义一个子类SwordMan继承父类
	public void fight(){	//定义父类中没有的方法,即子类特有的方法
		System.out.println("攻击");
	}
}

通常我们会使用以上类图,第一格Role表示类名,第二格中name,level,blood表示数据成员,:之后是成员类型,-为private,第三格表示方法名称,+表示public,:之后表示返回类型,继承则以空心箭头表示(此处用普通箭头代替)
注:private成员会被继承,只是子类无法直接进行存取,必须通过父类提供的方法存取。

6.1.2 多态和is-a

在Java中,子类只能继承一个父类,继承除了可避免类间重复的行为定义外,还有子类和父类间是is-a的关系,即子类“是一种”父类的关系。例如,以下程序是可以编译通过的:

Role role1 = new SwordsMan();//SwordsMan是不是一种Role
Role role2 = new Magician();//Magician是不是一种Role

对于以上程序,要检查自己的语法逻辑是否正确,方式是 从=号右边往左读:右边是不是一种左边(右边类是不是左边类的子类)?但是,SwordsMan swordsman = new Role();//Role是一种SwordsMan,这是错误的。
再如:

Role role1 = new SwordsMan();//SwordsMan是一种Role是正确的
SwordsMan swordsman = role1;//role1不一定是SwordsMan

针对以上程序,可以使用扮演通过编译:

Role role2 = new SwordsMan();
SwordsMan swordsman = (SwordsMan)role2;

但是,以上叙说的扮演并可能会存在问题,只是强制告诉编译器让这段代码通过编译,不过后果自负。扮演也有一定的规则要求,前后的类是一个类型,譬如如下扮演将会编译失败:

Role role3 = new Magician();//role3拥有的是Magician的属性和方法值
SwordsMan swordsman = (SwordsMan)role3;//Role扮演的是Magician,强制转换为	
										   //SwordsMan在执行上会报错

通过以上讲述,我们引出多态的概念,抽象的讲就是使用单一接口操作多种类型的对象
总结:
结合上述案例,简单的说就是(以游戏为例),当我们拥有了多种角色,但是每种角色都拥有很多相似的属性和方法,我们首先将相同的属性和方法封装为一个父类,让子类继承父类的这些属性和方法(如果是public或者提供存取的方法,在子类中可以使用),子类继承父类,可以在子类中定义父类中没有的方法或者对父类中的方法进行重载。当然这里有一个问题,当我们需要的对象(即游戏中的角色)很多时,每个子类继承父类后都要重写方法(这种方式显然不可取),对于这种问题,
即当所有的子类继承父类都要重写一个方法时,我们可以将其重新定义一个方法,传入的参数就是父类的类型运用上面讲到的即可进行调用。这样当我们创建的对象较多时,只要他们都是继承父类,都可以使用这个方法,就不需要使用重载的方式,对每个不同的子类都进行方法的重写,这样的方式有利于进行程序的维护,只需要对一个方法进行修改而不需要对每个重写的方法都进行修改。

...(省略子类继承父类的程序代码)
public class RPG{
	public static void main(String[] args){
		SwardsMan swardsman = new SwardsMan();
		swardsman.setBlood(200);
		Magician magician = new Magician();
		magician.setBlood(100);
		showBlood(swardsman);//SwordsMan是一种Role,可以调用showBlood方法
		showBlood(Magician);//Magician是一种Role,可以调用showBlood方法
	}
	static void showBlood(Role role){	//声明为Role类型
		System.out.println("血量为:%d",role.getBlood());
	}
}

6.1.3 重新定义行为

假设现在我们要定义一个static()方法,我们考虑使用多态,但是父类中并没有定义子类中特有的方法,那我们将子类特有的方法在父类中进行声明,至于方法具体如何实现,交给各子类对此方法的重写。
重新定义(Override):在继承父类方法后,定义与父类中相同的方法部署,但是执行的内容不同。
注:
1、在重新定义父类中的某个方法时,子类必须撰写父类方法中相同的签署。
2、在JDK5之后支持标注(Annotation),如果加入Override标注,在重写方法时如果方法名字写错(包括大小写),会引发编译器错误。

6.1.4 抽象方法、抽象类

如果某方法块中没有任何程序代码操作,可以使用abstract标注该方法为抽象方法。该方法不用撰写{}区块,直接以“;”结束即可。
注:
类中如果有方法没有操作(例如含抽象方法),并且标示为abstract,表示这个类是不完整类,那么这个类就不能用来创建实例。
Java中规定,内含抽象方法的类,一定要在class前标示abstract。

public abstract class Role{
	...(省略)
	public abstract void fight();
}

对于抽象方法的两种处理方式:
1、继续标示该方法为abstract(该子类因此也是个抽象类,必须在class前标示abstract);
2、操作该抽象方法(重写该抽象方法)。
如果没有以上两种操作,将引发编译错误。

6.2 编译语法细节

6.2.1 protected成员

如果只想让子类直接获取类的属性值,可以将属性值定义为protected。被声明为protected的成员,相同包中的类可以直接存取,不同包中的类可以在继承后的子类直接存取。
若没有定义权限关键字,就默认为是包范围。

6.2.2 重新定义的细节

在Java中,如果想获取父类中的方法定义,可以在调用方法前,加上super关键词。
可以使用super关键词调用的父类方法,不能定义为private(因为这样就限定只能在类内部使用)。
对于父类中的方法权限,只能扩大但不能缩小。若原来成员public,子类中重新进行定义时不可为private或protected。

6.2.3 再看构造函数

在创建子类实例后,会先进行父类定义的初始流程,在进行子类中定义的初始流程,也就是创建子类实例后,会先执行父类构造函数定义的流程,在执行子类构造函数定义的流程。
如果在子类构造函数中没有指定执行父类中哪个构造函数,默认会调用父类中无参数构造函数。如果想执行父类中某个构造函数,可以使用super()指定。
注:
1、this()和super()只能择一使用,而且一定要在构造函数第一行执行。
2、如果没有撰写任何构造函数时,自动会加入没有参数的默认构造函数,如果自行定义了构造函数,则不会自动加入任何构造函数了。
3、如果定义了有参数的构造函数,也可以加入无参数构造函数,即使内容为空也可以。

6.2.4 再看final关键词

在构造函数中,一定要对定义为final关键词的数据成员指定初始值,否则会编译出错。
如果在class前使用了final关键词,那么表示该类是最后一个,不会再有子类继承该类。同样,如果某一个方法限定为final,表示最后一次定义方法,子类不会重新定义final方法。

6.2.5 java.lang.Object

在java中,子类只能继承一个父类,如果在定义类时没有使用关键词extends指定继承任何类,那一定是继承java.lang.Object,即,如下写法是等价的:

public class Some{
	...
}
//相当于
public class Some extends Object{
	...
}

所以任何类追溯到最上层的父类,一定就是java.lang.Object,即java所有对象都是一种Object。
任何类型的对象,都可以使用Object声明的名称来参考。

6.2.5.1 重新定义toString()

Object的toString()默认定义为:

public String toString(){
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
6.2.5.2 重新定义equals()

在java中要比较两个对象的实质相等性,要通过equals()方法进行比较。
注:
instanceof运算符:用来判断对象是否由某个类创建,左操作数是对象,右操作数是类。

//判断other是否为Cat创造出来的
if(!(other instanceof Cat)){
	return false;
}
...

6.2.6 关于垃圾回收

对于不再有用的对象,JVM有垃圾收集(GC)机制,收集到的垃圾对象所占据的内存空间会被垃圾收集器释放。在执行流程中,无法通过变量参考的对象,就会被GC认定为垃圾对象。
GC在进行回收对象前,对调用对象的finalize()方法,在Object上定义的方法。如果在对象被回收前有些事情想做,可以重新定义finalize()方法。

posted on 2018-04-07 16:54  20169305孙德洋  阅读(282)  评论(0编辑  收藏  举报