Java 继承(下)

发布于:2025-05-28 ⋅ 阅读:(23) ⋅ 点赞:(0)

Java 继承(下)


观前提醒:这篇博客,需要你先进去我的主页,认真学习完 Java 继承(上)这篇博客了,再过来看 Java继承(下)这篇博客


本节目标:

  1. 继承关系下,子类的构造方法
  2. super 和 this 的区别
  3. 再谈初始化
  4. protected 关键字
  5. 继承的多种方式
  6. final 关键字的介绍
  7. 继承和组合

1. 继承关系下,子类的构造方法

Java 继承(上)这篇博客的 super 关键字的介绍中,我们讲到了super 关键字的用法有三个,其中,第三个用法的具体说明,将在这一个标题内容中讲解到。

1.1 借助例子,引出 super( ) 的使用

我们先用这么一个代码示例来引出 super( ) 的使用:还是先创建一个 Animal类,Dog类,将 Animal类 作为 Dog类 的父类,也就是,Dog类 继承了 Animal类。

public class Animal {
    public String name;
    public int age;
    
    public void sleep() {
        System.out.println(this.name+"正在睡觉...");
    }

    public void eat() {
        System.out.println(this.name+"正在吃饭...");
    }
}
//
public class Dog extends Animal{
    public void bark() {
        System.out.println(this.name+"正在汪汪叫...");
    }
}

然后,我们想使用构造方法,帮助Animal类的成员变量进行初始化,在Animal类当中,创建Animal类的构造方法,但是,当我们在 Animal类 当中创建构造方法的时候,却发现,Dog类(Animal的子类)居然报错了

在这里插入图片描述
为什么我在Animal类当中仅仅创建了一个构造方法,代码就直接报错了?

既然我在 Animal类 当中仅仅创建了一个构造方法,Dog类的代码就报错了,那么,我在Dog类当中,也创建一个构造方法,能不能把这个错误给消除掉呢?
这个方式就像小学的等式计算一样,等式左边加10,那右边也加10,结果依旧相等。

构造方法的作用,就是帮助类中的成员变量初始化的,看上面的代码,Dog类当中,没有一个成员变量,所以,Dog类的构造方法,应该是一个无参的构造方法

public Dog() {
    
}

在这里插入图片描述
根据图片可知,就算我在Dog类当中,也创建了一个构造方法,代码还是会报错,为什么?到底该如何解决这个错误?

答:在 Dog类 当中,也同样去创建一个构造方法的思路是对的,但是,想要解决这个错误,需要使用到 super关键字的第三种用法:通过super( )去访问父类的构造方法

1.2 使用 super( ) 解决问题,并分析原因

对于 上面的代码示例展示出来的问题,解决方法:
在 Dog类 当中,创建构造方法,同时,在构造方法的第一行,添加 super( ),但这里需要根据Animal类中的构造方法,进行参数匹配

public Dog(String name, int age) {
    super(name, age);
}

在这里插入图片描述
从图片可以看出,在 Dog类 中,加入了这段代码以后,代码就没有报错了,接下来,我们来具体分析分析为什么这样子做,代码的错误就解决了的原因

因为:子类对象中成员是有两部分组成的,父类继承下来的以及子类新增加的部分 。我们说,父子,父子肯定是先有父再有子,所以在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整

原因了解之后,我们可以简单总结一下
当子类继承父类的之后,如果父类中存在成员变量且创建了构造方法的时候,子类需要显式的调用父类的构造方法,先帮助父类的成员进行初始化,再进行子类的初始化。

简单就一句话:先帮助父类进行初始化,再进行子类自己的初始化
在这里插入图片描述
大概的执行流程就是这样的。

这块内容呢,建议多看几遍,直到真的理解了,为什么这样子做的原理!!!

1.3 不同情况下,super( ) 会有不同的形式

仍是以 Animal类,Dog类为例子去演示。这里并不会有代码,而是简单以图片展示构造方法的样子,知道有这么些情况就可以了。

1.3.1 父类(Animal类)中没有成员变量的时候

在这里插入图片描述

这样的super( ) 的形式,一般是见不到的

1.3.2 父类(Animal类)中有成员变量的时候,在子类(Dog类)中直接给定一个值

在这里插入图片描述

这样的 super( ) 给 Animal类 的 成员变量赋值的方式,不建议!!!

