继承是面向对象的三大特征之一,也是实现软件复用的重要手段。 Java 的继承具有单继承的特点,每个子类只有一个直接父类。
1,继承的特点
Java 的继承通过 extends 关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,有的也称其为基类、超类。父类和子类的关系,是一种一般和特殊的关系。例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。
因为子类是一种特殊的父类,因此父类 包含的范围总比子类包含的范围要大,所以可以认为父类是大类,而子类是小类。
修饰符 class SubClass extends SuperClass
{
//类定义部分
}
package com.demo;
public class Fruit {
public double weight;
public void info(){
System.out.println("我是一个水果!重" + weight + "g!");
}
}
package com.demo;
public class Apple extends Fruit {
public static void main(String[] args) {
//创建 Apple 对象
Apple a = new Apple();
//Apple 对象本身没有 weight Field
//因为Apple 的父类有 weight Field,也可以访问 Apple 对象的 Field
a.weight = 56;
//调用 Apple 对象的 info 方法
a.info();
}
}
上面的 Apple 类本来只是一个空类,它只包含了一个 main 方法,但程序中创建了 Apple 对象之后,可以访问该 Apple 对象的weight 实例 Field 和 info() 方法,这表明 Apple 对象也具有了 weight 实例 Field 和 info() 放法,这就是继承的作用。
如果定义一个 Java 类时并未显示指定这个类的直接父类,则这个类默认扩展 java.lang.Object 类。因此, java.lang.Object 类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的 Java 对象都可调用 java.lang.Object 类所定义的实例方法。
2,重写父类的方法
子类扩展了父类,子类是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的 Field 和方法。但有一种情况例外:子类需要重写父类的方法。
package com.demo;
public class Bird {
public void fly(){
System.out.println("我在天空里自由地飞翔");
}
}
package com.demo;
public class Ostrich extends Bird {
public void fly(){
System.out.println("我只能在地上奔跑...");
}
public static void main(String[] args) {
//创建Ostrich对象
Ostrich os = new Ostrich();
//执行Ostrich对象的fly方法,将输出"我只能在地上奔跑..."
os.fly();
}
}
执行上面的程序,将看到执行 os.fly() 时执行的不再是 Bird 类的 fly 方法,而是执行 Ostrich 类的 fly 方法。
这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说子类重写了父类的方法,也可以说子类覆盖了父类的说法。
方法的重写要遵循“两同两小一大”规则,“两同”即方法名相同、形参列表相同;“两小”指的是子类方法方法返回值类型比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
3,super限定
如果需要在子类方法中调用父类覆盖的实例方法,则可使用super 限定来调用父类被覆盖的实例方法。为上面的 Ostrich 类添加一个方法,在这个方法中调用 Bird 类中被覆盖的fly 方法。
public void callOverrideMethod()
{
//在子类方法中通过super 显示调用父类被覆盖的实例方法
super.fly();
}
通过 callOverrideMethod 方法的帮助,就可以让 Ostrich 对象既可以调用自己重写的 fly 方法,也可以调用 Bird 类中被该的fly 方法(调用 callOverrideMethod 方法即可)。
super 是 Java 提供的一个关键字,super也不能出现在 static 修饰的方法中。 static 修饰的方法是属于类的,该方法的的调用者可能是一个类,而不是对象,因而 super 限定也就失去了意义。
如果子类定义了和父类同名的 Field,则会发生子类 Field 隐藏父类 Field 的情形。在正常情况下,子类里定义的方法直接访问该 Field 默认会访问到子类中定义的 Field, 无法访问到父类中被隐藏的 Field。在子类定义的实例方法中可以通过 super 来访问父类中被隐藏的 Field。
package com.demo;
public class BaseClass {
public int a = 5;
}
package com.demo;
public class SubClass extends BaseClass {
public int a = 7;
public void accessOwner(){
System.out.println(a);
}
public void accessBase(){
//通过 super 来限定访问从父类继承得到 a Field
System.out.println(super.a);
}
public static void main(String[] args) {
SubClass sc = new SubClass();
sc.accessOwner();
sc.accessBase();
}
}
4,调用父类构造器
子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,类似于前面所介绍的一个构造器调用另一个重载的构造器。
package com.demo;
class Base{
public double size;
public String name;
public Base(double size, String name){
this.size = size;
this.name = name;
}
}
public class Sub extends Base{
public String color;
public Sub(double size, String name, String color){
//通过 super 调用来调用父类构造器的初始化过程
super(size, name);
this.color = color;
}
public static void main(String[] args) {
Sub s = new Sub(5.6, "测试对象", "红色");
//输出 Sub 对象的三个 Field
System.out.println(s.size + "--" + s.name + "--" + s.color);
}
}
从上面的程序中不难看出,使用 super 调用和使用 this 调用也很像,区别在于 super 调用的是其父类的构造器,而 this 调用的是同一个类中重载的构造器。因此,使用super 调用父类构造器也必须在子类构造器执行体的第一行,所以 this 调用和 super 调用不会同时出现。
不管我们是否使用 super 调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次。
下面程序定义了三个类,它们之间严格的继承关系,通过这种继承关系让读者看到构造器之间的调用关系。
package com.demo;
class Creature{
public Creature(){
System.out.println("Creature 无参数的构造器");
}
}
class Animal extends Creature{
public Animal(String name){
System.out.println("Animal 带一个参数的构造器," + "该动物的 name 为" + name);
}
public Animal(String name ,int age){
this(name);
System.out.println("Animal 带两个参数的构造器," + "其 age 为" + age);
}
}
public class Wolf extends Animal{
public Wolf(){
super("灰太狼", 3);
System.out.println("Wolf 无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}
}
Creature 无参数的构造器
Animal 带一个参数的构造器,该动物的 name 为灰太狼
Animal 带两个参数的构造器,其 age 为3
Wolf 无参数的构造器
从上面运行过程来看,创建任何对象总是从该类所在继承树最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类的构造器。如果某个父类通过this 调用了同类中重载的构造器,就会依次执行此父类的多个构造器。