【JavaSE】继承的详解
创始人
2025-05-29 07:13:56
0

前言

大家好,我是那个不会打拳的程序猿。今天我给大家带来的是面向对象之封装继承多态中的继承,文章通过继承的语法、父类成员的访问、super关键字、子类的构造方法、protected关键字等方面来详细讲解继承的用法。

目录

1.继承的概念

1.1继承的语法

1.2父类成员访问

1.2.1子类中访问父类的成员变量

1.2.2子类中访问父类的成员方法

1.3super关键字

1.4子类构造方法 

1.5super和this关键字

1.6protected关键字

1.7继承方式

1.8final关键字 


1.继承的概念

继承机制:是面向对象编程的一个重要体现,它主要是用来使一些重复的代码只用编写一份达到共用的效果。它主要解决:共性的抽取,实现代码的复用。

例如:狗和猫都是动物,他们共有的特性就是吃、睡、跑等,因此我们可以把这些特性作为一份代码,使得狗和猫这两类能共用这份代码,避免写两份代码甚至更多代码。

 通过Java就能写出这样一些代码:

class Dog {String run;String eat;public void sleep() {System.out.println("睡觉");}public void wangWang() {System.out.println("汪汪叫");}
}class Cat {String run;String eat;public void sleep() {System.out.println("睡觉");}public void wangWang() {System.out.println("汪汪叫");}
}

我们发现,狗类和猫类中的这一部分代码是相同的,因此,我们可以把这一部分代码专门用一个类来写出来,从而能人狗和猫类公共使用这份代码,这就是继承的思想。如图:

上图展示了,Dog和Cat类可以继承Animal类中的属性,并且Dog和Cat类也有属于自己的属性。其中Animal为父类(基类/超类),Dog和Cat为子类(派生类)。那么这样的关系怎样用代码去实现呢?请看下方讲解! 


1.1继承的语法

在Java中,我们如果要表示类与类之间的关系,可以通过extends关键字来区分:

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

那么对上方场景中Animal、Dog和Cat类使用继承方式设计如下:

class Animal {String run = "跑";String eat = "吃";public void sleep() {System.out.println("睡觉");}
}
class Dog extends Animal {public void wangWang() {System.out.println("汪汪叫");}
}class Cat extends Animal {public void miaoMiao() {System.out.println("喵喵叫");}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();Cat cat = new Cat();//dog类中并没有定义任何成员变量,run和eat是从父类Animal中继承过来的。System.out.println(dog.run);System.out.println(dog.eat);dog.wangWang();//dog类中并没有定义任何成员变量,run和eat是从父类Animal中继承过来的。System.out.println(cat.run);System.out.println(cat.eat);cat.miaoMiao();}
}

上述代码,我们之间在main方法中实例化Dog和Cat这两个类,Dog和Cat这两个类都继承了run和eat这两个成员变量,并且Dog类和Cat类他们都有自己的属性汪汪叫和喵喵叫。因此子类可以继承父类中的属性,而且子类也可以有属于自己的属性。

注意:

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

1.2父类成员访问

在继承体系当中,子类将父类的方法和字段可以继承下来,那么在子类中能否不通过实例化对象直接访问父类中的继承下来的成员或方法呢?


1.2.1子类中访问父类的成员变量

(1)子类和父类不存在同名成员变量

class Animal {String run = "跑";String eat = "吃";
}
class Dog extends Animal {String sleep = "睡觉";public void show() {System.out.println(run);//访问从父类中继承下来的runSystem.out.println(eat);//访问从父类中继承下来的eatSystem.out.println(sleep);//访问子类自己的sleep}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.show();}
}

通过以上代码我们可以看到,访问父类继承下来的成员变量是毫无争议的。子类中也有关于子类自己的属性因此访问自己的成员变量也是没有异议的。但是当子类与父类中的成员变量同名的时候,我们该如何去从呢?

 (2)子类和父类成员变量同名

class Animal {String run = "跑";String eat = "吃";
}
class Dog extends Animal {String run = "跑步";String sleep = "睡觉";public void show() {System.out.println(run);//访问子类自己的runSystem.out.println(eat);//访问从父类中继承下来的eatSystem.out.println(sleep);//访问子类自己的sleep}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.show();}
}

