Java继承全解析(超详解版
admin2025-10-14 09:22:07【世界杯比赛赛】
目录
1.继承的引出
2.继承的概念
3.继承的语法
4.继承——子类对父类成员的访问
4.1 对父类成员变量的访问
4.2 对父类成员方法的访问
5.super关键字
6.子类构造方法
7.super和this对比
8.代码块
8.1 普通代码块
8.2 实例代码块
8.3 静态代码块(static 代码块)
8.4 在继承中的代码块
9.访问限定修饰符——protected关键字
10.继承方式与final关键字
10.1继承方式
10.2 final关键字
11.小结
1.继承的引出
继承是Java面向对象编程的三大基本特性(其他两个是封装、多态)之一,它允许一个类(子类)基于另一个类(父类)来构建。 这里可以先理解成子承父业之意。
那么,为什么需要继承呢?
假设有两种动物,分别是猫和狗,可以将其定义为两个类,一个类是猫,一个类是狗,猫可以有自己的名字、年龄、体重等,和一些行为正在吃饭、正在喵喵叫~~~等,狗也可以有自己的名字、年龄、体重,和一些行为正在吃、正在汪汪叫~~~等
如果用Java语言来描述,则会有:
仔细观察以上代码会发现有很多重复的地方,比如名字、年龄、体重等,
可不可以把这些重复的地方抽取出来呢?也就是说:
答案是肯定的。这就是继承的概念,专门用来共性抽取,实现代码复用。
2.继承的概念
继承是面向对象程序设计时,使代码可以复用的手段,它允许程序员可以在原有类(称为父类/基类/超类)的特性基础上进行扩展,增加新的功能,这样产生的类就叫派生类,也叫子类。继承呈现了面对对象程序设计的层次结构,体现了由简单到复杂的过程成。继承主要解决的问题:共性抽取:实现代码复用。
每个动物都会有一个相同的名字、年龄、体重等,猫和狗属于动物,自然也就继承了这一点,并在这一点上进行扩展,比如他们的一些行为并不相同:猫的叫声是喵喵叫,狗的叫声是汪汪叫~~~
因此动物可以称为父类或者基类/超类,猫和狗就称为动物的子类/派生类。继承之后,子类就可以复用父类的成员变量或成员方法,子类在实现的时候就只需要关注自己新增加的成员变量或成员方法即可。
3.继承的语法
为了表示继承关系,Java提供了一个关键字,也就是extends。
class 父类 { // 父类的成员变量和方法 }
class 子类 extends 父类 { // 子类特有的成员变量和方法 }
对于这张图的表达,用Java语言表示就是(Animal、Cat和Dog分别是三个不同的java文件):
1)Animal类
public class Animal {
public String name;
public int age;
public double weight;
public void eat() {
System.out.println(this.name + "正在吃饭");
}
}
2)猫类
public class Cat extends Animal{
public void Meo() {
System.out.println(this.name + "正在喵喵叫~~~");
}
}
3)狗类
public class Dog extends Animal {
public void bark() {
System.out.println(this.name + "正在喵喵叫~~~");
}
}
在这里值得注意的是:当子类继承父类之后,子类必须要有自己的新成员,否则也就没有继承的必要了。
4.继承——子类对父类成员的访问
为了更好地理解父类成员的访问,这里重新定义一对父子类,即Base类和Derived类,为方便理解,这里都在同一个java文件实现。
4.1 对父类成员变量的访问
1)父类和子类没有同名变量情况:
class Base {
int a = 10;
double b = 3.14;
char c = 'A';
}
class Derived extends Base {
int d = 20;
public void method() {
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
derived.method();
}
}
运行结果:
结果说明:当子类中没有和父类同名成员变量时,对其进行编译输出,子类访问父类中继承下来的,并且访问子类自身新增加的成员。
2)父类和子类有同名变量情况:
class Base {
int a = 10;
double b = 3.14;
char c = 'A';
}
class Derived extends Base {
int a = 20;//与父类同名且同类型
int c = 52;//与父类同名但不同类型
public void method() {
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
//System.out.println("d = " + d);没有d,编译失败
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
derived.method();
}
}
运行结果:
结果说明: 当子类中有和父类同名成员变量时,不论是否同类型,对其进行编译输出,子类会优先访问自己的成员。如果父类子类中都没有的成员,则会编译失败。
以上可以得出:成员变量访问遵循就近原则,子类自己有的,就优先访问子类自己,否则再访问父类成员,如果都没有,则会编译失败。
4.2 对父类成员方法的访问
1)父类和子类没有同名方法情况:
class Base {
public void baseMethodA() {
System.out.println("这是父类方法A");
}
public void baseMethodB() {
System.out.println("这是父类方法B");
}
}
class Derived extends Base {
public void derivedMethod() {
System.out.println("这是子类方法");
}
public void method() {
baseMethodA();//访问父类成员方法A
baseMethodB();//访问父类成员方法B
derivedMethod();//访问子类自身成员方法
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
derived.method();
}
}
运行结果:
结果说明:当子类中没有和父类同名成员方法时,对其进行编译输出,子类访问父类中继承下来的,并且访问子类自身新增加的成员。
1)父类和子类有同名方法情况:
class Base {
public void baseMethodA() {
System.out.println("这是父类方法A");
}
public void baseMethodB() {
System.out.println("这是父类方法B");
}
}
class Derived extends Base {
public void baseMethodA() { //同名
System.out.println("这是子类方法A........");
}
public void method() {
baseMethodA();//访问父类成员方法A
baseMethodB();//访问父类成员方法B
//derivedMethod();//访问子类自身成员方法-->没有这个方法,编译失败
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
derived.method();
}
}
输出结果:
结果说明: 当子类中有和父类同名成员方法时,对其进行编译输出,子类会优先访问自己的成员。如果父类子类中都没有的成员,则会编译失败。
以上可以得出:成员方法访问遵循就近原则,子类自己有的,就优先访问子类自己,否则再访问父类成员,如果都没有,则会编译失败。
看到这里,也许会有这么一个疑惑:如果子类和父类出现相同成员(包括变量和方法)名时,能不能精确访问到父类的成员呢?
在这里,Java提供了一个关键字,也就是super。
5.super关键字
为了解决上面提出的疑问,Java提供了super关键字,它的作用是在子类中访问父类的成员。为了可以更好地区分子类和父类的成员,在子类访问自己的成员中,也可以给自己加上this关键字(this关键字在上一篇这篇文章https://mp.csdn.net/mp_blog/creation/editor/149191536有详细说明)。
拿上述成员变量同名的情况来举例:
class Base {
int a = 10;
char c = 'A';
}
class Derived extends Base {
int a = 20;
int c = 52;
public void method() {
System.out.println("父类a = " + super.a);
System.out.println("子类a = " + this.a);
System.out.println("父类c = " + super.c);
System.out.println("子类c = " + this.c);
//System.out.println("d = " + d);没有d,编译失败
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
derived.method();
}
}
运行结果:
说明:想要在子类中精确地访问父类成员,可以借助super关键字来完成。
6.子类构造方法
在继承关系里,如果有构造方法,父类和子类的执行顺序又是怎样的呢?
class Base {
public Base() {
System.out.println("这是父类的构造方法Base()");
}
}
class Derived extends Base {
public Derived() {
System.out.println("这是子类的构造方法Derived()");
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();//创建Derived对象
}
}
运行结果:
可以看出:在子类的构造方法中,并没有任何调用父类构造方法的代码,但是从运行结果来看,真个代码是先执行父类的构造方法,再执行子类的构造方法。 这是为什么呢?
这是因为:在了解继承的概念并通过例子发现,在子类对象中的成员实际上是由两部分组成,继承父类的成员和自己新增加的成员,所以在构造子类对象的时候,先要调用父类的构造方法,讲父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
为什么会先调用父类的构造方法呢?这背后就藏着一个super关键字,在我们没有定义任何的构造方法的时候,编译器会自动添加一个无参的父类的构造方法,即“super();”,值得注意的是,在子类的构造方法中调用父类的构造方法时,“super(); ”必须是子类构造方法的第一条语句,这样说明了在该子类构造方法中,super和this这两个关键字是不能同时存在的。即以下写法是错误的:
7.super和this对比
super和this都是对类的访问,二者区别这里笔者整理了一部分表格:
8.代码块
在 Java 中,代码块 是一段用 {} 括起来的代码,用于控制变量的作用域或实现特定的初始化逻辑。代码块主要分为以下几种类型:
8.1 普通代码块
作用:限制变量的作用范围(局部变量)
位置:方法内部或语句块中
执行时机:按代码顺序执行
例如以下代码:
public class Main {
public static void main(String[] args) {
int x = 10;
System.out.println("x = " + x); // 可以访问 x
{ // 普通代码块
int y = 20;
System.out.println("y = " + y); // 可以访问 y
}
// System.out.println("y = " + y); // 编译错误,y 超出作用域
}
}
说明:y 的作用域仅限于 {} 内部,外部无法访问。
8.2 实例代码块
作用:用于初始化实例变量,在每次创建对象时执行
位置:类中,方法外
执行时机:每次创建对象时,在构造方法之前执行
例如以下代码:
public class Main {
public Main() {
System.out.println("构造方法执行...");
}
{ // 实例代码块
System.out.println("实例代码块执行...");
}
public static void main(String[] args) {
new Main(); // 创建对象
}
}
运行结果:
说明:每次 new 一个新对象时都会先执行实例代码块,再执行构造方法
8.3 静态代码块(static 代码块)
作用:用于初始化静态变量,在类加载时执行
位置:类中,方法外,用 static 修饰
执行时机:类加载时执行,且只执行一次
例如以下代码:
public class Main {
public static void main(String[] args) {
System.out.println("main方法执行...");
}
static { // 静态代码块
System.out.println("静态代码块执行...");
}
}
运行结果:
说明:静态代码块在类加载时执行,比 main() 方法更早。
8.4 在继承中的代码块
在了解以上代码块后,回到继承里,这些代码块的会如何执行呢?
class Base {
public int a;
public double b;
public Base(int a , double b) {
this.a = a;
this.b = b;
System.out.println("父类的构造代码块...");
}
{
System.out.println("父类的实例代码块...");
}
static{
System.out.println("父类的静态代码块");
}
}
class Derived extends Base {
public Derived(int a , double b) {
super(a, b);
System.out.println("子类的构造方法");
}
{
System.out.println("子类的实例代码块...");
}
static{
System.out.println("子类的静态代码块");
}
}
public class Main {
public static void main(String[] args) {
Derived derived1 = new Derived(5,3.14);//第一次创建Derived对象
System.out.println("-----------分割线-----------");
Derived derived2 = new Derived(6,2.78);//第二次创建Derived对象
}
}
运行结果:
说明:
静态代码块优先执行,且先执行父类静态代码块再执行子类静态代码块实例代码块比构造代码块先执行,且执行完父类的实例代码块和构造代码块后,才执行子类的实例代码块和构造代码块。在第二次实例化对象的时候,父类和子类的静态代码块都不再执行,也就是说静态代码块最先执行,且只执行一次
9.访问限定修饰符——protected关键字
在继承里,Java提供了一个访问限定修饰符,即protected关键字。可以在不同的包中的子类使用。protected 成员的访问权限比 private 大,比 public 小。也就是说,当我们下在封装和扩展之间取得一个平衡——即:既可以保护父类的实现细节,又不想全部暴露在外,又允许子类进行必要的访问和扩展,那么我们就可以使用protected关键字来限定访问修饰。
在这里借助不同的包和类实现代码来理解protected关键字:
1)同一个包中的子类:
从结果来看,父类中protected修饰的成员可以在子类中被访问,其中没有限定修饰符的int c系统默认被default(同一包中的同一类用)修饰。
2)不同包中的子类
3)不同包中的非子类
说明:protected既可以保护父类的实现细节,又不想全部暴露在外,又允许子类进行必要的访问和扩展,这为Java继承体系提供了精妙的访问控制设计。
10.继承方式与final关键字
10.1继承方式
在本篇文章开头引出继承时,用猫和狗两种动物引出,但在现实生活中,猫和狗还可以分为很多类,比如猫类还可以分为:中华田园猫、苏格兰折耳猫、布偶猫、英国短毛猫等,其中中华田园猫又可以分为:山东狮子猫、狸花猫、橘猫,这些猫的外貌特征又不一样,比如山东狮子猫是鸳鸯眼、头大脸圆等,狸花猫杏核形眼、头部圆润等,用一个关系图来表示就是:
因此,在Java中,继承方式就可以有以下几种表示:
需要注意的是:Java中并不支持多继承。也就是说,一个子类不能拥有两个父类。
10.2 final关键字
在Java中,当定义一个变量并且初始化,在以后的应用中并希望它会被修改,这个时候就可以使用final关键字。
例如:
因此,在Java的继承关系里,如果不希望某一类会继承,那么就可以使用final关键字来修饰。
例如(为方便理解,这里也只在同一个java文件中展示):
例中,当定义的猫类继承动物时,用final修饰,在后面的田园猫便再也不能书写继承关系。这就说明: 使用final修饰类后,表示该类就不能再被继承。
11.小结
继承是Java面向对象编程的三大基本特性(其他两个是封装、多态)之一,允许子类复用父类的属性和方法,同时扩展自身功能。通过extends关键字建立继承关系,子类可以访问父类的非私有成员,并通过super关键字精确调用父类成员。
继承的关键规则包括:
成员访问:子类优先访问自身成员,如果父子类有同名成员,访问父类成员,需使用super。
构造方法:子类构造时默认先调用父类构造方法(隐式super()),确保父类成员正确初始化。
代码块执行顺序:静态代码块→ 实例代码块→ 构造方法。
访问控制:protected允许子类跨包访问父类成员,平衡封装与扩展性。
继承限制:Java不支持多继承,final修饰的类不可被继承。