• 继承的使用以及细节问题


    目录

    一、继承的基本介绍

    二、继承给编程带来的便利

    三、继承使用的细节问题

    四、继承本质分析(重要)

    五、继承练习题

    六、super关键字

    6.1 基本介绍

    6.2 基本语法

    6.3 super 给编程带来的便利/细节

    6.4 Super使用细节

    6.4.1 演示访问方法的规则

    6.4.2 演示访问属性的规则

    Super和this的比较


    一、继承的基本介绍

    继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。 

    二、继承给编程带来的便利

    1. 代码的复用性提高了
    2. 代码的扩展性和维护性提高了

    三、继承使用的细节问题

    (1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

    (2) 子类必须调用父类的构造器, 完成父类的初始化

    Base类是一个父类,里面有无参构造,Sub类继承了Base类,此时new一个Sub对象的时候,先调用父类的构造器,然后调用Sub的构造器

    (3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。) [举例说明]

    当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器

    这里有一个父类Base

    1. public class Base {
    2. private int n1 = 100;
    3. private int n2 = 200;
    4. private int n3 = 300;
    5. public Base() {
    6. System.out.println("父类Base()构造器被调用...");
    7. }
    8. }

     子类Sub

    1. public class Sub extends Base{
    2. public Sub() {
    3. System.out.println("子类Sub()构造器被调用");
    4. }
    5. public Sub(String name){
    6. System.out.println("子类Sub(String name)构造器被调用");
    7. }
    8. }

    测试类

    1. public class Test {
    2. public static void main(String[] args) {
    3. Sub sub = new Sub();
    4. System.out.println("======第二个对象======");
    5. Sub sub1 = new Sub("jack");
    6. }
    7. }


    如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过 

    在Base加个有参构造器,然后把无参构造器删除,这时候我们写的构造器就把默认的无参构造器覆盖了,无参构造器就没有了

    这时候去保存代码的时候,会发现,子类的构造器报错了

    那么此时需要用带参数构造器,用到super

    (4)如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)  

    1. public class Base {
    2. private int n1 = 100;
    3. private int n2 = 200;
    4. private int n3 = 300;
    5. public Base() {
    6. System.out.println("父类Base()构造器被调用...");
    7. }
    8. public Base(String name, int age) {
    9. System.out.println("父类Base(String name,int age)构造器被调用...");
    10. }
    11. public Base(String name) {
    12. System.out.println("父类Base(String name)构造器被调用...");
    13. }
    14. }
    1. public class Sub extends Base{
    2. public Sub() {
    3. super("smith",10);
    4. System.out.println("子类Sub()构造器被调用");
    5. }
    6. public Sub(String name,int age) {
    7. //1.调用父类的无参构造器,或者什么都不写,默认就是调用父类的无参构造器
    8. super();
    9. System.out.println("子类Sub(String name,int age)构造器被调用");
    10. }
    11. public Sub(String name){
    12. super("tom",30);
    13. System.out.println("子类Sub(String name)构造器被调用");
    14. }
    15. }
    1. public class Test {
    2. public static void main(String[] args) {
    3. System.out.println("======第三个对象======");
    4. Sub sub2 = new Sub("king",10);
    5. }
    6. }

    如果此时我们要调用父类的Base(String name)这个构造器,此时我们这样做

    (5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)

    (6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器

    (7) java 所有类都是 Object 类的子类, Object 是所有类的基类

    (8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)

    (9)子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。 思考:如何让 A 类继承 B 类和 C 类?

    【让A 继承 B, B 继承 C】

    (10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系


    四、继承本质分析(重要)

    1. public class ExtendsTheory {
    2. public static void main(String[] args) {
    3. Son son = new Son();
    4. }
    5. }
    6. class GrandPa {
    7. String name = "大头爷爷";
    8. String hobby = "旅游";
    9. }
    10. class Father extends GrandPa {
    11. String name = "大头爸爸";
    12. int age = 39;
    13. }
    14. class Son extends Father {
    15. String name = "大头儿子";
    16. }

    当我们去new一个Son的时候,在内存里面发生了什么?

    这里我们new一个Son,Son的父类是Father,而Father的父类是GrandPa,因为是先加载父类。在加载Son类信息的时候,会先查找父类,先加载父类信息,但是GrandPa也有一个父类,首先加载的是Object类,然后加载GrandPa,然后是Father类,最后加载Son。并且它们之间还有一种关联关系。

    这些东西好了之后,就在堆中分配地址空间,那么这个空间里面到底有什么属性??

    它首先会在GrandPa类分配属性,那么这里面由于是有值的,大头爷爷是放在常量池的,name的值用一个地址来表示,指向常量池,hobby也是。

    接下来是Father类,我们会发现Father有name属性,GrandPa也有name属性,这样是不会冲突的,因为还会开辟一个空间,里面存放Father的属性值,name的属性值:大头爸爸,也是存放在常量值,然后用一个地址来指向常量池。

    最后就是Son这个类了,又开辟一个空间,里面存放属性。


     那么当我们用son去访问name、age、hobby的时候,规则又是什么?

    System.out.println(son.name);

    这个本类中有,直接返回大头儿子

    这时候我们就要按照查找关系来返回信息

    (1) 首先查看子类是否有该属性
    (2) 如果子类有这个属性,并且可以访问,则返回信息
    (3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息)
    (4) 如果父类没有,就按照(3)的规则,继续找上级父类,直到Object

    System.out.println(son.age);

    这个Son类中没有,所以往上找自己的父类,父类里有age属性,那么这个age就是39。

    System.out.println(son.hobby)

    Son类没有,往Father找,Father也没有,去找GrandPa,这里面有,那么返回的就是旅游

    1. public static void main(String[] args) {
    2. Son son = new Son();
    3. System.out.println(son.name); //大头儿子
    4. System.out.println(son.age); //39
    5. System.out.println(son.hobby); //旅游
    6. }

    那么现在又有一个问题,当Father类中的age属性,我们用private去修饰的时候,这时候new了对象过后,在内存里面还有没有这个age?

    答案是这个age属性还是有的,但是我们不能去访问,私有只能在本类访问,我们可以写一个公有方法来使用

      


    但是这里又会有一个有意思的问题,当Father类里的age属性值是私有的时候,此时GrandPa类有个public的age属性值,我们通过:son.age的时候,会不会跳过Father类这个私有的,到GrandPa里面去找这个公有的Age呢? 

    答案是不会的,因为在Father里面已经找到了这个age,只是被设为私有不能访问而已。 


    五、继承练习题

    Computer.java

    1. public class Computer {
    2. private String cpu;
    3. private int memory;
    4. private int disk;
    5. public Computer(String cpu, int memory, int disk) {
    6. this.cpu = cpu;
    7. this.memory = memory;
    8. this.disk = disk;
    9. }
    10. public String getCpu() {
    11. return cpu;
    12. }
    13. public void setCpu(String cpu) {
    14. this.cpu = cpu;
    15. }
    16. public int getMemory() {
    17. return memory;
    18. }
    19. public void setMemory(int memory) {
    20. this.memory = memory;
    21. }
    22. public int getDisk() {
    23. return disk;
    24. }
    25. public void setDisk(int disk) {
    26. this.disk = disk;
    27. }
    28. public String getDetails() {
    29. return "cpu="+cpu+" memory="+memory+" disk="+disk;
    30. }
    31. }

    PC.java

    1. public class PC extends Computer{
    2. private String brand;
    3. public PC(String cpu, int memory, int disk, String brand) {
    4. super(cpu, memory, disk);
    5. this.brand = brand;
    6. }
    7. public String getBrand() {
    8. return brand;
    9. }
    10. public void setBrand(String brand) {
    11. this.brand = brand;
    12. }
    13. public void printInfo(){
    14. System.out.println("PC信息如下:");
    15. System.out.println(getDetails()+" brand="+brand);
    16. }
    17. }

    因为继承Computer,这里生成构造器的时候,会自动把父类的另外属性给你,子类的属性brand由自己初始化,父类由父类初始化,也就是super(cpu,memory,disk)

    NotePad.java

    1. public class NotePad extends Computer{
    2. private String color;
    3. public NotePad(String cpu, int memory, int disk, String color) {
    4. super(cpu, memory, disk);
    5. this.color = color;
    6. }
    7. public String getColor() {
    8. return color;
    9. }
    10. public void setColor(String color) {
    11. this.color = color;
    12. }
    13. public void printInfo(){
    14. System.out.println(getDetails()+" color="+color);
    15. }
    16. }

    Test.java 

    1. public class Test2 {
    2. public static void main(String[] args) {
    3. PC pc = new PC("Intel",32,500,"IBM");
    4. pc.printInfo();
    5. NotePad notePad = new NotePad("AMD", 64, 1000, "white");
    6. notePad.printInfo();
    7. }
    8. }

    六、super关键字

    6.1 基本介绍

    super 代表父类的引用,用于访问父类的属性、方法、构造器


    6.2 基本语法

    1. public class A {
    2. public int n1=100;
    3. protected int n2=200;
    4. int n3=300;
    5. private int n4=400;
    6. public void test100(){}
    7. protected void test200(){}
    8. void test300(){}
    9. private void test400(){}
    10. }
    1. public class B extends A {
    2. public void hi() {
    3. System.out.println(super.n1+" "+super.n2+" "+super.n3);
    4. }
    5. public void ok(){
    6. super.test100();
    7. super.test200();
    8. super.test300();
    9. }
    10. }

    6.3 super 给编程带来的便利/细节


    6.4 Super使用细节

    6.4.1 演示访问方法的规则

    这里就举个例子,这里有A类和B类

    1. public class A {
    2. public void sum(){
    3. System.out.println("B类的sum()方法...");
    4. }
    5. public void cal(){
    6. System.out.println("A类的cal()方法...");
    7. }
    8. }
    1. public class B extends A {
    2. public void sum(){
    3. System.out.println("B类的sum()方法...");
    4. cal();
    5. }
    6. }

    我们想调用A类的cal()这个方法的时候,有3种办法:

    (1)直接写cal(),它的查找顺序就是先从本类进行查找,如果没有则向上找父类,如果再没有还是继续往上找,直到Object

    (2)可以直接写this.cal(),这个效果和直接写cal()一样,也是从本类开始找

    (3)可以写成super.cal(),这个是没有查找本类的过程,而是直接进入到父类里面去找super的cal()方法,如果本类有cal()方法,这里不会去找。

    为了演示第三种方法,此时我们在B类也增加一个cal方法

    1. public void cal(){
    2. System.out.println("B类的cal()方法...");
    3. }

    这里输出的是B类的cal()方法。


    6.4.2 演示访问属性的规则

    属性的访问规则和方法也是一样的,这里也举2个类举例一下

    1. public class A {
    2. public int n1=100;
    3. protected int n2=200;
    4. int n3=300;
    5. private int n4=400;
    6. public void test100(){}
    7. protected void test200(){}
    8. void test300(){}
    9. private void test400(){}
    10. public void sum(){
    11. System.out.println("B类的sum()方法...");
    12. }
    13. public void cal(){
    14. System.out.println("A类的cal()方法...");
    15. }
    16. }
    1. public class B extends A {
    2. public void hi() {
    3. System.out.println(super.n1 + " " + super.n2 + " " + super.n3);
    4. }
    5. public void ok() {
    6. super.test100();
    7. super.test200();
    8. super.test300();
    9. }
    10. public void sum() {
    11. System.out.println("B类的sum()方法...");
    12. super.cal();
    13. //演示访问属性的规则
    14. //n1 和 this.n1 查找的规则是
    15. //(1) 先找本类,如果有,则调用,此时本类是有的
    16. System.out.println(n1); //100
    17. System.out.println(this.n1); //100
    18. System.out.println(super.n1); //100
    19. }
    20. public void cal() {
    21. System.out.println("B类的cal()方法...");
    22. }
    23. }

    这时候,B类中没有n1属性,此时调用sum()会打印出3个100,如果本类中加个n1=888,此时输出的顺序就是888,888,100

    主要注意的是用super调用的时候,会直接去调用父类的方法和属性,而不考虑本类的。



     

    基于刚刚的例子,我们多加一个Base类  

    1. public class Base {
    2. public int n1 = 999;
    3. public int age = 111;
    4. public void cal() {
    5. System.out.println("Base类的cal()方法...");
    6. }
    7. public void eat() {
    8. System.out.println("Base类中的eat()方法...");
    9. }
    10. }

    此时我们写在B类中写test方法

    1. //编写测试方法
    2. public void test(){
    3. System.out.println("super.n1="+super.n1); //直接定位到A类去找,输出:100
    4. super.cal(); //定位到A类去找,因为A类中有cal()方法
    5. }

     如果A类中没有cal()方法,那么就会去Base类中去找cal()方法,这里遵循就近原则

    七、Super和this的比较

  • 相关阅读:
    【C++高阶(一)】二叉搜索树深度剖析
    实用调试技巧(2)
    SpringBoot配置加载优先级
    外贸独立站的运营效果到底如何
    Apache Doris 系列: 入门篇-创建数据表
    南昌云宸网络发展有限公司-小分类客户可自选
    为什么推荐从Linux开始了解IT技术
    【官宣】游戏革命刚刚开始!
    在 uniapp 中 一键转换单位 (px 转 rpx)
    23.Visitor访问者(行为型模式)
  • 原文地址:https://blog.csdn.net/qq_44706176/article/details/126201783