 以上代码中,我们发现父类与子类中的成员变量run相同了,因此在子类中我们会优先访问子类自己的run,并不会继承父类中run的属性。

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

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

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


1.2.2子类中访问父类的成员方法

(1)成员方法名字不同

class Animal {public void funA() {System.out.println("Animal中的funA()");}
}
class Dog extends Animal {public void funB() {System.out.println("Dog中的funB()");}public void show() {funA();funB();}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.show();}
}

以上代码中,子类与父类中都没有成员方法同名的情况,因此没有报错情况。那么在子类方法中或者子类对象访问方法时,优先访问自己的,没有时再访问父类中的,如果父类中也没有则会报错

(2)成员方法名字相同

class Animal {public void funA() {System.out.println("Animal中的funA()");}public void funB() {System.out.println("Animal中的funB()");}
}
class Dog extends Animal {public void funA(int num) {System.out.println("Dog中的funA()");}public void funB() {System.out.println("Dog中的funB()");}public void show() {funA();//访问了父类中的funA方法funA(6);//因为有了参数,访问了子类中的funA方法funB();//访问的是子类中的funb方法}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.show();}
}

以上代码我们可以看到,访问的优先级也是跟成员方法是一样的。子类里面有的则访问子类里面的,子类里面没有的则访问父类里面的。那如果子类与父类中的成员变量或者成员方法名相同时,我们非要访问父类里面的成员变量或成员方法那怎么办呢?我们可以加上一个super关键字


1.3super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员

class Animal {String name = "赛虎";public void funA() {System.out.println("Animal中的funA()");}public void funB() {System.out.println("Animal中的funB()");}
}
class Dog extends Animal {String name = "小米";public void funA() {System.out.println("Dog中的funA()");}public void funB() {System.out.println("Dog中的funB()");}public void show() {System.out.println(super.name);super.funA();super.funB();}
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.show();}
}

 以上代码中,子类与父类中都有相同的成员变量名与方法名,但是在我们的show方法里使用了关键字super使得子类引用了父类的成员变量与方法,这就是super关键字的作用。


1.4子类构造方法 

我们在子类里面执行构造方法时,必须要先调用父类构造方法,然后才能执行子类构造方法。

class Base {public Base() {System.out.println("这是一个无参的父类构造方法");}
}
class Derived extends Base {public Derived() {//编译器会默认增加一个super();语句System.out.println("这是一个子类的构造方法");}
}
public class Test2 {public static void main(String[] args) {Derived der = new Derived();}
}

以上代码,我们可以看到。我们在main方法中创建der对象后,先执行了父类中的构造方法然后再执行了子类中的构造方法。因为编译器会默认在子类的构造方法中第一行添加一条语句:super();使得我们的父类构造方法优先级最高。

class Base {public Base(int a,int b) {System.out.println("这是带两个参数的父类构造方法");}
}
class Derived extends Base {public Derived() {System.out.println("这是一个子类的构造方法");}
}
public class Test2 {public static void main(String[] args) {Derived der = new Derived();}
}

当我们这样去编写代码时,就会报错。那是因为我们子类的构造方法中编译器默认提供的super()语句里面没有参数,因此我们应该这样写代码:

class Base {public Base(int a,int b) {System.out.println("这是带两个参数的父类构造方法");}
}
class Derived extends Base {public Derived() {super(2,3);System.out.println("这是一个子类的构造方法");}
}
public class Test2 {public static void main(String[] args) {Derived der = new Derived();}
}

当我们在子类里面,使用super()语句给父类中的构造方法赋初值后,子类的构造方法才能运行下去。注意,这个super语句如果我们没有人为的在子类构造方法中写入的话,编译器会默认super这个语句的存在。因此我们父类构造方法为无参的也就可以省略不写,如果是有参的就自己写上super语句并赋上值

注意:

