🐿️继承 extends
2021-3-5
| 2023-8-3
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
如果已经定义了Person类:
现在,假设需要定义一个Student类,字段如下:
 
Student类包含Person类已有的字段和方法,只是多了一个score字段和相应的getScore()setScore()方法。
能不能在Student中不要写重复的代码?
这个时候,继承就派上用场了。
 

继承

继承是面向对象编程中非常强大的一种机制,可以复用代码。StudentPerson继承时,获得了Person的所有功能,我们只需要为Student编写新增的功能。
Java使用extends关键字来实现继承:
通过继承,Student只需要编写额外的功能,不再需要重复代码。
 
 
在OOP的术语中,Person称为超类(super class),父类(parent class),基类(base class);Student称为子类(sub class),扩展类(extended class)

继承树

我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。
下图是PersonStudent的继承树:
notion image
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
 
类似的,如果定义一个继承自PersonTeacher,它们的继承树关系如下:
notion image
 

protected

继承有个特点,就是子类无法访问父类的private字段或者private方法,这使得继承的作用被削弱了:
 
为了让子类可以访问父类的字段,需要把private改为protectedprotected修饰的字段可以被子类访问:
因此,protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问。
 

super

super关键字表示父类(超类),子类引用父类的字段时,可以用super.fieldName
实际上,这里使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段。
但是,在某些时候,就必须使用super
编译错误,在Student的构造方法中,无法调用Person的构造方法。
 
在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会自动加一句super();,所以,Student类的构造方法实际上是这样:
但是,Person类并没有无参数的构造方法,因此,编译失败。
解决方法是调用Person类存在的某个构造方法。例如:
这样就可以正常编译了!
因此:如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
 
 

阻止继承

正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。
例如,定义一个Shape类:
上述Shape类就是一个sealed类,它只允许指定的3个类继承它。如果写:
是没问题的,因为Rect出现在Shapepermits列表中。但是,如果定义一个Ellipse就会报错:
原因是Ellipse并未出现在Shapepermits列表中。这种sealed类主要用于一些框架,防止继承被滥用。
sealed类在Java 15中目前是预览状态,要启用它,必须使用参数--enable-preview--source 15
 
 

向上转型

如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:
如果一个引用类型的变量是Person,那么它可以指向一个Person类型的实例:
现在问题来了:如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,能否指向Student类型的实例?
这种指向是允许的!
Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!
这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。
向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object
 

向下转型

和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)
Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException
 
为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:
instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false
利用instanceof,在向下转型前可以先判断:
Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:
可以改写如下:
 
 

区分继承和组合

在使用继承时要注意逻辑一致性,考察下面的Book类:
这个Book类也有name字段,那么能不能让Student继承自Book呢?
显然,从逻辑上讲,这是不合理的,Student不应该从Book继承,而应该从Person继承。
究其原因,是因为StudentPerson的一种,它们是is关系,而Student并不是Book。实际上StudentBook的关系是has关系。
具有has关系不应该使用继承,而是使用组合,即Student可以持有一个Book实例:
因此,继承是is关系,组合是has关系。
  • Java
  • 构造方法多态
    目录