【Java SE】继承的详解

篮球哥温馨提示:编程的同时不要忘记锻炼哦!

伴着秋风,聆听篮球落网的声音


目录

1、继承

1.1 继承的概念

1.2 继承的语法和简单使用

1.3 子类中访问父类成员出现同名

2、 super关键字

2.1 如何使用 super 关键字

2.2 子类构造方法

2.3 super 和 this 的区别 

3、再谈代码块

3.1 回顾 

 3.2 继承关系中的执行顺序

4、 protected 关键字

4.1 protected关键字的作用

4.2 子类如何访问父类 private 修饰的成员? 

4.3 为什么 private 和 protected 不能修饰外部类?

5、Java中的继承方式 

6、final 关键字

7、继承与组合


1、继承

1.1 继承的概念

这里我们回顾下之前 Student 类,如果今天我们要定义一个 Teacher 类,我们会发现,这两个类有很多共同点:首先学生有姓名性别年龄,老师也有姓名性别年龄,同时学生的行为有上课,老师的行为也有上课这一行为,但是呢,学生与老师又有不同的点,比如学生有成绩,而老师有工资,再比如学生有考试的行为,而老师却有改试卷的行为,所以我们再一想,他们共同的点是不是可以抽取出来形成一个新的类?

继承(inheritance)机制:这个机制是面向对象程序设计中一个可以使代码复用的重要手段,他可以在原有类的特性上进行扩展,增加新的功能,由继承下来的类,称为派生类,从而实现代码复用,就比如我们上面的 Student 和 Teacher 类,抽出共同的部分,然后继承共同的部分,达到代码复用:

【Java SE】继承的详解_第1张图片

通过上图我们发现 Student 和 Teacher 类都继承了 Person 类的属性,其中 Person 称为父类/基类/超类,而 Student,Teacher 可以称为 Person 的子类/派生类,继承之后,子类可以复用父类中的成员,而子类只需要关心自己新增加的成员即可,同时也可以看出,继承最大的作用就是实现代码复用和实现多态(多态下期讲)

1.2 继承的语法和简单使用

现在我们是有了这样的一个思路,如何用代码实现呢?这里我们需要了解 extends 关键字,具体的语法如下:

修饰符 class A extends B {
    // ... 
}

而如上代码中,我们就可以理解成,A类继承了B类,所以A是B的子类,B是A的父类,那我们讲我们上图中画的图用代码实现一下:

public class Person {
    public String name;
    public String sex;
    public int age;
    //老师和学生都需要吃饭和上课
    public void attendClass() {
        System.out.println(name + "正在上课");
    }
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
}
class Student extends Person {
    //学生有成绩
    public float score;
    //学生需要考试
    public void examination() {
        System.out.println(name + "同学正在考试");
    }
}
class Teacher extends Person {
    //老师有工资
    public float salary;
    //老师需要改试卷
    public void modifyPaper() {
        System.out.println(name + "老师正在改试卷");
    }
}

我们先分析下上面的代码:首先 Student 和 Teacher 分别继承了 Person 这个类,也就是说他们继承了父类的成员,所以子类中有两部分,第一部分是父类的成员,第二部分是子类自己的成员,简而言之,就是通过子类实例化的对象的成员里面包含了父类成员。

在一个我们可以观察下他们抽取出来的部分,确实是如我们刚开始所说,把学生和老师共同拥有的属性和行为抽取出来形成一个人的类,接下来我们就用main方法去测试下他们:

class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.name = "张三";
        student.examination();

        Teacher teacher = new Teacher();
        teacher.name = "李四";
        teacher.modifyPaper();

        student.eat();
        teacher.eat();
    }
}

【Java SE】继承的详解_第2张图片

从这个打印结果也能证明我们前面的结论,子类实例化的对象确实继承了父类的属性和行为!

总结:

  • 子类会将父类中的成员继承到子类中
  • 子类继承父类后,必须要添加自己特有的成员,体现出与父类的不同,否则就没有必要继承了

1.3 子类中访问父类成员出现同名

上面的演示我们也看到了,子类是可以访问父类的成员的(建立在限定符允许的情况,后面讲),那如果出现子类的成员变量和父类成员变量同名怎么办呢?如果成员方法也同名怎么办?

我们简单对上面的学生类做修改:

class Student extends Person {
    //学生有成绩
    public float score;
    public String name = "123";
    //学生需要考试
    public void examination() {
        System.out.println(name + "同学正在考试");
    }
    public void eat() {
        System.out.println("执行了子类的eat");
    }
}
class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.examination();
        student.eat();
    }
}

这个代码里面的 name 变量和 eat 方法跟父类里的同名了:

如果同名执行的是父类的话,则打印的是:null同学正在考试null正在吃饭 因为如果执行父类则 name 变量没有初始化,默认值是 null。

