灌篮少年 发表于 2025-1-21 00:44:08

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

目次
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 继承的概念

继承机制:它允许程序员在保持原有类特性的基础上举行扩展,增加新功能,如许产生新的类,称为派生类。继承出现了面向对象程序设计的层次布局, 体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用。
https://i-blog.csdnimg.cn/direct/ca4d8d993e7641c28a932ff921c23b19.png
上述图示中,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();
    }
} https://i-blog.csdnimg.cn/direct/5856c7b0b2b44a1594dba920e12224d9.png
这里的Dog(狗类)继承了Animal(动物类),所以dog的名字也继承了父类Animal的名字“小小”。
同时父类能做的事情,子类继承过来也能做,比如这里的dog可以使用父类的sleep()方法。
而且子类添加了本身特有的成员——bark()成员方法。


1.3 继承方式

在现实生存中,事物之间的关系是非常复杂,灵活多样,比如:
https://i-blog.csdnimg.cn/direct/2767d44e9fa249d98f7f1711a73d2435.png
类似的,java中有这3种继承关系:(箭头端表现子类)
1. 单继承
https://i-blog.csdnimg.cn/direct/947c934daac349f7b4a304104e52316e.png
2. 多层继承
https://i-blog.csdnimg.cn/direct/3d2c5ba47e5f4c0cb2bbf0bd810921de.png
3. 不同类继承自同个类(一个父类可以有多个子类) 
https://i-blog.csdnimg.cn/direct/9d3fdba916314196bfd890a103d0d03b.png
   注意:java中不存在多继承的关系,即一个子类不能有多个父类。
https://i-blog.csdnimg.cn/direct/fc58ee9aac9946ed9933b079d3bf463d.png

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();
    }
} https://i-blog.csdnimg.cn/direct/8c1340f0ca444bfe9490c4cbdbab3d77.png
结果都是20,而不是父类的10,阐明访问的是子类的a。
   继承类中的成员变量访问规则:子类优先,父类其次。

[*]子类无,父类无:报错。
[*]子类有,父类无:访问子类的成员变量。
[*]子类无,父类有:访问父类的成员变量。(向上探求)
[*]子类有,父类有 [重名]:优先访问子类的成员变量。

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();
    }
} https://i-blog.csdnimg.cn/direct/96b194f97bba4976855ba41f595bb9be.png

由这个例子我们可以总结出访问子类的成员方法的规则。
   继承类中的成员方法访问规则:子类优先,父类其次;重载看参数,重写用子类。

[*]子类无,父类无:报错。
[*]子类有,父类无:访问子类方法。
[*]子类无,父类有:访问父类方法。(向上探求)
[*]子类有,父类有 [重名]:
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();
    }
} https://i-blog.csdnimg.cn/direct/9617147e3b51454eba058485ed12a23d.png

在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。
   注意:super关键字不能用于静态代码块、静态变量和静态方法。
因为super是对子类对象的父类的引用,必要子类对象的创建,而静态的成员不依靠于对象。

4. super、this 与 构造方法

4.1 子类中的super()调用

4.1.1 父类只有无参构造方法

   当父类只有 无差的构造方法 或 无构造方法时,子类构造方法中默认会调用父类的无参构造方法:super()。【super()默认是子类构造方法的第一条语句。】
在子类构造方法中,并没有写任何关于父类构造的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法。
因为:子类对象中成员是由两部门组成的,基类继承下来的以及子类新增加的部门 。父子父子,肯定是先有父再有子,所以在构造子类对象时间 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类本身的构造方法,将子类本身新增加的成员初始化完整 。
例如:
https://i-blog.csdnimg.cn/direct/6f5066e5d5a6490c9938e246667c80ea.png

4.1.2 父类有带参构造方法时

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

[*]用户要为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用。
[*]该子类构造方法的第一句是super(...)。
例如:
https://i-blog.csdnimg.cn/direct/a5f7191dd3264aadbefe2abed0a0b3df.png

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

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

[*]不能用于静态变量、静态方法 和 静态代码块。
[*]显式使用时,必须是构造方法中的第一条语句。
    【不同点】

[*]this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。
[*]构造方法中肯定会存在super(...)的调用,用户不写编译器也会增加,但是this(...)用户不写则没有。
    【注意】
