JavaSE语法之八:继承(超全!!)

发布时间:2023年12月21日

一、继承的概念

Java中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,现实世界错误复杂,事物之间可能会存在一些联系,比如:狗和猫,它们都是一个动物。
按我们之前学到,猫和狗有一些相同的动物属性,代码写起来就比较冗余,所以引出继承。

继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。

继承最大的意义:对代码可以进行复用。
子类 extends 父类:is-a 的关系

二、继承的语法

修饰符 class 子类 extends 父类 {
// …
}

// Animal.java
public class Animal{
	String name;
	int age;
	public void eat(){
		System.out.println(name + "正在吃饭");
	}
	public void sleep(){
		System.out.println(name + "正在睡觉");
	}
}

// Dog.java
public class Dog extends Animal{
	void bark(){
	System.out.println(name + "汪汪汪~~~");
}
// Cat.Java
public class Cat extends Animal{
	void mew(){
	System.out.println(name + "喵喵喵~~~");
	}
}
// TestExtend.java
public class TestExtend {
	public static void main(String[] args) {
		Dog dog = new Dog();
	// dog类中并没有定义任何成员变量,name和age属	性肯定是从父类Animal中继承下来的
		System.out.println(dog.name);
		System.out.println(dog.age);
		// dog访问的eat()和sleep()方法也是从Animal中继承下来的
		dog.eat();
		dog.sleep();
		dog.bark();
	}
}

注意:

  1. 子类会将父类中的成员变量或者成员方法继承到子类中;
  2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了。

三、父类成员访问

1.子类中访问父类的成员变量

(1)子类和父类不存在同名成员变量
子类又有,就拿子类的;子类没有,就拿父类的。

//父类
public class Base {
	int a;
	int b;
}

//子类
public class Derived extends Base{
	int c;
	
	public void method(){
		a = 10; // 访问从父类中继承下来的a
		b = 20; // 访问从父类中继承下来的b
		c = 30; // 访问子类自己的c
	}
}

(2)子类和父类成员变量同名
如果父类和子类有重名的成员变量,优先使用子类的(就近原则)。

//父类
public class Base {
	int a = 1;
	int b = 2;
	int c = 3;
}
//子类
public class Derived extends Base{
	int a; // 与父类中成员a同名,且类型相同
	char b; // 与父类中成员b同名,但类型不同
	
	public void method(){
		a = 100; // 访问子类自己新增的a
		b = 101; // 访问子类自己新增的b
		c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
	// d = 103; // 编译失败,因为父类和子类都没有定义成员变量b

	super.a; //是超类,暂且理解为引用的父类(就想访问父类)
}
}

在子类方法中 或者 通过子类对象访问成员时:

  • 如果访问的成员变量子类中有,优先访问自己的成员变量;
  • 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类中也没有定义,则编译报错;
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。

成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。

1.子类中访问父类的成员方法

在成员方法名不同时,在子类方法中或者通过子类对象访问方法时:
①优先访问自己的;
②如果自己没有,再到父类找;
③如果父类中也没有,则报错。
(1)成员方法名字不同

//父类
public class Base {
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
}
//子类
public class Derived extends Base{
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}

	public void methodC(){
		methodB(); // 访问子类自己的methodB()
		methodA(); // 访问父类继承的methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
}
}

(2)成员方法名字相同
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法传递参数选择合适的方法访问,如果没有则报错;

//父类
public class Base {
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
		public void methodB(){
		System.out.println("Base中的methodB()");
	}
}
//子类
public class Derived extends Base{
	public void methodA(int a) {
		System.out.println("Derived中的method(int)方法");
	}
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
}

	public void methodC(){
		methodA(); // 没有传参,访问父类中的methodA()
		methodA(20); // 传递int参数,访问子类中的methodA(int)
		methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
		super.methodB(); //访问父类的methodB()方法
}
}

四、super 关键字

主要作用:在子类方法中访问父类的成员。
super只是一个关键字,最大的作用其实是在你写代码或者读代码的时候,体现出更好的可读性!

【注意事项】:

  1. 只能在非静态方法中使用;
  2. 在子类方法中,访问父类的成员变量和方法;
  3. 只能指代直接的父类,不能访问爷爷类。

五、子类构造方法

父子父子,先有父再有子。
即:子类对象构造时,需要先调用基类构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。

//父类/基类
public class Base {
	public Base(){
		System.out.println("Base()");
	}
}

//子类
public class Derived extends Base{
	public Derived(){
	// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
  // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
  // 并且只能出现一次
		System.out.println("Derived()");
	}
}

 public class Test {
	public static void main(String[] args) {
		Derived d = new Derived();
}
}

执行结果:
Base()
Derived()

在继承关系上,我们什么构造方法都没写的时候,编译器会有默认的以下两个构造方法:

//父类
public Animal() {
}

//子类
public Dog() {
	super();
}

其中,关于super:
① 注意子类构造方法中默认会调用基类的无参构造方法:super();
② 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句;
③ super只能出现一次,且不能和this同时出现。

【注意】
① 若父类显示定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法;
② 如果父类构造方法是带参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败;

六、super 和 this

在调用构造方法的时候,不能同时出现!

【相同点】

  • 都是Java中的关键字;
  • 只能在类的非静态方法中使用,用来访问非静态成员方法和字段;
  • 在构造方法中调用时,必须是 构造方法中的第一条语句,并且不能同时存在。

【不同点】

  • this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象从父类继承下来部分成员的引用;
  • 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性;
  • 在构造方法中,this(…) 用来调用本类构造方法,super(…) 用于调用父类构造方法,两种调用不能同时在构造方法中出现;
  • 构造方法中一定会存在super(…)

七、初始化