  • 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构
  • 造方法
  • 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
  • 父类构造方法调用,否则编译失败。
  • 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  • super(...)只能在子类构造方法中出现一次,并且不能和this同时出现

1.5super和this关键字

superthis都可以在成员方法中访问成员变量或调用成员方法,并且都可以放在构造方法的第一行,那他们直接有什么区别呢? 

相同点:

  • 都是Java中的关键字
  • 只能在类中的非静态方法中使用,用来访问非静态成员方法和变量
  •  在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
class Derived extends Base {public Derived() {super(2,3);this();System.out.println("这是一个子类的构造方法");}
}

class Derived extends Base {public Derived() {this();super(2,3);System.out.println("这是一个子类的构造方法");}
}

我们可以看到,当super和this两个关键字同时在子类构造方法中出现时。都出现必须要在第一条语句的说法。

不同点:

  • this是当前对象的引用,而super是对父类中的成员或方法的引用。
  • 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
  • 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
class Derived extends Base {public Derived(int c,int d) {super(2,3);this(3,4);}
}

当我们同时在一个构造方法中使用super(...)和this(...)就会报错,但如果我们分别使用super和this两个独特的功能时,比如this对当前对象的引用,super对父类构造方法进行传参。就不会造成冲突

class Base {public Base(int a,int b) {System.out.println("a="+a+" b="+b);}
}
class Derived extends Base {int c;int d;public Derived(int c,int d) {super(2,3);this.c = c;this.d = d;System.out.println("c="+c+" d="+d);}
}
public class Test2 {public static void main(String[] args) {Derived der = new Derived(4,5);}
}

 以上代码,我们可以看到,如果super和this各自用各自独特的功能就不会发生报错。


1.6protected关键字

(1)同一个包内同一个类

public class Test {protected int num = 99;public static void main(String[] args) {Test test = new Test();System.out.println(test.num);}
}

 以上代码展示了在同一个包同一个类内里面,被protected修饰的成员变量可以被使用。


(2)同一包中不同类

package Pack1;class Dog {protected int num = 88;
}
public class Test {public static void main(String[] args) {Dog dog = new Dog();System.out.println(dog.num);}
}

  同样在同一包内,我们照样可以很清晰的访问到被protected修饰的变量。


(3)不同包中的子类

//包1中
package Pack1;public class Test1 {protected int num = 99;public static void main(String[] args) {}
}
//包2中
package Pack2;//导入包1中的Test1类
import Pack1.Test1;//继承包1中的Test1父类
public class Test2 extends Test1{public static void main(String[] args) {Test1 test = new Test1();System.out.println(test.num);}
}

当我们在不同包子类访问父类的时候,按我们在同一个包内的情况我们可以直接实例化并继承父类中所有的成员和方法。但是在不同包的时候,被protected修饰的变量不能随意继承,因此我们得使用super关键字来引用被protected修饰的变量或方法

//包1
package Pack1;public class Test1 {protected int num = 99;public static void main(String[] args) {}
}
//包2
package Pack2;//导入包1中的Test1类
import Pack1.Test1;//继承包1中的Test1父类
public class Test2 extends Test1{public void show() {System.out.println(super.num);}public static void main(String[] args) {Test2 test = new Test2();test.show();}
}

 以上代码我们可以看到,通过super关键字就能很好的访问到了protected修饰的关键字了。因此有以下表格:

NO范围privatedefaultprotectedpublic
1同一包中的同一类可以访问可以访问可以访问可以访问
2同一包中不同类不可以访问可以访问可以访问可以访问
3不同包中的子类不可以访问不可以访问可以访问可以访问
4不同包中的非子类不可以访问不可以访问不可以访问可以访问