1.3.3 父类(Animal类)中有成员变量的时候,匹配父类构造方法的参数

在这里插入图片描述

最常见的就是这种。

1.4 注意事项:

  1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super( ) 调用,即调用父类构造方法
    在这里插入图片描述

  2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
    在这里插入图片描述

  3. 在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
    在这里插入图片描述

  4. 对于构造方法而言:this(…) 必须在构造方法的第一条语句,super(…) 也必须在构造方法的第一条语句,所以,super( ) 和 this( ) 在构造方法里不能共存!!!
    在这里插入图片描述

1.5 快速构建 构造方法的快捷方式

提醒:我本人的 IDEA 是安装了中文插件包的,所以我的显示界面都是 中文的,但是,对应的选项的对应位置是不变的,所以,即使你的是英文,对应选项的位置去寻找,也是一样的。

按住 Alt + Insert (笔记本的话,是按住 Fn + Alt + Insert ) --> 选择构造函数 --> 点击 或 按下回车(Enter)就可快速生成 构造方法

在这里插入图片描述

2. super 和 this 的区别

super 和 this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

【相同点】

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在

至于,什么是非静态的方法,非静态的字段,看 JAVA 初始类和对象(下) 我的这篇博客,里面有详细介绍。

【不同点】

  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
  2. 非静态成员方法中,this用来访问本类的方法和属性super用来访问父类继承下来的方法和属性
  3. 在构造方法中:this(…)用于调用本类构造方法super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
  4. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是**this(…)**是调用本类其他的构造方法,如果 用户不写则没有

3. 再谈初始化

关于初始化的内容,静态代码块,示例代码块,构造方法的执行顺序,在 JAVA 初始类和对象(下) 我的这篇博客,里面有详细介绍。

提醒:本标题内容,目的是要知道,在 加入了继承体系之后,静态代码块,实例代码块,构造方法的执行顺序是怎么怎么样的。

3.1 没有加入继承体系下的代码块执行顺序

class Person {
public String name;
public int age;
public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("构造方法执行");
}
	//实例代码块
    {
        System.out.println("实例代码块执行");
    }
    //静态代码块
static {
        System.out.println("静态代码块执行");
    }
}
///
public class TestDemo {
    public static void main(String[] args) {
        Person person1 = new Person("jin",10);
        System.out.println("============================");
        Person person2 = new Person("jfidas",20);
    }
}

运行结果:
在这里插入图片描述

  1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

3.2 加入继承体系后,各代码块执行顺序

class Person {
public String name;
public int age;
public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person:构造方法执行");
}
    //实例代码块
    {
        System.out.println("Person:实例代码块执行");
    }
    //静态代码块
    static {
        System.out.println("Person:静态代码块执行");
    }
}
/
class Student extends Person{
    public Student(String name,int age) {
        super(name,age);
        System.out.println("Student:构造方法执行");
    }
    //示例代码块
    {
        System.out.println("Student:实例代码块执行");
    }
    //静态代码块
    static {
        System.out.println("Student:静态代码块执行");
    }
}

///
public class TestDemo {
    public static void main(String[] args) {
        Student student1 = new Student("张三",19);
        System.out.println("===========================");
        Student student2 = new Student("李四",20);
    }
}

运行结果:
在这里插入图片描述

通过分析运行结果,得出以下结论:

  1. 父类静态代码块优先于子类静态代码块执行,且是最早执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类的实例代码块和子类构造方法紧接着再执行
  4. 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行

4. protected 关键字

讲到这个关键字,需要再把在 JAVA 初始类和对象(下) 这篇博客中的那张图再拿出来。
在这里插入图片描述
JAVA 初始类和对象(下) 这篇博客中,讲解了包的概念,介绍了defaultprivatepublic,当时我们也提到过子类的概念。

现在我们已经初步了解到了子类,所以,相信现在正在看这篇博客的你,应该能看懂这张图中的 不同包,子类,非子类…等都是什么意思了。

所以,现在我们只差 protected 没有介绍了,protected 的访问权限是:除了不同包下下的非子类都可以进行访问
可能还是有人会不知道,什么是不同包下的非子类?没事,我们用一张图来演示一下,你就可以大概明白一点了。

在这里插入图片描述

