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

devtools/2025/1/20 17:20:55/

目录

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);       //子类的变量aSystem.out.println(super.a); //父类的变量a}public void method(){A();        //子类的方法Asuper.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.ppmy.cn/devtools/152144.html

相关文章

电路研究9——GPRS用的AT命令手册

因为用到了GPRS模块,是合宙的所以就用到了他给的AT指令了,这个是在合宙网上扒下来的手册,便于到时使用: 补:本来再模块里面说的是AirM2M 无线模块AT命令手册,但是我查到了的是网上百度2024年6月的版本&…

Android系统定制APP开发_如何对应用进行系统签名

前言 当项目开发需要使用系统级别权限或frame层某些api时,普通应用是无法使用的,需要在AndroidManifest中配置sharedUserId: AndroidManifest.xml中的android:sharedUserId“android.uid.system”,代表的意思是和系统相同的uid&a…

一些面试常见问题及其回答参考

1、请你自我介绍一下你自己? 回答提示:一般人回答这个问题过于平常,只说姓名、年龄、爱好、工作经验,这些在简历上都有。其实,企业最希望知道的是求职者能否胜任工作,包括:最强的技能、最深入研…

Node.js path.join

path.join 是 Node.js 中的 path 模块提供的一个方法,用于连接多个路径片段并规范化路径。与 path.resolve不同,path.join 只是将给定的路径片段合并为一个单一的路径,并且不会自动转换为绝对路径,它只会拼接并返回一个规范化的路…

Perl语言的数据库编程

Perl语言的数据库编程 近年来,随着互联网和数据技术的发展,数据库编程变得越来越重要。在众多编程语言中,Perl因其强大的文本处理能力而受到许多开发者的青睐。虽然Perl在网页开发和系统管理中起着重要的作用,但在数据库编程方面…

QT信号槽 笔记

信号与槽就是QT中处理计算机外设响应的一种机制 比如敲击键盘、点击鼠标 // 举例: 代码: connect(ls,SIGNAL(sig_chifanla()),ww,SLOT(slot_quchifan())); connect(ls,SIGNAL(sig_chifanla()),zl,SLOT(slot_quchifan()));connect函数:这是…

vue2与vue3的区别

目录 1. 性能 2. 组合式 API 3. 生命周期钩子 4. 片段(Fragments) 5. 递归组件 6. 自定义渲染器 7. 全局 API 8. 组件内部的 this 9. 模板语法 10. 兼容性 总结 Vue 2 和 Vue 3 是 Vue.js 框架的两个主要版本,它们在多个方面有所不…

MyBatisPlus--分页插件

文章目录 MyBatisPlus自带分页插件添加配置类或在启动类中配置分页插件测试 自定义分页在UserMapper中定义接口方法UserMapper.xml中编写SQL测试 MyBatisPlus自带分页插件 MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能 添加配置类或在启动类中配置分…