一. 继承简介
概述
在日常生活中,“继承”是施方的一种赠与,受方的一种获得,是将一方所拥有的东西给予另一方。
概念
开发中的”继承“,其实和我们日常生活中所熟知的含义类似,代表着子类可以从父类中得到的承接。
在Java中,继承表示子类能够承接父类的特征和行为,使得子类对象(实例)具有父类的成员属性。或者子类可以从父类继承方法,使得子类具有父类相同的行为,所以继承是类与类之间特征(属性)和行为(方法)的一种赠与或获得。继承能让我们创建出带有等级层次的类,两个类之间的继承会满足“is a”的关系,如下图所示:
Java中的继承是对已存在的类进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。
优缺点
继承能够减少代码的冗余,提高代码复用性,具有以下优点:
1. 实现了代码共享,减少了创建类的工作量,使子类可以拥有父类的方法和属性;
2. 提高了代码的维护性和可重用性;
3. 提高了代码的可扩展性,更好的实现父类的方法。
但继承也并非全是优点,毕竟这个世界上没有十全十美的东西,也有如下一些缺点:
4. 继承具有侵入性。只要继承,就必须拥有父类的非私有属性和方法;
5. 降低了代码的灵活性。子类拥有父类的属性和方法后就会多了一些约束。
6. 提高了代码的耦合性(应该高内聚低耦合)。父类的常量、变量和方法被修改时,也要考虑对子类进行修改,有可能会导致大段的代码被重构。
7. 使用特性
继承在使用时,具有如下特性,需要我们牢牢掌握:
● 子类继承父类,可以继承父类中的属性和方法,即儿子可以继承爹的特征、遗产;
● 子类可以拥有自己独有的属性和方法,即儿子可以有自己的个性;
● 只能单继承,java中一个子类只能继承一个父类,但一个父类可以拥有多个子类。即一个儿子只能有一个亲爹,但一个爹可以有多个儿子;
● 多重继承结构,父类还可以继承另外一个类。Java中最大的父类是Object,如果一个类没有显式地标明继承自哪个父类,默认都是Object的子类。即儿子有爹,爹也有自己的爹......最终有个老祖宗是Object,这是根!
8. 注意事项
但是我们要注意,虽然子类继承父类时,很多属性和方法都能继承过来,但也有一些内容无法继承,主要是以下几点:
● 构造方法不能被继承,即生成父类对象的方法不能传给儿子,这样就”乱伦“了;
● 父类的私有属性不能被继承,即爹的私有财产小金库不能继承给儿子;
● 父类中使用默认修饰符修饰的属性和方法,在不同包的子类中不能被继承;
● 使用final声明的类是最终类,也不能被继承。
在继承时,需要考虑父类中的访问修饰符问题。关于访问修饰符,壹哥在之前的文章中就给大家讲过,你还能想起来吗?看看下面这个表格回忆一下吧。
修饰符 本类 本包(不同类) 不同包子类 其他
private ✔️
默认的 ✔️ ✔️
protected ✔️ ✔️ ✔️
public ✔️ ✔️ ✔️ ✔️
类的继承不会改变类成员的访问权限。也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,且子类不能继承父类的构造方法。
了解了关于继承的这些内容之后,接下来我们再通过一些代码案例来实操一下吧。
二. 代码实现
1. 基本语法
首先我们来看看继承的基本语法。
class 父类 {
...
}
class 子类 extends 父类 {
...
}
extends关键字直接跟在子类名称之后,后面是子类要继承的父类名称。
1. extends关键字
Java中的继承主要是通过extends关键字来实现。extends的英文意思是扩展,而不是继承。extends很好地体现了子类和父类的关系,即子类是对父类的扩展,子类是一种特殊的父类。从这个角度看,使用“继承”这个词来描述子类和父类的关系是错误的,所以用“扩展”更恰当。
Java类的继承是单一继承,即一个子类只能拥有一个父类。如果一个类没有明确地继承某个别的类,编译器会自动加上extends Object,即默认继承Object(在java.lang包中,不需要手动import导包)祖先类。所以,除了Object之外的任何类,都会继承某个类,只有Object没有父类。
另外我们也可以利用implements关键字实现接口,这其实也是一种变相的继承,壹哥后面会再给大家单独讲解接口的实现。所以很多地方在介绍Java单继承时,会说Java类只能有一个父类,其实严格地说,这种说法是不准确的!应该是一个类只能有一个直接父类,但它可以有多个间接的父类。比如儿子只能有一个亲爹,但是爷爷、老爷爷、老老爷爷等也是儿子的“父类”,“父辈”,这属于间接父类。
2. 需求分析
接下来壹哥通过一个案例,让大家来看看该如何进行继承的实现。这里我对动物的共性做了一些抽象,如下图所示:
从上图中,我们可以看到这些不同的动物有一些共同的属性,比如“品种、年龄、性别”;也有一些共同的方法,比如吃、睡等。但不同的动物也有会一些个性化的行为或特征,比如鱼可以游泳,鸟会飞,狗会跑,蛇会爬。那么如果让我们来设计一个程序对动物进行描述,就需要对他们的特征和行为进行抽象归纳。
所以根据上图,我们可以总结出一些基本的规律:如果我们想实现继承,需要把使用到的多个具体类,进行共性的抽取,进而定义父类。在一组相同或类似的类中,抽取出共性的特征和行为定义在父类中,实现代码的重用。
4. 代码实现
那么具体该怎么进行代码实现呢?我们来参考下面这些案例吧。
4.1 Animal父类
我们先来定义一个Animal父类,在这里定义一些共同的属性和方法。
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义父类
*/
public class Animal {
·
//定义公共属性
String name;
int age;
String type;
·
//定义公共方法
public void sleep() {
System.out.println("睡觉...");
}
·
public void eat() {
System.out.println("吃饭...");
}
·
}
在OOP面向对象的术语中,我们可以把Animal称为父类(parent class)、超类(super class)或者基类(base class);把Cat/Dog等称为子类(subclass)、扩展类(extended class)。
4.2 定义子类Cat
我们再来定义一个子类Cat。子类会从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性,子类中可以定义自己特有的属性和方法。
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义子类
*/
public class Cat extends Animal{
·
//从父类中继承共同的属性和方法,但不能继承父类的构造方法和私有属性!
·
//定义独有属性
String color;
·
//定义独有方法
public void catchMouse() {
System.out.println("抓老鼠...");
}
·
}
子类继承父类之后,就具有了父类中的属性和方法,子类不用再重复地编写这些代码。我们只需要为子类编写新增的功能即可,这样代码的可维护性和复用性也就提高了,代码也更加简洁了。
4.3 定义子类Dog
我们再来定义第二个子类Dog。
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义子类
*/
public class Dog extends Animal{
·
//定义子类独有属性
String color;
·
//定义子类独有方法
public void lookHome() {
System.out.println("看家...");
}
·
}
4.4 效果测试
接下来我们在main()方法中对上面的继承关系进行测试。
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
测试继承
*/
public class ExtendTest {
·
public static void main(String[] args) {
Dog dog = new Dog();
//使用父类继承下来的属性
dog.name = "旺财";
dog.type = "泰迪";
dog.age = 3;
//使用子类独有属性
dog.color = "黄色";
System.out.println("姓名为:"+dog.name+",品种为:"+dog.type+",毛色为:"+dog.color);
//使用父类继承下来的方法
dog.eat();
dog.sleep();
//使用子类独有方法
dog.lookHome();
}
·
}
三. 几种不能继承的情况
1. 构造方法不能被继承
子类不能继承父类的构造方法,只能隐式或显式地调用。如果父类的构造方法带有参数,继承的子类可以在自己的构造方法中,显式地利用 super关键字调用父类的构造方法,并配以适当的参数列表。
如果父类的构造方法没有参数,则子类的构造方法中可以不用 super关键字调用父类的构造方法,系统会自动调用父类的无参构造方法。
接下来我们再通过一个案例来进行说明。
1.1 定义父类
这里定义了一个带有2个构造方法的父类,如下所示:
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
父类
*/
public class Father {
·
//私有属性不能被继承
private String name;
private int age;
private String secret;
·
//公开的属性--姓氏。公开属性可以被继承
public String familyname;
·
//如果在父类中存在有参的构造方法,但没有重载无参的构造方法,那么子类中必须存在有参的构造方法。否则会产生如下异常:
//Implicit super constructor Father() is undefined. Must explicitly invoke another constructor
public Father() {
System.out.println("Father父类的无参构造方法");
}
·
public Father(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Father父类的有参构造方法");
}
·
}
如果在父类中存在有参的构造方法,但没有重载无参的构造方法,那么子类中必须显式地调用父类有参的构造方法。否则会产生如下异常:
Implicit super constructor Father() is undefined. Must explicitly invoke another constructor。
这是因为子类中默认会去调用父类中无参的构造方法,而在父类中如果没有无参的构造方法就会出错。
1.2 定义子类
接着我们定义一个带有2个构造方法的子类,如下所示:
/**
*
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义子类
*/
public class Son extends Father{
·
//子类自己的私有属性
private String hobby;
private int height;
private String job;
·
//如果在父类中存在有参的构造方法,但没有重载无参的构造方法,那么子类中必须显式地调用父类有参的构造方法。否则会产生如下异常:
//Implicit super constructor Father() is undefined. Must explicitly invoke another constructor
public Son() {
//不用显式调用super();方法
//super();
//父类中存在有参构造方法,但没有重载无参构造方法,需要显式调用如下方法。
//super("",11);
//这里会隐式地调用父类的无参数构造方法
System.out.println("Son子类的无参构造方法");
}
·
public Son(String name,int age,String job) {
//super();
//子类显式地调用父类中带有参数的构造方法
super(name, age);
this.job = job;
System.out.println("Son子类的有参构造方法"+job);
}
·
}
从这些案例中我们可以知道,子类不会继承父类任何的构造方法,子类默认的构造方法是Java自动生成的,不是继承来的!
1.3 测试类
这里定义一个测试类,测试上面的继承关系,如下所示:
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
*/
public class FatherTest {
·
public static void main(String[] args) {
//创建第一个子类对象
Son son1 = new Son();
//创建第二个子类对象
Son son2 = new Son("小棒",38,"盗窃");
}
·
}
执行结果如下图所示:
1. 私有属性不能被继承
父类中的私有属性不能被子类继承,公开的属性是可以的,如下图所示:
但private私有的修饰符,有可能会使得继承的作用被削弱。所以有时候为了让子类可以访问父类的某些字段,我们可以把private改为protected关键词,用protected修饰的字段可以被子类访问。protected关键字可以把字段和方法的访问权限控制在继承树的内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。
另外父类中使用默认修饰符修饰的属性和方法,在不同包的子类中也不能被继承。
3. final类不能被继承
假如我们把上面的父类进行调整,用final关键字修饰Father类,如下图所示:
此时子类就会出现如下图所示的提示信息:
“The type Son cannot subclass the final class Father”,即子类不能继承final类!
四. 新特性(拓展)--sealed+permits阻止继承
1. 概述
一般情况下,只要一个类没有被final修饰,那么任何类都可以继承该类。但从JDK 15开始,允许使用sealed(密封)关键字来修饰class,并利用permits(许可)关键字明确写出能够从该类继承的子类名称。
2. 示范案例
2.1 定义Shape类
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义一个父类的“形状类”
·
·
·
Permitted class Triangle does not declare demo14.Shape as direct super class
*/
public sealed class Shape permits Rect, Circle, Triangle {
·
...
·
}
Shape类是一个被sealed修饰的类,它指定了3个类Rect/Circle/Triangle可以继承它,我们是利用permits关键字实现允许继承。sealed类主要用于一些框架中,防止继承被滥用!
2.2 定义Rect类
/**
· @author 一一哥Sun
· QQ:2312119590
· CSDN、掘金、知乎找我哦
·
· 定义一个子类的“矩形类”
·
· The class Rect with a sealed direct superclass or a sealed direct superinterface Shape
· should be declared either final, sealed, or non-sealed
*/
public final class Rect extends Shape{
}
Rect类可以继承Shape类,因为Rect类是Shape的permits允许列表中的一个,属于白名单中的类。但如果是别的不在permits列表中的类就会报错。
2.3 定义Circle类
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义一个子类的“矩形类”
·
·
·
The class Rect with a sealed direct superclass or a sealed direct superinterface Shape
·
·
should be declared either final, sealed, or non-sealed
*/
public final class Circle extends Shape{
·
...
·
}
2.4 定义Triangle类
/**
·
@author 一一哥Sun
·
·
QQ:2312119590
·
·
CSDN、掘金、知乎找我哦
·
·
·
定义一个子类的“矩形类”
·
·
·
The class Rect with a sealed direct superclass or a sealed direct superinterface Shape
·
·
should be declared either final, sealed, or non-sealed
*/
public final class Triangle extends Shape{
·
...
·
}
2.5 定义Ellipse类
/**
· @author 一一哥Sun
· QQ:2312119590
· CSDN、掘金、知乎找我哦
·
· 定义一个子类的“三角形类”
·
· The type Ellipse extending a sealed class Shape should be a permitted subtype of Shape
*/
public final class Ellipse extends Shape{
//Compile error: class is not allowed to extend sealed class: Shape
...
}
Ellipse类没有出现在Shape的permits列表中,就不能继承Shape类,否则就会报错:The type Ellipse extending a sealed class Shape should be a permitted subtype of Shape。Compile error: class is not allowed to extend sealed class: Shape
把Java里的继承给大家讲解完毕了,现在你知道继承有什么特点了吗?关于继承,有如下几个要点:
● 继承是面向对象编程的一种强大的代码复用方式;
● Java只允许单继承,所有类最终的根类都是Object,C++可以有多重继承(即一个子类有多个直接父类);
● 父类中的 private 成员在子类中是不可见的,子类中不能直接使用它们;
● protected允许子类访问父类的字段和方法;
● 在子类的构造方法中可以通过super()调用父类的构造方法;
● 子类一般比父类包含更多的属性和方法;
● 子类和父类的关系是is a,has关系不能用继承,但也并不是所有符合“is-a”关系的都应该用继承。如正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。
关于继承的内容其实还有很多,比如super关键字的详情、父子类之间的转型问题等,更多关于继承的内容,壹哥会在后面的文章中专门进行讲解。
相关文章
07.13抢座
06.15抢座
06.29抢座
06.15抢座
06.29抢座
06.29抢座
06.15抢座
06.29抢座
06.29抢座
06.15抢座
了解千锋动态
关注千锋教育服务号
扫一扫快速进入
千锋移动端页面
扫码匿名提建议
直达CEO信箱