如果同名执行的是子类的话,则打印的是:123同学正在考试执行了子类的eat 因为我们原地初始化了子类的 name 变量为"123",我们让main方法走起来:

【Java SE】继承的详解_第3张图片

总结: 

  • 如果子类成员变量和父类成员变量同名,则优先访问子类的
  • 成员变量优先访问遵循就近原则,自己有优先自己的,自己没有则向父类中找,都没有则报错
  • 如果子类成员方法和父类成员方法同名(没有构成重载),则优先访问子类的
  • 如果子类成员方法和父类成员方法同名(构成了重载),则根据调用方法传递的参数选择合适的成员访问,都没有则报错

如果在子类中存在与父类相同的成员时,我非要访问父类的,怎么办?用super关键字,我们马上了解


2、 super关键字

2.1 如何使用 super 关键字

由于场景的需要,可能会出现父类和子类出现相同名字的成员,如果我们要在子类中访问相同名字的父类成员,直接访问是不可以做到的,这里就可以使用 super 关键字,我们把上面的代码稍作修改:

class Student extends Person {
    //学生有成绩
    public float score;
    public String name = "123";
    //学生需要考试
    public void examination() {
        System.out.println(super.name + "同学正在考试");
    }
    public void eat() {
        System.out.println("执行了子类的eat");
    }
    public void test() {
        super.eat();
    }
}
class Test {
    public static void main(String[] args) {
        Student student = new Student();
        student.examination();
        student.test();
    }
}

如果说 super 可以当子类和父类成员出现同名加上 super 会帮我们访问父类的话,则会打印 null同学正在考试null正在吃饭 我们来看看是不是真的帮我们访问的父类成员:

【Java SE】继承的详解_第4张图片

注意:

  • super 只能在非静态方法中使用,因为只有产生了对象才能访问实例成员变量和成员方法,而静态成员变量和静态成员方法是不依赖于对象的
  • 在子类方法中,访问父类的成员变量或者成员方法
  • super 调用构造方法后面讲

2.2 子类构造方法

之前我们了解过,当创建对象的时候,先要调用类的构造方法,当构造方法调用完毕,对象才真正产生了,那么在我们实例化子类的时候,子类既然要继承父类的属性,需先调用父类的构造方法,然后再执行子类的构造方法,所以当我们什么构造方法都没写的时候,编译器会默认提供无参的构造方法。

既然实例化子类的时候需要先调用父类的构造方法,所以编译器默认给子类提供的构造方法就是这样的:

public Student() {
        super();
    }

也就是说,子类构造方法中,如果用户没有写,编译器会默认调用父类的无参构造方法: super();而且 super();必须是构造方法中的第一条语句,并且只能出现一次。

这里我们做个测试,证明没有写自己写 super();的时候编译器会默认调用:

public class Person {
    public String name;
    public String sex;
    public int age;

    public Person() {
        System.out.println("父类构造方法执行!");
    }
}

class Student extends Person {
    public float score;

    public Student() {
        System.out.println("子类构造方法执行!");
    }
}

【Java SE】继承的详解_第5张图片

总结:子类虽然没有调用父类构造方法,但编译器还是默认带上了, 同时也正如我们所说,先调用父类在调用子类,因为子类对象中的成员有两部分组成,分别是父类继承下来的部分以及子类自己增加的部分,字面意思父子,肯定是现有父后有子,所以在子类实例化的时候,必须先调用父类的构造方法,将父类继承下来的成员构造完整之后,在调用子类的构造方法,将子类自己的成员初始化完整。

注意:

  • 在子类构造方法中,编译器虽然隐含了super() 调用,但是如果父类构造方法是有参的,则需要子类构造方法里显式提供合适父类构造方法的调用
  • super(...) 只能在子类构造方法出现一次,不能和 this 同时出现

2.3 super 和 this 的区别 

这个其实也是面试会问到的一个题,下面我们就来看下他们的区别:

相同点:

  • 都是Java中的关键字
  • 都只能在类的非静态方法中使用,用来访问非静态成员和变量
  • 在构造方法中出现的话,必须是构造方法中的第一条语句,而且他俩不能同时出现 

不同点:

  • this 是当前对象的引用,而 super 是从父类继承下来部分成员的引用,不能说是父类对象的引用,因为实例化子类的时候并没有创建父类的对象
  • this 用来访问本类的方法和属性,super 用来访问父类的方法和属性(都是非静态)
  • 在构造方法中,this 用来调用本类的构造方法,super 用来调用父类的构造方法
  • 子类构造方法中,super 一定会出现,this 如果不写则没有

图示例:

【Java SE】继承的详解_第6张图片


3、再谈代码块

3.1 回顾 

上期我们测试过代码块,发现执行顺序的优先级分别是 静态代码块 -> 实例代码块 -> 构造方法 分别是这种执行循序。

  • 静态代码块最先执行,并且只执行一次,因为是再JVM加载类的时候就开辟了。
  • 当有对象创建时就会执行实例代码块,实例代码块执行完毕后,接着执行构造方法 

 3.2 继承关系中的执行顺序

