当前位置: 首页 > article >正文

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

目录

1. 继承

1.1 继承的概念

1.2 extends关键字

1.3 继承方式

2.继承类的成员访问

2.1 成员变量

2.2 成员方法

3. super关键字

4. super、this 与 构造方法

4.1 子类中的super()调用

4.1.1 父类只有无参构造方法

4.1.2 父类有带参构造方法时

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

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

5.1 无继承关系

5.2 有继承关系

6. protected的包访问权限

7. final关键字

7.1 修饰变量

7.2 修饰类

8. 组合与继承


1. 继承

1.1 继承的概念

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

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

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

1.2 extends关键字

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

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

         // ...  

}

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

例如:

// 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 成员变量

//父类
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 成员方法

//父类
 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关键字,该关键字主要作用:在子类方法中访问父类的成员。

父类
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. 显式使用时,必须是构造方法中的第一条语句

【不同点】

  1. this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
  2. 构造方法中一定会存在super(...)的调用,用户不写编译器也会增加,但是this(...)用户不写则没有

【注意】

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

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

例如:

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

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

5.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 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 有继承关系

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

父类
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修饰的范围包括成员变量局部变量

例如:

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修饰类时,表示此类不能被继承

例如:

package demo1;
final public class Animal {      //Animal类被final修饰

}

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

}

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


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

8. 组合与继承

组合的思想:

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

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

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

组合的实现:

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

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

// 轮胎类
class Tire{
    // ...
}

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

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

    // ...
}
——————————————————————————————————————————————————【继承】

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

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


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


http://www.kler.cn/a/510889.html

相关文章:

  • 大模型GUI系列论文阅读 DAY1:《基于大型语言模型的图形用户界面智能体:综述》
  • 口令攻击和钓鱼攻击
  • 基于32QAM的载波同步和定时同步性能仿真,包括Costas环的gardner环
  • TextButton组件的功能与用法
  • Redis可视化工具--RedisDesktopManager的安装
  • 【Pytorch实用教程】TCN(Temporal Convolutional Network,时序卷积网络)简介
  • SLAM 6 3Dto2D 的Pnp 和光束平移法
  • 医院挂号就诊系统设计与实现(代码+数据库+LW)
  • 红黑树封装map和set(c++版)
  • Vue3:当v-if和v-for同时使用时产生的问题和解决办法
  • AI Agent的总体概念:感知,记忆,规划,外部工具,执行
  • PTA乙级1001~1005【c++】
  • 线段树优化dp,abc389F - Rated Range
  • C++中.h文件中的实现方法
  • 云原生前端开发:打造现代化高性能的用户体验
  • Kotlin Bytedeco OpenCV 图像图像54 透视变换 图像矫正
  • C#如何获取电脑中的端口号和硬件信息
  • Observability:最大化可观察性 AI 助手体验的 5 大提示(prompts)
  • 游戏开发中常用的设计模式
  • 大数据中 TopK 问题的常用套路
  • 基于.NetCore+Vue的贫困地区儿童资助系统
  • GraphRAG: Auto Prompt Tuning 实践
  • 一文大白话讲清楚webpack基本使用——1——完成webpack的初步构建
  • PyTorch使用教程- Tensor包
  • windows 远程链接 Ubuntu 24.04 LTS 图形界面
  • 机器狗:科技新宠,未来已来