this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造 方法中出现。
因为super和this要显式使用时,都必须是构造方法的第一句,但两者又不可能同时是第一句。
例如:
https://i-blog.csdnimg.cn/direct/017962d2c38d41fab7ae30eb97aaf284.png
https://i-blog.csdnimg.cn/direct/8ccaf9b9676741ad853992797ffed6f9.png
https://i-blog.csdnimg.cn/direct/5251262c6ded4c6dbe6798bc501b85ed.png

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);//第二次无类加载,不再执行静态代码块的内容
    }
} https://i-blog.csdnimg.cn/direct/bcdea76528dd402f99d4dbb00d729043.png
由这个结果可以总结出无继承关系时的执行次序:
   无继承关系时:
【类未加载】
  静态代码块 --> 实例代码块 --> 构造方法
【类已加载】
  实例代码块 --> 构造方法
    补充:

[*]静态变量的初始化、静态代码块和静态方法的代码都是同一个时期执行的。(执行的次序由代码文本的上下次序决定)
[*]静态成员执行完后,接下来就是成员变量、实例代码块和成员方法的执行时期。(执行的次序也是由代码文本的上下次序决定)
[*]构造方法最后执行。
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);
      }
} https://i-blog.csdnimg.cn/direct/80475772bc4e49a1beb49edd8fdff1de.png
由该例子可以得出以下执行次序:
   有继承关系时:(创建子类对象)
【父类子类均未加载】
父类静态 --> 子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
【父类已加载、子类未加载】
子类静态 --> 父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
【父类子类都已加载】
父类实例 --> 父类构造方法 --> 子类实例 --> 子类构造方法
图示:
https://i-blog.csdnimg.cn/direct/e9b4e6252ffb49bfbfb1a6437755c332.png
6. protected的包访问权限

在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其 他包中被访问。
我们来回忆一下这些关键字:
   https://i-blog.csdnimg.cn/direct/071eaeced7244a6ea59ef899d4c72850.png
如果把不同的包比作不同的村落,把子类和父类比作家属成员的话,那么:
public:可以理解为一个人的外貌和声誉,谁都可以看得到。(不同包中的不同类都可以访问)
protected:对于同一个乡村的村民当然知道你的外面特征和声誉(个同一包中的不同类可以访问);而对于其他地方或城市,也是你的亲戚和家属成员比力了解你一些(不同包中的子类和父类)。
无修饰(default):只有同一个乡村的人才知道你。(同一包中的不同类可以访问)
private:只有本身知道,其他人都不知道。
举例阐明:
   [同一个包]
其他类(子类+非子类):可使用的权限是public、protected、default
https://i-blog.csdnimg.cn/direct/00f11f7cf54e4162b986d2505a2cd4fe.png
   [不同包]
是继承类:可使用的权限是public、protected
非继承类:可使用的权限是public
https://i-blog.csdnimg.cn/direct/611b15aa5621428d98963123d8f9ad8f.png
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 都是常量,常量的值不能修改,所以会运行报错:
https://i-blog.csdnimg.cn/direct/9f2b8910b804472495742776d5f9cfde.png
https://i-blog.csdnimg.cn/direct/c975f592e0cf490995f2ae4d7b8c6f61.png
   

[*] 基本数据范例变量:当 final 修饰基本数据范例变量时,该变量一旦被赋值后就不能再次改变其值。
[*] 引用数据范例变量:对于引用数据范例变量,final 关键字表现该变量的引用不能再指向其他对象,但对象本身的内容是可以修改的。

7.2 修饰类

   final修饰类时,表现此类不能被继承。
例如:
package demo1;
final public class Animal {      //Animal类被final修饰

}

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

} https://i-blog.csdnimg.cn/direct/d39d0b780d754c2a982347911bf642c1.png
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承:
https://i-blog.csdnimg.cn/direct/6c7cd3e400d44a20870572670bc80f8b.png

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♪(・ω・)ノ
https://i-blog.csdnimg.cn/direct/8c26040c9b8e41e78bc9758854bff3c0.gif

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 类和对象(3)——继承:extends关键字、super关键字、protected关键字、fina