这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
继承
本章将学习面向对象编程的另一个概念:继承(inheritance)。继承的基本思想是,可以基于已有的类创建新的类。
Note: 一般而言,子类来继承超类,目的不是使用超类的方法,而是去修改超类的方法,或者是去扩展超类的方法。因此,继承一般很少去使用,如果处于使用为目的,那就可以使用依赖关系进行注入,或者使用代理模式来使用这些类。
继承特点
-
子类可以获得父类的所有属性和方法
class Parent { private int a = 1; } class Child extends Parent { private int b = 2; } Child c = new Child();上面的这个对象C,在对象头位置之外,还会保留两个变量大小的内存位置,一个存放
Parent.a另一个位置存放Child.b。因此,子类是可以获得父类的所有属性的。 -
子类可以在父类的基础上扩展,使得功能更具多样性
-
Java中的继承仅仅支持单继承,不支持多继承,但是Java中是存在继承层次的。Java中所有的类都直接或者间接继承于
Object类。
子类和超类
“is-a” 关系是继承的一个明显特征。
C++: Java 与 C++ 定义继承类的方式十分相似。Java 用关键字 extends 代替了 C++ 中的冒号(:)。在Java 中,所有的继承都是公有继承,而没有C++ 中的私有继承和保护继承。
定义子类
已存在的类称为超类(superclass)、 基类( base class) 或超类(parent class);新类称为子类(subclass)、 派生类(derived class) 或孩子类(child class)。
public class A extends B { }
子类能够继承超类所有的方法和属性,但是却不能访问超类的私有方法和属性。因此,private 保证了子类和超类之间的封装性。protected能够使得子类和超类之间能够相互调用,保证了超类和子类与其他类之间的封装性。
可以从三方面来考虑子类的继承内容:构造方法、成员变量、成员方法。其中构造方法没有办法被继承;成员变量在对象内存意义上可以全部被继承;成员方法也是被全部继承,关于方法的调用可以更多参考:理解方法的调用过程。
Note:
- 成员方法这个比较复杂,在《Java核心技术卷》中解释为子类继承了超类的全部属性和方法。
- 定义子类的时候,需要指出不同之处,因为上面说到,子类继承了超类的所有方法和属性,那么应该将一般的方法放在超类里面,而将更特殊的方法放在子类中,这种将通用的功能抽取到超类的做法在面向对象程序设计中十分普遍。
覆盖(override)
子类如果声明和超类方法签名一致的方法,那么子类调用此方法就需要关键字super来保证调用超类的方法,而不是子类的。
public class Child {
@Override
public Product method() {
// super.method(); 可以调用超类的方法
/* 重写的方法内容 */
}
}
@Override 是一个由编译器来检查的注解,可以在编译时检查重写的方法是否正确。
@Retention(RetentionPolicy.SOURCE) // 在源码期间有效
@Target(ElementType.METHOD) // 只能标注在方法上
public @interface Override {
}
对于覆盖/重写,本质上就是覆盖方法表中的记录,在调用的时候优先调用子类所重写的方法。
方法重写的原则:
- 重写方法的方法签名必须和父类一致
- 重写方法的访问权限必须大于等于父类
- 重写方法的返回值类型必须小于等于父类的方法返回值类型
- 私有方法,静态方法和声明为
final的方法,或者说是静态绑定的方法,无法被重写
子类构造器
如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。
如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java编译器报告错误。
/*
super 作为构造方法调用必须是第一句
this 作为构造方法调用必须是第一句
*/
public class Child extends Parent {
public Child() {
// super(); 在这里也无法使用
this("hello");
// super(); 在这里无法使用
/* 其他初始化过程 */
}
public Child(String[] args) {
super();
/* 其他初始化过程 */
}
}
关键字 this 有两个用途∶一是引用隐式参数,二是调用该类的方法,三是调用该类其他的构造器。
同样,super 关键字也有两个用途∶一是调用超类的方法,二是调用超类的构造器。
- 在调用构造器的时候,这两个关键字的使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this)的其他构造器,也可以传递给超类(super)的构造器。
this更多是这个对象引用,而super关键字并非这样,只是一个指示编译器调用超类方法的一个标志,不能将super赋给另一个对象变量。
继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次( inheritance hierarchy ) ,在继承层次中, 从某个特定的类到其祖先的路径被称为该类的继承链 ( inheritance chain) 。
C++中一个类可以有多个超类。Java不支持多重继承,但是提供了一些类似多重继承的功能 -- 接口。看下一章6.1节有关接口的讨论。
阻止继承:final 类和方法
将方法或者类声明为final的意义:确保它们不会再子类中改变语义。
Note: 在早期,为了避免动态绑定带来的开销,会使用final关键字。编译器会识别关键字,并且进行优化处理,这种处理叫做内联(inline) 。
受保护的访问
在类封装中会使用private关键字来标识,但是,如定义所见,private标识的类、方法、字段,只能由这个类自己来访问,包括派生类也就是子类也不能访问。那么就需要一种访问机制来为继承关系提供一种访问权限,那么这个访问就是受保护的访问,即protected。
在实践中,还需要谨慎使用受保护的字段。如果想要修改这个类的内容,那么就可能会导致其他派生类因此导致出现问题。受保护的方法更具有实践意义。需要限制某个方法的使用,但是子类还能使用,那么就需要将他声明为protected的方法。