Java体系中的继承

前言

#继承(Inheritance) 是面向对象编程(OOP)中的一个重要概念,它允许一个类(称为子类或派生类)可以从另一个类(称为父类、基类或超类)继承属性(数据)和方法(行为)。简单来说继承主要用于实现共性的抽取,达到代码的复用。

1 继承


1.1 什么是继承

Java中使用类对实体对象进行描述,而不同的事物间根据不同的业务场景可能存在一些特定联系。因此面向对象编程中,为我们提供了继承(inheritance)机制,它允许操作者在保持原有类的基础上进行扩展、增加新功能,这样产生的类称为派生类/子类。

1.2 为什么需要继承

我们发现很多事物间存在着联系,也就是所谓的共性,那么我们能否将这些共同的特性进行抽取,从而达到代码的复用呢?
拿猫和狗进行举例,猫和狗都是动物

//Dog.java
public class Dog {
	//成员变量
	public String name;
	int age;
	//成员方法
	public void eat() {
		System.out.println(this.name + "正在吃饭...");
	}
	public void sleep() {
		System.out.println(this.name + "正在睡觉...");
	}
	public void bark() {
		System.out.println(this.name + "正在汪汪叫...");
	}
	
}

//Cat.java
public class Cat {
	//成员变量
	public String name;
	int age;
	//成员方法
	public void eat() {
		System.out.println(this.name + "正在吃饭...");
	}
	public void sleep() {
		System.out.println(this.name + "正在睡觉...");
	}
	public void meow() {
		System.out.println(this.name + "正在喵喵叫...");
	}
	
}

从上面的两个Java文件中,我们可以发现猫和狗类中存在着相同的共性
在这里插入图片描述
面对这种情况,面向对象思想中提出了继承概念,用于共性的抽取,实现代码的复用
因此我们可以定义一个Animal类用于共性的抽取:
在这里插入图片描述
Dog和Cat都继承于Animal类,Animal类称为父类/基类/超类,Dog和Cat称为子类/派生类,继承之后子类可以复用父类中的成员,从而实现代码的复用。

1.3 继承的语法

在Java中表示类的继承关系需要借助extends关键字

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

那么我们可以借助继承的语法将上述类组装起来:

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

//Dog.java
public class Dog extends Animal{
	public void bark() {
		System.out.println(this.name + "正在汪汪叫...");
	}
	
}

//Cat.java
public class Cat extends Animal{
	public void meow() {
		System.out.println(this.name + "正在喵喵叫...");
	}
	
}

在组装起来之后,我们可以通过在Test类中实例化相应类来进行代码的理解

//Test.java
public class Test {
	public static void main(String[] args) {
		Dog dog = new Dog();
		System.out.println(dog.name);
		System.out.println(dog.age);
		//我们发现dog类可以访问从Animal中继承来的name和age属性,

		dog.eat();
		dog.sleep();
		//dog类中也可以访问从Animal中继承下来的方法
	}
}

1.4 父类成员的访问

通过子类对象访问成员时:

  • 如果访问的成员变量子类中有,优先访问自己的成员变量。
  • 如果访问的成员变量子类中无,则访问父类继承下来的;如果父类中也没有定义,则编译报错
  • 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
  • 通过子类对象访问父类与子类不同名方法时,优先在子类中找
  • 通过子类对象访问父类与子类同名方法时,如果子类和父类同名方法的参数列表不同(方法重载),根据传递的参数选择合适的方法访问,如果没有则报错
    总的来说,成员变量访问遵循就近原则,自己有优先访问自己的,如果没有则向父类中寻找。
//Base.java
public class Base {
	//成员变量
	public int a;
	public int b;
	public int c;
	
	//成员方法
	public void method() {
		System.out.println("Base中的method()");
	}
	public void methodA() {
		System.out.println("Base中的methodA()");
	}
	public void methodB() {
		System.out.println("Base中的methodB()");
	}
	
}

//Derived.java
public class Derived extends Base{
	//成员变量
	public int a;//与父类成员a同名,相同类型
	public char b;//与父类成员b同名,不同类型
	
	//成员方法
	public void methodA(int a) {
		System.out.println("Derived中的methodA()");
	}
	public void methodB() {
		System.out.println("Derived中的methodB()");
	}
	//在methodC()中探讨成员变量的继承问题
	public void methodC() {
		a = 10;//访问子类自己的a
		b = 100//访问子类自己的b
		c = 1000//子类中没有c,访问父类继承下来的c
	}
	//在methodD()中探讨成员变量的继承问题
	public void methodD() {
		method();//访问从父类继承的method()方法
		methodA();//没有传递参数,根据参数列表访问父类的
		methodA(10);//传递参数int,访问子类中的methodA()方法
		methodB();//直接访问则永远访问的是子类的methodB()方法
		methodC();//访问子类自己的methodC()方法
	}
}

