类和对象(3)——继承:extends关键字、super关键字、protected关键字、final关键字

目录

[1. 继承](#1. 继承)

[1.1 继承的概念](#1.1 继承的概念)

[1.2 extends关键字](#1.2 extends关键字)

[1.3 继承方式](#1.3 继承方式)

2.继承类的成员访问

[2.1 成员变量](#2.1 成员变量)

[2.2 成员方法](#2.2 成员方法)

[3. super关键字](#3. super关键字)

[4. super、this 与 构造方法](#4. super、this 与 构造方法)

[4.1 子类中的super()调用](#4.1 子类中的super()调用)

[4.1.1 父类只有无参构造方法](#4.1.1 父类只有无参构造方法)

[4.1.2 父类有带参构造方法时](#4.1.2 父类有带参构造方法时)

[4.2 super 与 this 的异同与注意事项](#4.2 super 与 this 的异同与注意事项)

[5. 再谈初始化:类中代码的执行顺序](#5. 再谈初始化:类中代码的执行顺序)

[5.1 无继承关系](#5.1 无继承关系)

[5.2 有继承关系](#5.2 有继承关系)

[6. protected的包访问权限](#6. protected的包访问权限)

[7. final关键字](#7. final关键字)

[7.1 修饰变量](#7.1 修饰变量)

[7.2 修饰类](#7.2 修饰类)

[8. 组合与继承](#8. 组合与继承)


1. 继承

1.1 继承的概念

继承机制: 它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。

继承主要解决的问题是:共性的抽取,实现代码复用。

上述图示中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或超类,Dog和Cat可以称为Animal的 子类/派生类。继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。

1.2 extends关键字

在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类名 extends 父类名 {

// ...

}

子类继承父类之后,建议要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

例如:

cpp 复制代码
// Animal.java
public class Animal {
    public String name = "小小";
    public int age = 3;

    public void sleep(){
        System.out.println(name+"正在睡觉");
    }
}

// Dog.java
public class Dog extends Animal{
    public void bark(){
        System.out.println(name+"在汪汪叫");
    }
}

// Test.java
public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println("小狗叫"+dog.name);
        dog.sleep();
        dog.bark();
    }
}

这里的Dog(狗类)继承了Animal(动物类),所以dog的名字也继承了父类Animal的名字"小小"。

同时父类能做的事情,子类继承过来也能做,比如这里的dog可以使用父类的sleep()方法。

而且子类添加了自己特有的成员------bark()成员方法。

1.3 继承方式

在现实生活中,事物之间的关系是非常复杂,灵活多样,比如:

类似的,java中有这3种继承关系:(箭头端表示子类)

1. 单继承

2. 多层继承

3. 不同类继承自同个类(一个父类可以有多个子类)

注意:java中不存在多继承的关系,即一个子类不能有多个父类。

2.继承类的成员访问

在刚刚的例子中,我们通过dog.name的方式访问到父类的name叫"小小",也通过dog.sleep的方式访问到父类的sleep方法。那如果Dog类中也有自己的name成员变量和sleep成员方法又会发生什么?

(子类的成员与父类的成员名字相同时,系统会访问子类的还是父类的?)

2.1 成员变量

cpp 复制代码
//父类
public class Dad {
    public int a = 10;
}

//子类
public class Child extends Dad{
    public int a = 20;

    {
        System.out.println("在代码块中输出a:"+ this.a);
    }

    public void printa(){
        System.out.println("使用成员方法输出a:"+ this.a);
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        //访问的是父类的a还是子类的a?
        System.out.println("通过子类对象访问a:"+child.a);
        child.printa();
    }
}

结果都是20,而不是父类的10,说明访问的是子类的a。

继承类中的成员变量访问规则:子类优先,父类其次。

  1. 子类无,父类无:报错。
  2. 子类有,父类无:访问子类的成员变量。
  3. 子类无,父类有 :访问父类的成员变量。(向上寻找)
  4. 子类有,父类有 [重名] :优先访问子类的成员变量。

2.2 成员方法

cpp 复制代码
//父类
 public class Dad {
    public void A(){
        System.out.println("Dad中的方法A()");
    }

    public void B(){
        System.out.println("Dad中的方法B()");
    }
}

//子类
public class Child extends Dad{
    public void A(int a) {
        System.out.println("Child中的方法A(int)");
    }

    public void B(){
        System.out.println("Child中的方法B()");
    }

    public void method(){
        A();      // 没有传参,访问父类中的A()
        A(20);    // 传递int参数,访问子类中的A(int)
        B();      // 直接访问,则永远访问到的都是子类中的B(),父类的无法访问到
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        Child child = new Child();        
        child.method();
    }
}

由这个例子我们可以总结出访问子类的成员方法的规则。

继承类中的成员方法访问规则:子类优先,父类其次;重载看参数,重写用子类。

  1. 子类无,父类无:报错。
  2. 子类有,父类无:访问子类方法。
  3. 子类无,父类有 :访问父类方法。(向上寻找)
  4. 子类有,父类有 [重名]
    a. 参数列表不同(构成方法重载):根据输入的参数来决定使用哪一个方法。
    b. 参数列表相同(构成方法重写):优先使用子类的方法。

3. super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成 员时,该如何操作?

直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。

cpp 复制代码
父类
public class Dad {
    public int a = 10;
    public void A(){
        System.out.println("Dad中的方法A()");
    }

}

子类
public class Child extends Dad{
    public int a = 20;
    public void A() {
        System.out.println("Child中的方法A()");
    }

    public void field(){
        System.out.println(a);       //子类的变量a
        System.out.println(super.a); //父类的变量a
    }

    public void method(){
        A();        //子类的方法A
        super.A();  //父类的方法A
    }
}

测试
public class Test {
    public static void main(String[] args) {
        Child child = new Child();
        child.field();
        child.method();
    }
}

在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

注意:super关键字不能用于静态代码块、静态变量和静态方法。

因为super是对子类对象的父类的引用,需要子类对象的创建,而静态的成员不依赖于对象。

4. super、this 与 构造方法

4.1 子类中的super()调用

4.1.1 父类只有无参构造方法

当父类只有 无差的构造方法 或 无构造方法时,子类构造方法中默认会调用父类的无参构造方法:super()。【super()默认是子类构造方法的第一条语句。】

在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法。

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

例如:

4.1.2 父类有带参构造方法时

如果父类构造方法是带有参数的,此时需要:

  1. 用户要为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用。
  2. 该子类构造方法的第一句是super(...)

例如:

4.2 super 与 this 的异同与注意事项

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

【相同点】

  1. 不能用于静态变量、静态方法 和 静态代码块。

  2. 显式使用时,必须是构造方法中的第一条语句。
    【不同点】

  3. this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。

  4. 构造方法中一定会存在super(...)的调用,用户不写编译器也会增加,但是this(...)用户不写则没有。
    【注意】

this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现

因为super和this要显式使用时,都必须是构造方法的第一句,但两者又不可能同时是第一句。

例如:

5. 再谈初始化:类中代码的执行顺序

我们知道,静态代码块、实例代码块和构造方法是在类加载时或者实例对象创建时执行的。那么它们执行的顺序是怎样的呢?

5.1 无继承关系

观察下面的代码,猜测一下在没有继承关系时的执行顺序

cpp 复制代码
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 Test {
    public static void main(String[] args) {
        System.out.println("第一次:");
        Person person1 = new Person("小明",10);//第一次触发类加载和静态代码块
        System.out.println("============================");
        System.out.println("第二次:");
        Person person2 = new Person("大明",20);//第二次无类加载,不再执行静态代码块的内容
    }
}

由这个结果可以总结出无继承关系时的执行顺序:

无继承关系时:

【类未加载】

静态代码块 --> 实例代码块 --> 构造方法

【类已加载】

实例代码块 --> 构造方法
补充:

  1. 静态变量的初始化、静态代码块和静态方法 的代码都是同一个时期执行的。(执行的顺序由代码文本的上下顺序决定)
  2. 静态成员执行完后,接下来就是成员变量、实例代码块和成员方法 的执行时期。(执行的顺序也是由代码文本的上下顺序决定)
  3. 构造方法最后执行

5.2 有继承关系

当存在继承关系时,子类的执行顺序是怎么样的?

cpp 复制代码
父类
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 Test {
        public static void main(String[] args) {
            System.out.println("第一次:");
            Student student1 = new Student("小明",19);
            System.out.println("===========================");
            System.out.println("第二次:");
            Student student2 = new Student("大明",20);
        }
}

由该例子可以得出以下执行顺序:

有继承关系时:(创建子类对象)

【父类子类均未加载】

父类静态 --> 子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法

【父类已加载、子类未加载】

子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法

【父类子类都已加载】

父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法

图示:

6. protected的包访问权限

在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其 他包中被访问。

我们来回忆一下这些关键字:

如果把不同的包比作不同的村子,把子类和父类比作家族成员的话,那么:

public:可以理解为一个人的外貌和声誉,谁都可以看得到。(不同包中的不同类都可以访问)

protected:对于同一个村庄的村民当然知道你的外面特征和声誉(个同一包中的不同类可以访问);而对于其他地方或城市,也是你的亲戚和家族成员比较了解你一些(不同包中的子类和父类)。

无修饰(default):只有同一个村庄的人才知道你。(同一包中的不同类可以访问)

private:只有自己知道,其他人都不知道。

举例说明:

[同一个包]

其他类(子类+非子类):可使用的权限是public、protected、default

[不同包]

是继承类:可使用的权限是public、protected

非继承类:可使用的权限是public

private修饰的,只能父类使用。

7. final关键字

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

7.1 修饰变量

final修饰变量或字段时,表示常量,不能修改。

(final修饰的范围包括成员变量和局部变量)

例如:

cpp 复制代码
public class Test {
    final static int a = 1;

    public static void main(String[] args) {
        final int b = 2;
        a = 2;
        b = 3;
    }
}

此时 成员变量a 和 局部变量b 都是常量,常量的值不能修改,所以会运行报错:

  • 基本数据类型变量 :当 final 修饰基本数据类型变量时,该变量一旦被赋值后就不能再次改变其值。

  • 引用数据类型变量 :对于引用数据类型变量,final 关键字表示该变量的引用不能再指向其他对象,但对象本身的内容是可以修改的。

7.2 修饰类

final修饰类时,表示此类不能被继承。

例如:

cpp 复制代码
package demo1;
final public class Animal {      //Animal类被final修饰

}

class Dog extends Animal{        //继承Animal类会报错

}

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承:


7.3 修饰方法:表示该方法不能被重写【放在下一篇文章中介绍】

8. 组合与继承

组合的思想:

和继承类似,组合也是一种表达类之间关系的方式。它允许我们将对象组合成树形结构以表示部分-整体的层次结构。

继承表示对象之间是is-a的关系,比如:狗是动物,猫是动物

组合表示对象之间是have-a的关系,比如:汽车中有方向盘、发动机、前照灯...

组合的实现:

组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字),仅仅是将一个类的实例作为另外一个类的字段

汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的:

cpp 复制代码
// 轮胎类
class Tire{
    // ...
}

// 发动机类
class Engine{
    // ...
}

// 车载系统类
class VehicleSystem{
    // ...
}
------------------------------------------------------------------------------------------------------------------------------------------------------【组合】
// 汽车类 将上述所有类组合起来
class Car{
    private Tire tire;          // 可以复用轮胎中的属性和方法
    private Engine engine;      // 可以复用发动机中的属性和方法
    private VehicleSystem vs;   // 可以复用车载系统中的属性和方法

    // ...
}
------------------------------------------------------------------------------------------------------------------------------------------------------【继承】

// 奔驰汽车类 继承自汽车类
class Benz extend Car{
    // 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}

这里的轮胎实例变量、发动机实例变量 和 车载系统实例变量 都作为 汽车类的成员变量,这就是组合。


本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ

相关推荐
热心网友俣先生1 分钟前
2026年第二十三届五一数学建模竞赛C题超详细解题思路+各问题可用模型推荐+部分模型结果展示
c语言·开发语言·数学建模
01漫游者6 分钟前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript
GottdesKrieges7 分钟前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU7 分钟前
Java高级开发进阶教程之系列
java·开发语言
leo825...11 分钟前
Claude Code Skills 清单(本地)
java·python·ai编程
csbysj202014 分钟前
SQL NULL 函数详解
开发语言
其实防守也摸鱼17 分钟前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp17 分钟前
Java进阶——如何查看Java字节码
java·开发语言
We་ct1 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
身如柳絮随风扬1 小时前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务