我们一共学了 4种访问修饰限定符,那么什么时候下用哪一种呢?

  1. 我们希望类要尽量做到 “封装”, 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
  2. 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限。例如如果一个方法能用 private, 就尽量不要用 public。
  3. 另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 “谁” 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)

虽然比较难区分,但是,当你继承和多态的代码写多了之后,你自然而然就知道怎么使用这四种不同的访问权限了,就像打篮球,你投篮投得多了,手型就会固定了,命中率也会上升的,所以,还是要多练,多练习,多实践,一定是学会一项技能的关键要素!

5. 继承的多种方式

在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:
在这里插入图片描述
java的继承方式,提供了这么几种:

5.1 单继承

class Animal {
    //...
}

class Dog extends Animal{
    //...
}

在这里插入图片描述

5.2 多层继承

class A {
    //...
}

class B extends A{
    //继承了 A类  拥有的成员方法或字段
    //...
}

class C extends B {
    // 继承了 A类 和 B类 拥有的成员方法或字段
    //...
}

在这里插入图片描述

5.3 不同的类,继承了同一个类

class Animal {
    //...
}

class Dog extends Animal {
    //...
}

class Cat extends Animal {
    //...
}

在这里插入图片描述

5.4 多继承(Java不支持)

在这里插入图片描述

注意:Java 不支持多继承!
但是,在后面的章节中,我们会学到 接口, 可以利用接口,实现多继承

5.5 Java的继承方式的总结:

  1. 时刻牢记,我们写的类是现实事物的抽象。真正的项目往往业务比较复杂,可能会涉及到一系列复杂的概念,都需要我们使用代码来表示,所以我们真实项目中所写的类也会有很多类之间的关系也会更加复杂
  2. 但是即使如此,我们并不希望类之间的继承层次太复杂。一般我们不希望出现超过三层的继承关系。如果继承层次太多, 就需要考虑对代码进行重构了。
  3. 如果想从语法上进行限制继承,就可以使用 final 关键字

6. final 关键字的介绍

final关键字可以用来修饰变量、成员方法以及类。

6.1 final 修饰变量或字段

final 修饰变量或字段,表明该变量的值,是一个定值(常量),不能被修改

这是一段错误代码的演示:

public class Test {
    public static void main(String[] args) {
        final int a = 10;
        a = 20; //编译出错
    }
}

在这里插入图片描述

6.2 final 修饰类

使用 final 关键字修饰类,表示这个类,不可以被继承!!!

错误代码演示:

final class Animal {

}

class Dog extends Animal {

}
//Error:无法从final 'TestDemo1.Animal' 继承

在这里插入图片描述

6.3 final 修饰方法

使用 final关键字修饰方法,表示该方法不能被重写!!!

什么是重写?后面讲到多态的时候,会介绍到重写,也会介绍到被使用 final关键字修饰方法,该方法不能被重写的知识点。

7. 继承和组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段

如何区别 继承 和 组合 呢?
我们可以将 继承理解为:is - a 的关系,将 组合理解为:has - a 的关系
举个例子:
继承:Dog extends Animal --> Dog is a animal(狗是一种动物)

class Animal {

}

class Dog extends Animal {

}

组合:以学校为例子:学校里面有老师和学生,School has teachers and students

class School {
    Teacher[] teachers = new Teacher[10];
    Student[] students = new Student[10];
}

class Teacher {
    
}

class Student {
    
}

这就是组合,将一个类的实例化对象,作为另一个类的成员变量

我们知道,上面代码的 School,Teacher,Student类,都是自定义的类,属于引用类型,可以创建数组,所以上面这段代码的意思就是,School类里,实例化了10个老师对象 和 10个学生对象,用生活中的语言翻译就是:这个学校里面,有10个老师和10个学生。

组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般而言,在项目中,使用更多的是组合,但项目中,肯定也需要使用继承。
这里有个文章链接,可以点进去看看,继承和组合,里面有关于继承和组合的一些详细介绍。

8. 总结

到这里,继承介绍完了,需要学习继承的读者,看完 java 继承(上)和本篇博客,相信你会对继承有一个全面认识,具体掌握继承,还是需要把 java 继承(上)和本篇博客的代码都去写一写,自己实现继承和继承中的各种知识点,这样才能算是真正的初步掌握继承,为后面学习多态的时候,打下基础。

最后,如果这篇文章能帮到你,希望你点个赞,如果有错误,也欢迎你评论指出,谢谢!