type
status
date
slug
summary
tags
category
icon
password
Property
如果已经定义了
Person
类:现在,假设需要定义一个
Student
类,字段如下:Student
类包含Person
类已有的字段和方法,只是多了一个score
字段和相应的getScore()
、setScore()
方法。能不能在
Student
中不要写重复的代码?这个时候,继承就派上用场了。
继承
继承是面向对象编程中非常强大的一种机制,可以复用代码。
Student
从Person
继承时,获得了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
,都会继承自某个类。下图是
Person
、Student
的继承树:Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有
Object
特殊,它没有父类。类似的,如果定义一个继承自
Person
的Teacher
,它们的继承树关系如下:protected
继承有个特点,就是子类无法访问父类的
private
字段或者private
方法,这使得继承的作用被削弱了:为了让子类可以访问父类的字段,需要把
private
改为protected
,protected
修饰的字段可以被子类访问:因此,
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
出现在Shape
的permits
列表中。但是,如果定义一个Ellipse
就会报错:原因是
Ellipse
并未出现在Shape
的permits
列表中。这种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
继承。究其原因,是因为
Student
是Person
的一种,它们是is关系,而Student
并不是Book
。实际上Student
和Book
的关系是has关系。具有has关系不应该使用继承,而是使用组合,即
Student
可以持有一个Book
实例:因此,继承是is关系,组合是has关系。