继承
继承如何使用,就不多说了。
一个类(类A)继承另一个类(类B)后,类A将会获得类B的所有属性与方法,当然类B的private域的属性类A是不可以直接操作的,还有private域的方法也没法重写,如果类A中定义类B中private域的方法,是不会有问题的,因为该方法属于类A自己的。从这方面可以看出,private域的限制范围,除了自身外,连继承都无法访问的(private域虽然无法直接操作或者重写,但不代表没有继承到或者说是不存在)。
接下来,再来说一说有关继承的一些问题。
重写与重载
说起继承,总会提到重写,提到重写也会提到重载,两个都一起说一下吧。
public class Example {
public void printString(String str) {
System.out.println(str);
}
public void printString(int i) {
System.out.println("打印数字字符串" + i);
}
public void printString(String str, int i) {
System.out.println("打印数字与字符串" + str + "," + i);
}
}
这个就是一个比较简单的重载,那么重载是如何实现的呢?
首先我们想想,当我们调用方法的时候,虚拟机是如何知道我们调用哪个方法的呢?也就是是如何指定我们所要调用的方法。
方法的名字与方法的参数列表成为方法的签名,例如printString(String)与printString(int),它们方法名相同,但是签名不同。而签名就是指定的方法。方法会被记录于一个方法表中,而这个表的索引就是方法的签名。重载利用的就是相同的方法名,不同的签名。
返回类型并不是签名的一部分,也就是说返回类型与指定方法是无关的。所以有如下的写法
public class Example {
public void printString(String str) {
System.out.println(str);
}
public void printString(int i) {
System.out.println("打印数字字符串" + i);
}
public void printString(String str, int i) {
System.out.println("打印数字与字符串" + str + "," + i);
}
public String printString() {
return "test";
}
}
这样写是没有问题的,也是重载的一种。
这里提一个问题,方法System.out.println()重载了多少个?其实不用去查看,可以用重载的定义稍微推理一下,方法println()所有的基本类型和String都可以作为参数传入,那么有几个基本类型,就重载了多少个,还有方法println() 可以传入各种对象,那么它应该还重载了一个println(Object),还有一个无参的。当然实际上还是有出入的。
重写,在子类中定义与超类有相同签名和返回类型,或子类方法中返回类型是超类方法中返回类型的子类的方法时,就会重写超类的方法。假如返回类型不同而且不是上述提到的继承关系,但签名相同,将会报错。类的继承,子类会获取超类的非private的属性与方法,相同的签名但是不同的返回类型,没有进行重写,相当于方法重复,所以会报错。
还有子类重写方法时,修饰符不能低于超类的控制等级。
从面向对象的角度去看,子类修饰符的控制等级降低,如将public改为protected,相当于把超类的这个方法隐藏起来,子类改写了继承于超类的特性,这是不允许的。
从代码实现的角度去看,当从外部调用一个被子类重写的方法时,虚拟机会去执行子类中的方法,而不是超类的方法。结果子类的方法是private域的,不可以调用,进而产生错误。
再来说一个比较常见的问题
Class SuperClazz {
public void f() throws IOException {
/*
操作
*/
}
}
Class SupClazz extends SuperClazz {
//报错
public void f() throws Exception {
/*
操作
*/
}
}
子类重写超类的方法时,不能抛出更多的异常。
以面向对象的角度去看,记得当年上JAVA基础时,老师说的是“因为继承下,子类不能比超类做更多的事情,就像徒弟与师傅,徒弟不会比师傅更能干”什么的。
现在回想一下,其实描述得并不正确,子类是可以比超类做更多的事情,子类定义自己的方法时,其所能做的事情就比超类多了。更准确来说,应该是子类所继承的行为(方法),不能超出超类的限制范围(能力),否则就是改写了继承于超类的特性,是不被允许的。
因为封装性,方法内部的实现对外是不可见的,方法内部如何改变,都不会改变这个方法对外展现的特性。但是抛出异常这一行为并没有被封装,它是对外所展现的。
以代码实现的角度去看的话,会涉及到多态,在下面将会提及。
多态
多态的实现源于动态绑定,而JAVA的动态绑定属于后期绑定,即在运行时绑定,再通俗点讲,就是在运行时,才确定对象是哪个类的实例。
再说一下以下的问题:
public class SuperClazz {
public void simpleFunction() {
/*
* 操作
*/
}
}
public class SubClazz extends SuperClazz {
@Override
public void simpleFunction() {
// TODO Auto-generated method stub
super.simpleFunction();
}
public void otherFunction() {
/*
* 操作
*/
}
}
public class Main {
public static void main(String[] args) {
SuperClazz superClazz = new SubClazz();
//报错,无法找到改方法
superClazz.otherFunction();
}
}
如上面所说的,在运行时,才确定对象的类型是什么。
声明类型为SuperClazz(超类),那么在运行前都不确定实际的类型,那么自然就先认为这个对象是SuperClazz,调用子类才有的方法,自然会报找不到方法的错误。
现在再从代码实现的角度说一下为什么子类不允许比超类抛出更多的异常:
JAVA的多态是后期绑定,在运行期间判断对象的类型,并分别调用适当的方法。但在运行前,我们不知道对象的实际类型,自然就先默认为是所声明类(如SuperClazz),所以所捕获的异常,也是SuperClazz的方法所声明要抛出的,而实际运行的时候,这个对象其实是它的子类(SubClazz),而这时SubClazz抛出更多的异常,多出来的异常就无法捕获了。