如果父类中有静态代码块,实例代码块,构造方法,而子类中也有这些,那么他们的先后执行顺序是什么呢?我们用代码来做测试:

public class Person {
    public String name;
    public String sex;
    public int age;
    
    {
        System.out.println("执行 Person 实例代码块!");
    }
    
    static {
        System.out.println("执行 Person 静态代码块!");
    }
    
    public Person() {
        System.out.println("执行 Person 构造方法!");
    }
}
class Student extends Person {
    public float score;

    {
        System.out.println("执行 Student 实例代码块!");
    }

    static {
        System.out.println("执行 Student 静态代码块!");
    }

    public Student() {
        System.out.println("执行 Student 构造方法!");
    }
}
class Test {
    public static void main(String[] args) {
        Student student = new Student();
    }
}

【Java SE】继承的详解_第7张图片

通过测试,我们可以得出以下结论:

  • 静态代码块永远是最早执行,但是父类优于子类 
  • 紧接着就是父类实例代码块和构造方法,再就是子类实例代码块和构造方法
  • 第二次实例化的时候,父类和子类静态代码块将不会执行(可以自己下去测试,上一期也测试)

4、 protected 关键字

4.1 protected关键字的作用

其实在上一期中我们就已经简单的介绍了以下这个关键字,只是当时我们并不知道继承的概念,所以当时出现,允许在不同包中的子类中访问这个权限,相信大家目前应该已经了解他的权限了,那这里我们就做一个小的代码演示:

【Java SE】继承的详解_第8张图片

看到这,相信你就理解了 protected 关键字了,建议大家自己下来多尝试敲代码,多测试,简单来说,要想在不同的包中访问 protected 修饰的成员,首先你这个类必须称为那个类的子类。

4.2 子类如何访问父类 private 修饰的成员? 

其实本期的所有代码,除了4.1例子之外,不管是父类还是子类的成员变量都是 public 修饰的, 那我们前面学过 private 修饰的成员只能在本类中访问,也就是说如果父类中有 private 修饰的成员,我们也会继承下来,但是无法直接访问,因为权限限定符,他限定的就是你访问的权限,那我们就想访问父类使用 private 修饰的成员怎么办?有一个目前能理解的方法:在父类中写一个公共的获取方法就行,比如 public String getName() { return name; } 

4.3 为什么 private 和 protected 不能修饰外部类?

我们要先了解他们的权限:

private:如果使用 private 修饰了外部类,那么这个类将不能在其他类中进行实例化,也就是不能用这个类来创建对象,那么这个类的属性和方法就不能被访问,这个类将毫无意义,所Java直接不允许使用 private 修饰外部类

protected:如果 A 类用了 protected 修饰,那么在不同包中的 B 类要想访问 A 类的话,B 必须为 A 的子类, 但是 B 想继承 A 的前提是 B 可以访问到 A,这里就会发生冲突,继承是为了拥有父类的属性和方法,所以 protected 是用来修饰类的成员的

总结:

也就是说,如果我这个类的属性和方法可以被任何的子类访问,我就用 protected,如果我这个类的属性和方法只能在本类中访问,我就用 private,如果我们不想让这个类被继承,就用 final(后续讲)


5、Java中的继承方式 

Java中支持一个类单继承,也就是一个类继承另一个类

Java中支持多层继承,也就是一个类继承了另一个类,另一个类又继承了另一个类

Java中支持不同类继承同一个类,就是一个父类有多个子类,但子类只有一个父类

Java中不支持多继承,也就是不能一个子类有多个父类! 

【Java SE】继承的详解_第9张图片

一般我们不希望出现超过三层的继承关系,继承层次太多,就可能考虑要对代码重构了! 


6、final 关键字

这个关键字在前面我们也见过,final简单来说可以修饰变量,成员方法,类,我们下面就来看看修饰他们所不同的表现:

final修饰变量:这个我们在之前说过,修饰了变量或者字段,表示常量,是不能被修改的

final修饰方法:表示方法不能被重写(下期介绍)

final修饰类:表示该类不能被继承:

【Java SE】继承的详解_第10张图片


7、继承与组合

组合就相当于一个东西是由什么零件组装而成的,而继承则是把相同的部分抽取了出来,两个其实都是实现代码复用的效果

  • 继承与对象之间的关系是:is - a:比如猫是动物,狗也是动物,他们都属于动物
  • 组合于对象之间的关系是:has - a:比如主机是由CPU,主板,显卡等组合而成形成主机

这个在我们后续的学习中也会接触,这里先做一个简单了了解即可


【Java SE】继承的详解_第11张图片

 下期预告:【Java SE】多态的详解

你可能感兴趣的