什么是继承
继承是面向对象的三大特征之一,是实现代码复用的重要手段。
Java
的继承通过extends
关键字实现,实现继承的类称为子类,被继承的类称为父类(或者基类、父类)。
继承的语法如下:
1访问修饰符 class SubClass extends SuperClass{
2 //类定义部分
3}
子类继承了父类,将获得父类的全部成员变量和方法,但是不能获得父类的构造器。
下面程序演示子类继承父类。
先定义一个父类:
1/**
2 * Person类,作为父类
3 */
4public class Person {
5
6 String name;
7
8 int age;
9
10 public void eat() {
11 System.out.println("吃饭");
12 }
13
14}
再定义一个子类,继承自上面已经定义好的父类:
1/**
2 * Man子类,继承Person父类
3 */
4public class Man extends Person {
5
6}
再定义一个测试类:
1public class Test {
2
3 public static void main(String[] args) {
4 Man man = new Man();
5
6 // Man类本身并未定义name和age属性,但可以访问,说明Man继承了来自其父类的man和age属性
7 man.name = "Java";
8 man.age = 18;
9
10 // eat方法也继承自其父类
11 man.eat();
12 }
13
14}
上面代码说明:当一个子类继承了一个父类,将获得父类的全部成员变量和方法。
做为子类,也可以有自己单独的成员变量和方法,其和父类构成了一般和特殊的关系。如,可以在上面的Man类添加自己的属性和方法:
1/**
2 * Man子类,继承Person父类
3 * 添加自己的属性和方法,这样一样,这个类的对象就拥有了父类的成员变量、方法和自己类中的成员变量、方法
4 */
5public class Man extends Person {
6 boolean isRich;
7
8 public void earnMoney() {
9 System.out.printf("男人要赚钱");
10 }
11}
Java的继承特点
在Java
语言中,每个类只有一个直接父类,是单继承的。例如下面代码编译会报错:
1class SubClass extends SuperClass1, SuperClass2,SuperClass3...
2
但是,可以有多个间接父类,如:
1public class Man extends Person{...}
2public class Person extends Creature{...}
上面代码中,Creature
是Person
的直接父类,是Man
的间接父类。
如果定义一个类时,没有显式地声明其直接父类,则这个类默认继承自java.lang,Object
类。Object
类是所有类的父类,要么是直接父类,要么是间接父类。
方法的重写
子类扩展了父类,是父类的一个特殊实现。当从父类继承来的方法不满足子类时,可以重写父类的方法。
下面程序先定义了一个Bird父类如下:
1/**
2 * Person类,作为父类
3 */
4public class Person {
5
6 public void eat() {
7 System.out.println("人吃饭");
8 }
9
10}
下面再定义一个Man类,这个类扩展了Person类,重写了Person类的eat()方法。
1/**
2 * Man子类,继承Person父类
3 */
4public class Man extends Person {
5
6 // 子类重写了父类的eat()方法
7 @Override
8 public void eat() {
9 System.out.println("男人要吃很多饭");
10 }
11
12}
再写一个测试类:
1public class Test {
2
3 public static void main(String[] args) {
4 Man man = new Man();
5 // 输出为:男人要吃很多饭
6 man.eat();
7 }
8
9}
可以看到,程序执行的是Man类中的eat()方法,而不是Person类的eat()方法。
像上面这种子类和父类包含有相同名称、相同参数列表的现象被称为方法的重写(Override),也被称为方法的覆盖。可以说子类重写了父类的方法,也可以说父类的方法被子类覆盖了。
方法的重写要遵循”两同两小一大“规则,即:
- ”两同“:方法名相同,参数列表相同
- “两小”:子类方法返回值类型应比父类方法返回值类型小或相等,子类方法声明抛出的异常应比父类小或相等
- ”一大“:子类方法的访问权限应比父类的大或相等。
super关键字
前文提到,子类可以重写父类的方法。当子类覆盖了父类方法后,子类对象将无法访问父类被重写的方法,但可以通过super
关键字在子类对象的方法中访问父类被重写的方法。
代码是这样的:
1/**
2 * Man子类,继承Person父类
3 */
4public class Man extends Person {
5
6 @Override
7 public void eat() {
8 // 可以在子类的方法中调用父类被覆盖的方法
9 super.eat();
10 System.out.println("男人要吃很多饭");
11 }
12
13}
如果子类定义了和父类同名的实例变量,则在子类实例方法中将无法访问父类的该变量,这是因为被子类实例同名的实例变量隐藏了,这时候可以通过super
关键字来访问。如下代码所示。
1class BaseClass{
2 public int a = 5;
3}
4
5public class SubClasss extends BaseClass{
6 public int a = 7;
7
8 public void accessSubVar() {
9 System.out.println(a);
10 }
11
12 public void accessBaseVar() {
13 System.out.print(super.a);
14 }
15
16 public static void main(String[] args) {
17 SubClasss s = new SubClasss();
18 s.accessSubVar(); // 输出7
19 s.accessBaseVar(); // 输出5
20 }
21
22}
当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承来的所有实例变量分配内存,即使子类定义了和父类同名的实例变量。也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,自己子类中定义了2个实例变量,那么这个Java对象共有2+3+2个实例变量。
此外,super
关键字还可以用来调用父类构造器。
1class Base {
2 public double size;
3 public String name;
4 public Base(double size, String name) {
5 this.size = size;
6 this.name = name;
7 }
8}
9
10public class Sub extends Base {
11 public String color;
12
13 public Sub(double size, String name, String color) {
14 // 使用super关键字显式调用父类构造器
15 super(size, name);
16 this.color = color;
17 }
18}
如果我们在子类构造器中未显式调用父类构造器,则会隐式调用父类无参数的构造器。
1class Person {
2
3 public Person() {
4 System.out.println("父类构造器执行。。。");
5 }
6
7}
8
9public class Man extends Person {
10
11 public Man() {
12 System.out.println("子类构造器执行。。。");
13 }
14
15 public static void main(String[] args) {
16 // 执行Man类的构造器时,会先执行其父类的构造器
17 // 所以输出结果为:父类构造器执行。。。子类构造器执行。。。
18 Man man = new Man();
19 }
20
21}
所以,调用子类构造器时,总是会先调用其父类的构造器,如果父类还继承自别的间接父类,则会依次往上追溯执行构造器。Object是所有类的父类,所以,构造任何Java对象时,总会先执行Object类的构造器。