 以上情况大家可以一一测试一下,体会各个关键字的不同之处。


1.7继承方式

我们的继承方式分为:单继承、多层继承、不同类继承同一类

(1)单继承

//此类为父类
class Animal {
}
//此类为子类
class Dog extends Animal {
}

以上代码展示了子类继承父类的情况,这是一个比较常见的继承方式。

(2)多层继承

//此类为父类
class Animal {
}
//Dog类继承Animal类
class Dog extends Animal {
}
//Cat类继承Dog类
class Cat extends  Dog {
}

以上代码展示了多层继承的情况,当这个层次为三层的时候,它是第一个类作为"爷爷类",第二个类作为"儿子类"继承了它的父类也就是"爷爷类",第三个类作为"孙子类"继承它的父类也就是"儿子类"。这就是一个多层继承的方式。

(3)不同类继承同一个类

//此类为父类
class Animal {
}
//Dog类继承Animal类
class Dog extends Animal {
}
//Cat类继承Animal类
class Cat extends  Animal {
}

以上代码展示了不同类继承同一个类的情况,也就是一个类为父类,其余的类作为子类都继承这个父类。我们称之为不同类继承同一个类。

(4)多继承是不可行的

//不能继承多个父类
class Me extends Animal,Dog {
}

当我们把一个类继承多个类的时候,编译器先是通过不了的,因此我们只能依靠以上三种方式去实现继承。 


1.8final关键字

final关键字会锁定被修饰的变量或者方法,当你试图修改被final修饰的变量或方法时,编译器会报错,令你无法通过编译。

(1)final修饰变量

public class Test3 {public static void main(String[] args) {final int a = 10;a = 20;}
}

当我们在main方法里定义一个用final修饰的变量后,想对它进行修改。此时是不可行的,被final修饰的变量或方法是不可修改的。 

(2)final修饰类

final class Father {
}
class Son extends Father {
}

当我使用final修饰类的时候,此时不能把此类当作父类给其他类继承。


本期博客到这里就结束啦,相信大家对继承有了一定的了解,感谢你的阅读,如有收获还请三连支持。

 下期预告:多态

相关内容

热门资讯

为什么说网络安全是风口行业?是... 前言 “没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度ÿ...
博客14周年:博客这一年工作汇... 我是卢松松,点点上面的头像,欢迎关注我哦! 2022年我一...
React 的源码与原理解读(... 写在专栏开头(叠甲) 作者并不是前端技术专家,也只是一名...
线性回归 梯度下降原理与基于P... 线性回归基础知识可查看该专栏中其他文章。 文章目录1 梯度下降算法原理2 一元函数梯度下降示例代码3...
块级元素和行内元素 一、块级元素和行内元素 行内元素:         常见的行内元素: a、span、b、img、st...
shell 部分内置变量 说明 这些变量都是由bash这个程序本身进行修改使用; 其他shell不一定试用; 变量 $_ to...
链表(没做完。。) BM1 反转链表 给定一个单链表的头结点pHead(该头节点是有值的,比如在下图&#...
十九、互斥量(互斥锁) 文章目录1、基本概念2、使用场景:用于实现对临界资源的独占式处理(能够解...
springboot控制层消息... 大概了解三种方式: 1.区别在于WebMvcConfigurerAdapter 新版本...
2023年湖北安全员ABC证书...   2023年湖北安全员ABC证书报名入口!报考条件?启程别 证...
蓝桥杯倒计时 | 倒计时18天 作者🕵️‍♂️:让机器理解语言か 专栏🎇:...
pandas读CSV、读JSO... 学习让我快乐   pandas的数据读取基本操作 pandas是Python中非常流行的数据处理库...
Hybrid App开发模式 Hybrid App(混合模式移动应用)是指介于web-app、nati...
SQL预编译和批量提交对性能提... 背景 一个项目,从kafka获取数据后,经过业务处理,生成...
小白学Pytorch系列--T... 小白学Pytorch系列–Torch.nn API (1) 方法注释Parameter一种被认为是...
C++ Lambda表达式的常... ⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生👨‍🎓。...
【总结】docker 安装教程 各操作系统版本下载地址 mac 版:https://download.docker.c...
12 致远OA开发规范 开发案例(业务扩展接口) 1.客开开发前缀约束: 为了快速区分标准开发与客开开发&#x...
文件包含漏洞全面详解 文件包含漏洞总结一、什么是文件包含漏洞二、本地文件包含漏洞(LFI)三、LFI漏洞利用技巧1.配合文...
【C++进阶】unordere... 文章目录unordered系列容器介绍unordered_setunordered_set的模板参数...