何谓继承
继成共同行为
继承就是避免多个类间重复定义共同行为。
比如在游戏中的角色都会具有角色名称,等级,血量等属性,不同角色之间或许只存在某些属性的不同。把角色当作类的话,如果有更多类具有重复的程序代码,想要修改一个类,会造成维护上的不便。如果要改进,就可以把相同的代码提升为父类。
package cc.openhome;
public class Role {
private String name;
private int level;
private int blood;
public int getBlood() {
return blood;
}
public void setBlood(int blood) {
this.blood = blood;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这个类是游戏角色中的重复代码。
接着SwordsMan继承Role:
package cc.openhome;
public class SwordsMan extends Role {
public void fight() {
System.out.println("挥剑攻击");
}
}
关键字extends表示SwordsMan会扩充Role的行为,即继承后再扩充Role原本没有的fight()行为。
private成员会被继承,不过子类无法直接存取,必须通过父类提供的方法来存取。
多态与is-a
在Java中,子类只能继承一个父类。
is-a: SwordsMan继承了Role,所以SwordsMan是一种Role。
要理解多态,必须知道我们操作的对象是“哪一种”东西。可以将自己当做编译程序,检查语法的逻辑是否正确,方法是从=号右边往左读:右边是不是一种左边呢(右边的类是不是左边的一种子类)。有效的理解多态有助于我们写出来的东西更加的有弹性,更好的维护。
问题来了,如果要设计一个方法,显示所有角色的血量,或许你会这样撰写:
public static void showBlood(SwordsMan swordsMan) {
System.out.printf("%s 血量 %s%n",swordsMan.getName(),swordsMan.getBlood());
}
public static void showBlood(Magician magician) {
System.out.printf("%s 血量 %s%n",magician.getName(),magician.getBlood());
}
这是重载方法的运用,但是如果有100个角色呢?这种方法显然不可能。如果角色都是继承于Role,我们可以用以下的方法设计并调用:
static void showBlood(Role role)
{
System.out.printf("%s 血量 %d %n", role.getName(), role.getBlood());
}
showBlood(swordsMan);
showBlood(magician);
这样写就算有100种角色,只要它们都是继承Role,都可以使用这个方法显示角色的血量,而不需要像前面重载的方式,为不同角色写100个方法,多态的写法显然具有更高的可维护性。
多态:以抽象讲法解释,就是使用单一接口操作多种类型的对象。在showBlood()方法中,既可以通过Role类型操作SwordsMan对象,也可以通过Role类型操作Magician对象。
重新定义行为
操作接口相同,只是方法操作内容不同时,可以将这个方法提升至父类。在父类中只是定义了这个方法,但是具体的操作内容是由子类来执行的。在继承父类后,定义与父类中相同的方法部署,但执行的内容不同,这称为重新定义。
标注:如果在子类中的某个方法前标注@Override,表示要求编译程序检查,该方法是不是真的重新定义了父类中某个方法,如果不是的话,就会引发编译错误。
抽象方法、抽象类
如果某方法区块中没有任何程序代码操作,可以使用abstract标示该方法为抽象方法,该方法不用撰写{}区块,直接;结束即可。
package cc.openhome;
public abstract class Role {
...
public abstract void fight();
}
类中若有方法没有操作,并且标示为abstract,表示这个类定义不完整,定义不完整的类就不能用来生成实例。
Java中规定,内含抽象方法的类一定要在class前标示abstract,表示这是一个定义不完整的抽象类。如果尝试用抽象类创建实例就会引发编译错误。
子类如果继承抽象类,对于抽象方法有两种做法,一种做法是继续标示该方法为abstract,另一种做法是操作抽象方法。如果两种都没有实施,就会引发编译错误。
继承语法细节
protected成员
被声明为protected的成员,在相同的包中可以直接存取,在不同的包中,只有继承了父类之后才能进行存取。
关键字 | 类内部 | 相同包类 | 不同包类 |
---|---|---|---|
public | 可存取 | 可存取 | 可存取 |
protected | 可存取 | 可存取 | 子类可存取 |
无 | 可存取 | 可存取 | 不可存取 |
private | 可存取 | 不可存取 | 不可存取 |
重新定义的细节
在Java中,如果想取得父类中的方法定义,可以在调用方法前,加上super关键字。使用super关键字调用的父类方法,不能定义为private。
重新定义方法要注意,对于父类中的方法权限,只能扩大但不能缩小。若原来成员public,子类中重新定义时不可以为private或protected。
在JDK5之后,如果子类中重新定义的方法的返回类型是父类方法返回类型的子类,是可以通过编译的。
static方法属于类拥有,如果子类中定义了相同签署的static成员,该成员属于子类所有,而非重新定义,static方法也没有多态,因为对象不会个别拥有static成员。
再看构造函数
创建子类实例后,会先执行父类构造函数定义的流程,再执行子类构造函数定义的流程。
构造函数可以重载,父类中可重载多个构造函数,如果子类构造函数中没有指定执行父类中哪个构造函数,默认会调用父类中无参数构造函数。
如果自行定义了构造函数,就不会加入任何构造函数:
class Some
{
Some(int i)
{
out.println("呼叫Some(int i)");
}
}
class Other extend Some
{
Other()
{
out.println("呼叫Other()");
}
}
这是会报错的,因为Some类中已经定义了Some(int i)这个方法,所以不会默认给Some类中加入无参数的方法,在编译Other()方法时,程序默认调用父类中的无参数方法,但是父类中没有无参数的方法,所以编译失败了。
再看final关键字
class前可以加上final关键字,表示这个类是最后一个,不会再有子类,也就是不能被继承。
定义方法时,也可以限定该方法为final,表示最后一次定义方法,也就是子类不可以重新定义final方法。
java.lang.Object
在Java中,如果没有使用extends关键字指定一个类继承哪个类的时候,默认是继承自Object的,因此Object是最上层的父类,即所有的对象都“是一种”Object。
如果有个需求是使用数组收集各种对象,那应该声明为什么类型呢?
Object[] objs = {"Monica",new Date(),new SwordsMan()};
String name = (String)objs[0];
Date date = (Date)objs[1];
SwordsMan swordsMan = (SwordsMan) objs[2];
因为数组长度有限,使用数组来收集对象不方便,ArrayList类可以不限长度地收集对象:
package cc.openhome;
import java.util.Arrays;
public class ArrayList {
private Object[] list; //使用Object数组收集
private int next; //下一个可存储对象的索引
public ArrayList(int capacity) { //指定初始容量
list = new Object[capacity];
}
public ArrayList() {
this(16); //初始容量默认为16
}
public void add(Object o) { //收集对象方法
if(next == list.length) { //自动增长Object数组长度
list = Arrays.copyOf(list,list.length*2);
}
list[next++] = o;
}
public Object get(int index) { //依索引取得收集的对象
return list[index];
}
public int size() { //已收集的对象个数
return next;
}
}
自定义的ArrayList类,内部使用Object数组来收集对象,每一次收集的对象会放在next指定的索引处,在创建ArrayList实例时,可以指定内部数组初始容量,如果使用无参数构造函数,则默认容量为16。如果要收集对象, 可通过add()方法,注意参数的类型为Object,可以接收任何对象。如果内部数组原长度不够,就使用Array.copyOf()方法自动建立原长度两倍的数组并复制元素。如果想取得收集的对象,可以使用get()指定索引取得。如果想知道已经收集的对象个数,则可通过size()方法得知。
关于垃圾收集
创建对象会占据内存,如果程序执行流程中已无法再使用某个对象,该对象就只是徒耗内存的垃圾。
对于不再有用的对象,JVM有垃圾收集机制,收集到的垃圾对象所占据的内存空间,会被垃圾收集器释放。
简单地说,执行流程中,无法通过变量参考的对象,就是垃圾对象。