public class Animal { String name; public void move() { System.out.println("Move!"); }}class Cat { String name; String age; public void move() { System.out.println("Move!"); } public void catchMouse() { System.out.println("catch mouse!"); }}// 以上代码为了方便就不封装了
但这样无疑多写了很多代码,如果方法和属性多的话,写起来浪费效率,这时候就有了继承,上面的猫类有动物类的特征,我们就在猫类的代码中把动物类的特征都继承过来,继承后我们只需要在猫类写它特有的属性和方法就行。而继承的格式为:
class 子类名称 extends 父类名称 {...}
所以上述的代码可以修改成:public class Animal { String name; public void move() { System.out.println("Move!"); }}class Cat extends Animal { String age; public void catchMouse() { System.out.println("catch mouse!"); }}
是不是看起来好很多了呢?从这里我们可以看出继承可以让代码得到重复的使用,从而提高开发效率。以上的Animal称为
父类、超类或者基类,而Cat称为
子类、派生类或者扩展类。父类与子类的连接我们用
extends关键字。关于继承我们需要知道的是:
Java中只支持单继承(只能有一个父亲),不支持多继承(一个类继承多个类)
子类除了父类的构造方法不能继承之外,其他的都可以继承。
一个Java类没有继承的话,默认继承Object类(Java类库中的一个类)。
第三点也就是说上面的class Animal {}实际上默认是class Animal extends Object {}。
针对Java的类库,前面我们对于Java自带的类库才刚提及到一个接受用户输入的Scanner类而已,但对于Java自带的这么一套完整的类库,我们可以直接用Java之包机制及接收用户输入中的导包机制来方便我们的开发效率。所以我们需要不断地学习了解才行。
以上这么多,其实读者没必要惊慌,我们Java SE只需要了解java.base目录下的几个包就行,其中,java.lang、java.util、java.io、java.math等都是以后我们首要学习的内容。 不过要快捷学习Java自带的类库的话,我们可以去网上下载一个JDK的API文档,这个文档里面都是Java类库的帮助说明(这里作者用JDK1.6的中文帮助文档来说明)。 从以上的帮助文档中我们可以看到Object类中的构造方法和方法等等的具体说明。这些我们以后会说,但今天我们主要说的是Object和继承有关的特性: 从上面图就可以很清楚的知道 Object类中的方法在我们自己写的每个类中都可以用。具体的以后会提到。大体上了解了Object的作用后,我们就可以进入下一个由继承而衍生出来的东西了——方法覆盖/重写(override/overwrite)。在学习了以上的简单继承之后,如果我们直接去敲代码,我们可能就会遇到子类继承了父类的方法后,发现父类的方法无法达到子类所想要的效果这种情况,这时候我们就需要方法覆盖来解决这样的问题。比如上述的动物类和猫类,猫类继承了动物类移动的这个方法,但是动物类的移动的方法只是输出了一个Move!无法达到猫类想要的效果(假设这里猫类调用move方法想要的效果时输出"Cat move!"),这时候我们就可以在猫类的方法中覆盖move这个方法:
public class Animal { String name; public void move() { System.out.println("Move!"); }}class Cat extends Animal { String age; // 重写Aninal的move方法 public void move() { System.out.println("Cat Move!"); } public void catchMouse() { System.out.println("catch mouse!"); }}
从以上的覆盖来看,和我们讲过的Java之方法初步中的方法重载很像,但两者的区别还是很大的,这点文末会详解一下下。对于方法覆盖,也有几点注意事项:
方法覆盖只发生在有继承关系的类中
覆盖时要有相同的方法名、形参列表,而返回值类型对于基本数据类型一定要一样,而对于引用数据类型,子类的返回值类型可以是父类的返回值类型的子类。
子类覆盖的方法的访问权限修饰符不能比父类的访问权限修饰符的访问权限更低。当父类的方法被访问权限最低的private修饰时,子类无法覆盖父类的方法
子类覆盖的方法抛出的异常不能比父类抛出的异常多[子类异常集合包含于父类异常集合]
最后两点先大致有点印象就行,后期就会有的。而对于方法覆盖的第二点,读者有兴趣可以自行尝试下,这点只是了解就好,方法覆盖一般都是想覆盖哪个方法就复制粘贴哪个就行,返回值类型一般都写一样的。
接下来到了super关键字了,还记得Java之面向对象特征——封装及private修饰符初步、this关键字中this关键字嘛?两者用法相近,结合比较会加深记忆的!super关键字指的是 父类的属性和方法(但不是像this那样保存着一个内存地址)。为什么会有super关键字呢?主要是针对以下两种情况的:1、父类方法被子类覆盖了,子类想调用父类原来的方法,需要用super.2、用于区分是子类特有的属性/方法还是从父类继承过来的以下用第二种情况来说明,第一种情况读者自行实验下就可以明白的:public class Animal { String name; // move方法被private修饰 // 子类不能覆盖这个方法 private void move() { System.out.println("Move!"); }}class Cat extends Animal { String age; public void catchMouse() { System.out.println("catch mouse!"); } // 提供一个改变名字的方法并提示改变成功的方法 public void changeName(string name) { super.name = name; System.out.println("Change successfully!"); }}
因为上述的name是父类Animal中的属性,所以子类用super.name用来提示是父类的属性,但这个是可以省略的,只有
当子类和父类有一样的属性或方法时,super就不能省略了:
public class Animal { String name; // move方法被private修饰 // 子类不能覆盖这个方法 private void move() { System.out.println("Move!"); }}class Cat extends Animal { String name; public void catchMouse() { System.out.println("catch mouse!"); } // 提供一个改变名字的方法并提示改变成功的方法 public void changeName(string name) { super.name = name; System.out.println("Change successfully!"); }}
以上的代码,Animal和Cat类都有一个name属性,当我们要操作父类的name时候就必须加上super.了,不然不加或者加this.的话意味着对Cat中的name进行操作。所以super关键字我们需要注意以下几点:
super和this都是和对象挂钩的,没有对象就没有这两者,所以super和this都只能用在构造方法和实例方法中,静态方法不适用。
super一般都是可以省略的,但当子类和父类有相同的属性,或者父类的方法被覆盖了,这时候想调用父类的属性或方法就需要super.
当子类的构造方法想要调用父类的构造方法时,需要用super(参数列表)的格式调用。
再看看Java之面向对象特征——封装及private修饰符初步、this关键字中的this:
是不是很相像呢?开始我们的super(参数列表),我们在写子类的构造方法的时候,如果要对父类的属性赋初始值的时候一个一个列出来很麻烦的,比如:public class Animal { String name; int age; public void Animal() { } public void Animal(String name, int age) { this.name = name; this.age = age; }}class Cat extends Animal { String color; public void Cat() { } public void Cat(String name, int age, String color) { // 下面的name和age属性也可以变成 // super.name = name; // super.age = age; this.name = name; this.age = age; this.color = color; }}
这样无疑是浪费效率的,这时候我们就可以用super(参数列表)来快捷的赋初始值:
public class Animal { String name; int age; public void Animal() { } public void Animal(String name, int age) { this.name = name; this.age = age; }}class Cat extends Animal { String color; public void Cat() { } public void Cat(String name, int age, String color) { super(name,age); this.color = color; }}
是不是节省了许多呢?
对于super(参数列表),和this(参数列表)一样,只能出现在构造方法的第一行,
因此一个构造方法中只能调用一个父类或者本类的构造方法!构造方法中有super(参数列表)就没有this(参数列表),有this(参数列表)就没有super(参数列表)。而且重要的是!
如果一个构造方法没有super(参数列表)或者this(参数列表)时,在构造方法的首行默认会有一个super()
;因此我们就又有了
当子类的构造方法被调用的时候,父类的构造方法就一定会被调用,
所以回到继承的Object类中,因为每个类直接或间接继承了它,
所以子类的构造方法被调用的时候,Object类中的构造方法一定会被调用!
所以以上代码就有这样的结构:
public class Animal { String name; int age; public void Animal() { // super(); // 因为父类是Object // 所以相当于调用Object(); } public void Animal(String name, int age) { // super(); this.name = name; this.age = age; }}class Cat extends Animal { String color; public void Cat() { // super(); // 父类是Animal // 所以相当于Animal(); } public void Cat(String name) { // super(); this.name = name } public void Cat(String name, int age) { // 调用了this(参数列表)了 // 所以没有默认的super了 this(name); this.age = age; } public void Cat(String name, int age, String color) { // 调用了super(参数列表)了 // 所以没有默认的super了 super(name,age); this.color = color; }}
上面我们举个例子都是没有封装的类在继承,而对于有经过封装的类,我们之前说过不能在其他类直接进行访问其属性,因为封装就等于受到了严密的保护,是不可进行直接读取修改的,因此,
对于有封装的父类,子类不可以用this.父类属性或者super.父类属性来对父类的属性进行修改,能修改当且仅当父类提供了getter和setter方法。子类调用方法来进行修改。这里就不再演示。从生物学来理解private修饰的父类属性和方法的话:儿子继承了父类型特征,这是儿子出生就有的,有些特征如:黄种人是儿子不能选择并修改的(等同于
private修饰的父类属性并且没有setter和getter方法,不能被子类直接访问并修改),有些行为如:天生就会背圆周率也是儿子不能选择并修改的(等同于private修饰的父类方法不能被覆盖)。这些我们都用private修饰来保证父类特征不能被篡改。
最后终于到了文章的尾声了,最后我们来简单看看方法重载和方法覆盖的区别:
方法重载 | 方法覆盖 |
同一个类中 | 父子类中 |
与修饰符无关; 与返回值类型无关; 方法名必须相同; 参数列表必须不同 | 修饰符访问权限不能更低; 返回值类型建议写相同; 方法名必须相同; 参数列表必须相同; 抛出的异常不能更多; |