在继承关系中,我们可以将子类对象中存在的成员方法和变量看成两部分构成,一部分是从父类继承来的,一部分是子类自己的:
在这里插入图片描述
#方法重写(Override) 当子类继承父类并且子类中定义了与父类同名、参数列表相同的方法时,子类的方法会覆盖(取代)父类中的方法。这意味着,通过子类对象调用这个方法时,会执行子类中的方法实现,而不会执行父类中的方法。

1.5 superthis关键字

通过上述演示,我们知道子类和父类中存在同名成员时,成员的访问中遵循着就近原则,那么当我们想访问父类中的同名成员时,应该怎么操作呢?

super关键字:在子类方法中访问父类成员
也就是说在上述的methodC()方法中,我们可以通过super.a来访问父类中的a;

public void methodC() {
	super.a = 10;//访问父类中的a
	a = 100//访问子类中的a	
}

在methodD()方法中

public void methodD() {
	//如果想要在子类中访问重写的父类方法,则需要借助
	//`super`关键字
	methodB();//直接访问则永远访问的是子类的methodB()方法
	super.methodB();//访问父类的methodD();
}

1.5.1 再谈this关键字

  • this关键字是当前对象的引用,super相当于是子类对象从父类继承下来的成员的引用
  • 在非静态方法中,this一般用来对本类的方法和属性访问,super用来访问父类继承下来的方法和属性
  • 在构造方法中:this(...)用来调用本类的构造方法,super(...)用来调用父类的构造方法
  • 当父类存在无参的构造方法时,子类的构造方法中一定会存在super()的调用,用户没有写编译器也会隐式为你增加,但用户不写则没有this()

1.6子类构造方法和初始化

1.6.1 构造方法

子类对象中成员是由两部分组成的,父类继承下来的和子类新增的部分,父子父子,如字面意思,肯定是先有父再有子,因此在构造子类对象的时候,我们会==先调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法==,将子类自己新增的成员初始化完整。

//Base.java
public class Base {
	public Base() {
		System.out.println("Base()");
	}
}

//Derived.java
public class Derived extends Base {
	public Derived() {
		//super();子类构造方法默认调用父类无参构造方法
		//用户没写时编译器隐式添加,且super()是子类构造第一条语句
		System.out.println("Derived()");
	}
}

//Main.java
public class Main {
	public static void main(String[] args) {
		Derived derived = new Derived();
	}
}

结果打印:
Base();
Derived();

注意:

  1. 父类显示定义无参或者默认无参构造方法时,子类构造方法第一行默认隐含Super();
  2. 当父类构造方法带有参数时,此时需要子类显示调用父类的构造方法,否则编译失败
  3. 在构造方法中,super(…)和this(…)都只能出现在第一行,所以在构造方法中不能同时存在
  4. 最后superthis关键字都依赖于对象,所以只能在类的非静态方法中直接使用

1.6.2 初始化

一般在类中我们存在着几个重要的代码快:实例代码块和静态代码块

//Base.java
public class Base {
	//被static修饰的代码快叫静态代码块
	static {
		System.out.println("Base静态代码块执行了");
	}
	//没有被修饰的代码快叫实例代码块代码块
	{
		System.out.println("Base实例代码块执行了");
	}
	public Base() {
		System.out.println("Base()构造方法执行了");
	}
}

//Derived.java
public class Derived extends Base {
	static {
		System.out.println("Derived静态代码块执行了");
	}
	//.....
	{
		System.out.println("Derived实例代码块执行了");
	}
	public Derived() {
		//super();子类构造方法默认调用父类无参构造方法
		//用户没写时编译器隐式添加,且super()是子类构造第一条语句
		System.out.println("Derived()构造方法执行了");
	}
}

//Main.java
public class Main {
	public static void main(String[] args) {
		//代码段1
		//Base base1 = new Base();
		//System.out.println("= = = = = = = = = =");
		//Base base2 = new Base(); 
		
		

		//代码段2
		//Derived derived1 = new Derived();
		//System.out.println("= = = = = = = = = =");
		//Derived derived2 = new Derived();
		
	}
}

执行结果:

执行代码块1
Base静态代码块执行了
Base实例代码块执行了
Base()构造方法执行了
= = = = = = = = = =
Base实例代码块执行了
Base()构造方法执行了

观察发现:

  1. 静态代码块先执行,且只在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,最后执行构造方法

执行代码块2
Derived静态代码块执行了
Derived实例代码块执行了
Derived()构造方法执行了
= = = = = = = = = =
Derived实例代码块执行了
Derived()构造方法执行了

通过分析执行结果,我们发现:

  1. 父类静态代码块优于子类静态代码块执行,且最早执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类实例化代码块和子类构造方法再紧接着执行
  4. 静态代码块只在类加载的时候执行,在第二次实例子类对象时,父类和子类的静态代码块将不在执行

#projected关键字 为了进一步实现封装特性,Java中引入了访问权限修饰符,限定:类或者类中成员能否在类外或者其他包中被访问。projected关键字修饰的权限只能在同一个包下或者不同包的子类中访问。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值