之前我们学的实例代码块和静态代码块,在没有继承关系时的执行顺序。

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 TestDemo {
    public static void main(String[] args) {
        Person person1 = new Person("张三",10);
        System.out.println("============================");
        Person person2 = new Person("李四",20);
    }
}

执行结果:
静态代码块执行
实例代码块执行
构造方法执行
====================
实例代码块执行
构造方法执行
  1. 静态代码块先执行,并且执行一次,在类加载阶段执行;
  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 TestDemo4 {
    public static void main(String[] args) {
        Student student1 = new Student("张三",19);
        System.out.println("===========================");
        Student student2 = new Student("gaobo",20);
}

//执行结果
Person:静态代码块执行
Student:静态代码块执行
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行
===========================
Person:实例代码块执行
Person:构造方法执行
Student:实例代码块执行
Student:构造方法执行

通过分析执行结果,得出以下结论:
① 父类静态代码块优先于子类静态代码块执行,且是最早执行;
②父类实例代码块和父类构造方法紧接着执行;
③ 子类的实例代码块和子类构造方法紧接着再执行;
④第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行。
父类优先于子类执行,静态的最先执行。

八、protected关键字

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

1. 同类

package demo1;
public class TestDemo1 {
    protected  int a = 99;

    public static void main(String[] args) {
        TestDemo1 testDemo1 = new TestDemo1();
        System.out.println(testDemo1.a);
    }
}

2. 同包不同类

package demo1;
public class TestDemo1 {
    protected  int a = 99;
}
package demo1;
public class TestDemo2 {
    public static void main(String[] args) {
        TestDemo1 testDemo1 = new TestDemo1();
        System.out.println("不同类" + testDemo1.a);
    }
}

3. 不同包的子类
在不同包的继承子类中,注意:
① 不能直接在main方法中引用,因为他是受保护的,所以要使用super发来访问;
② 新建一个方法写super,因为静态方法中不能用super。

package demo1;
public class TestDemo1 {
    protected  int a = 99;
    private int b;
}
package demo2;
import demo1.TestDemo1;
public class TestDemo3 extends TestDemo1 {
    public void func() {
    //再写一个方法放super,不直接写道main方法中,是因为main方法是静态方法,静态方法中不能放super
        System.out.println(super.a);
       // System.out.println(super.b); //编译报错,父类private成员再相同包子类中不可见
    }
    

    public static void main(String[] args) {
        TestDemo3 testDemo3 = new TestDemo3();
        testDemo3.func();
    }
}

其中:类必须都是public
注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了。

什么时候下用哪一种呢?
我们希望类要尽量做到“封装”,即隐藏内部实现细节,只需暴露出必要的信息给类的调用者。
因此我们在使用的时候,应尽可能的使用比较严格的访问权限。例如,如果一个方法能用private,就尽量不要用public。
另外还有一种粗暴的做法:将所有的字段设为 private,将所有的方法设为 public。 不过这种方式属于是对访问权限的滥用,我们在写代码的时候,还是要认真思考该类提供的字段方法到底给“谁”使用(是类内部自己用,还是类的调用者使用,还是子类使用)

九、继承方式

【单继承】
在这里插入图片描述

public class A {
……
}
public class B extends A {
……
}

【多层继承】
在这里插入图片描述

public class A {……}
public class B extends A {……}
public class C extends B {……}

【不同类继承同一个类】
在这里插入图片描述

public class A {……}
public class B extends A {……}
public class C extends A {……}

【多继承】(不支持)
在这里插入图片描述

public class A {……}
public class B {……}
public class C extends A , B {……} //错误,Java不支持多继承

注意:Java中不支持多继承。
一般我们不希望出现超过三层的继承关系,如果继承层次太多,就需要考虑对代码进行重构了。
如果想从语法上进行限制继承,就可以使用final关键字。

十、final关键字

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

1. 修饰变量或字段,表示常量(即不能修改)
一般常量名要大写。

	final int A = 10;
	A = 20; // 编译出错

如果是成员变量定义为final,语法规定必须要给一个初始值。

class Animal {
	public final String name = "123";
}

关于数组:

	final int[] array = {1,2,3};
	array = new int[10]; //报错
	array[0] = 19; //正确

被final修饰的array这个引用指向的对象是不能改变的,但是可以改变里面的值。
2. 修饰类:表示此类不能被继承

	final public class Animal {
		...
	}
	public class Bird extends Animal {
		...
	}
	
// 编译出错
Error:(3, 27) java: 无法从最终com.bit.Animal进行继

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

3. 修饰方法:表示该方法不能被重写

十一、继承与组合

组合:是一种代码的实现方式,设计思想。
面向对象的三大特性:封装、继承、多态。

继承表示对象之间是 is - a 的关系,如:狗是动物,猫是动物
组合表示对象之间是 has - a 的关系,如:汽车和轮胎、发动机、方向盘等的关系就是组合,因为汽车是由这些不见组成的。

在一个类里面,会将另一个类当作这个类的属性,用的比较多!

// 学生类
class Student{

}
// 教师类
class Teacher{

}
//教学楼类
class House{

}
// 学校类
class School{
	public Student[] students = new Student[3];// 可以复用学生中的属性和方法
	public Teacher[] teachers = new Teacher[3];// 可以复用教师中的属性和方法
}


// 一中是学校
class YiZhong extend School{
// 将学校中包含的:学生、教师全部继承下来
}

public class Test {
	public static void main(String[] args) {
		School school = new School();
 }
}

在一个类里面,会将另一个类作为我当前类的属性,这个设计思想就是组合,也相当于是代码的复用。我们日常用的比较多,如:接口、循序表等都有组合的思想在其中。

其中上述代码中,main函数中的内存情况如图所示:
在这里插入图片描述

文章来源:https://blog.csdn.net/T2283380276/article/details/134929049
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。