和现实世界中:子女可以继承父母的一些特征(如:基因)、财产等一样。OOP 中也有提供类似的特性,一个类完全可以从其它类里获得一些属性和方法,而不需要我们自己重新定义。这种特性简单但强大 (Simple and powerful)。
快速了解继承
在 Java 的继承关系里:子类可以从获取父类的所有的公共和受保护成员(字段、方法和内部类)。当然,构造方法不是成员 (members) ,所以不能被继承。同时,在 Java 的继承里,子类可以做如下事情:
- 直接使用继承来的字段
- 直接使用继承来的方法
- 声明和父类同名字段,隐藏 掉父类字段
- 通过父类提供的公有/受保护的方法访问父类的私有成
- 创建新的字段
- 重写父类同方法签名的实例方法,隐藏 掉父类方法
- 重写父类同方法签名的静态方法,隐藏 掉父类方法
- 编写父类中不存在的方法
- 使用 super 关键字,利用父类构造方法
覆盖
当子类拥有和父类(或接口)同样方法签名的方法,这种现象叫做覆盖。如以下代码:
class Father
{
public void doSomthing(){
// Father do something
}
}
class Son extends Father{
@Override
public void doSomething(){
// Son do something
}
}
- 覆盖父类实例方法
- 覆盖父类静态方法
- 覆盖接口默认方法 (JDK 8+)
对于实例方法的覆盖,实际是子类拥有自己的方法。
对于静态方法的覆盖,要记住:静态方法时属于类的,在多态中,调用的始终是类的方法。接口里的静态方法永远不会被覆盖。
Father father = new Son();
Father.staticMethod(); // 这里使用的是父类Father的静态方法
对于接口方法的覆盖,遵循以下原则:
1.实例方法的优先级大于接口方法 (JDK8+)
interface Animal{
default void saySomething(){
// Animal say something
}
}
interface Cow extends Animal{
default void saySomething(){
// Cow say something
}
}
class MyCow implements Cow{
@Override
public void saySomething(){
// MyCow say something
}
Animal myCow = new MyCow();
myCow.saySomething(); // MyCow say something
}
2.有共同祖先的接口,先被覆盖的方法(继承深度低)会被后覆盖的方法(继承级别高)覆盖
interface Animal{
default void saySomething(){
// Animal say something
}
}
interface Pig extends Animal{
default void saySomething(){
// Pig say something
}
}
interface BigPig extends Pig{
default void saySomething(){
// BigPig say something
}
}
class MyPig implements Pig, BigPig{
public static void main(String...args){
MyPig myPig = new MyPig();
myPig.saySomething(); // BigPig saySomething()
}
}
P.S. 如果出现同一继承级别(上例中 BigPig 和 Pig 都继承 Animal 接口)或者子类继承的接口无相关关系,但是接口间有同方法签名的方法,就会出现覆盖冲突。需要用 super
关键字指明具体实现哪个接口的方法或者直接覆盖。
interface Run{
default void run(){
// Run run
}
}
interface Car{
default void run(){
// Car run
}
}
class MyCar implements Run, Car{
@Override
public void run(){
Car.super.run();
}
}
同时,我们需要注意,Java 子类覆盖父类方法,应该:
- 方法的访问权限大于等于父类
- 方法的返回值小于等于父类
- 方法抛出的异常小余等于父类
如果子类定义和父类同方法签名的方法,会有如下结果:
x | 父类的实例方法 | 父类的静态方法 |
---|---|---|
子类的实例方法 | 覆盖父类方法 | 编译错误 |
子类的静态方法 | 编译错误 | 隐藏父类方法 |
多态
我们已经知道,子类可以覆盖父类的方法。如果有一个类继承自另外一个类,我们完全可以用一个父类来引用一个子类,如:
class Person{
public void saySomething(){
// Person say something
}
}
class Father{
@Override
public void saySomething(){
// Father say something
}
}
class Test{
pubilc static void main(String...args){
Father father = new Father();
Person person = father; // 父类引用子类对象
}
}
像这种父类引用子类对象的现象,Java 里叫做多态 (Polymorphism)。在 Java 里,只有满足如下三个条件,才能叫多态:
- 继承关系
- 覆盖方法
- 父类引用子类对象
对于多态方法的运行,编译器会列举所有父类和子类符合调用方法签名(方法名+参数列表)的方法,然后以以下原则编译、调用方法:
- 成员变量(编译和运行都看左边)
- 成员方法(编译看左边,运行看右边)
- 静态方法(编译和运行都看左边)
其中,调用 private, final, static 方法的过程叫静态绑定,否则叫动态绑定。
在使用多态的过程中,最有效的判断语句能否通过编译的方法是:
右边的类是否是(IS-A)左边的类
如示例代码里的 father(Father) IS-A Person。
阻止继承
有些情况下,我们可能不希望子类覆盖父类的方法,这时候,用 final 关键字修饰方法即可实现该目的。编译器可以对用final的方法进行内联操作优化处理。
强制转换
在多态里,我们知道一个父类可以引用一个子类对象:
Father father = new Son();
但是,反过来就不行了:
Son son = new Father();
如果需要让编译器不报错,我们就得进行强制类型转换操作:
Son son = (Son)new Father();
不过,这么做有风险,我们一般要使用 instanceof
关键字先判断,能否将父类“安全”转换成子类:
Father f = ...;
if (f instanceof Son){
Son son = (Son)f;
}
抽象类与接口
在继承体系里,比较常用的就是抽象类和接口。它们比较相似:
- 都能被继承
- 都包含抽象方法
- 都不能被实例化
然而,它们还是有不同的,例如:
- 抽象类可以声明非 final&&static 的字段,接口不行(默认 public static final )
- 抽象类可以有构造方法,接口不行
- 抽象类可以声明非 public 方法,接口不行(默认方法是 public )
- 一个类只能继承一个抽象类,可以继承多个接口
然后,抽象类和接口有不同的使用场景:P
抽象类:
- 在相关类里共享代码
- 规定了一系列通用的方法和属性
- 需要定义非静态、非共有的方法
接口:
- 定义属性,如可比较 (Comparable)、可飞(Flyable)
- 定义行为,不关心具体实现
- 希望使用多继承
继承的技巧
在 Java 里,我们一般按照如下规则使用继承:
- 将公共操作放在超类(父类)中
- 不要使用受保护的域
- 继承严格遵循
is-a
原则 - 不要过多得使用反射
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。