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()方法。