Extends 关键字
- Implements 关键字:
- 用于定义类与接口的层次关系
- Extends 关键字:
- 定义类与类之间的层次关系
- 使用方式:
public class RotatingSLList<Item> extends SLList<Item>
总结:
假设有 VengefulSLList extends SLList,则他们:
- 满足is-a 关系
- VengefulSLList 是 SLList(subtype polymorphism)
- 满足继承内容
- 子类继承父类的所有成员(字段、方法、嵌套类)
- private 也被继承,但不可访问
- 满足构造函数规则
- 子类构造函数必须调用父类构造函数
- 使用
super关键字来调用重写的超类方法和构造函数
Object 类
实际上,java 中的每个类都是 Object 的子类,也就是 extends 了 Object 类(interface 除外)。即使某些类在其类声明中没有显式的 extends 语句,它们仍然隐式地继承了 Object 类。
类型检查 - Static type 和 dynamic type
- Static type:
- 在 declaration 时指定的类型
- Dynamic type:
- 在 instantiation 时指定的类型
- 根据它当前所指向的对象的类型而变化
例如:X something = new Y() 的静态类型是 X,动态类型是 Y
- 若执行
something = new Z()则:- 动态类型变为
Z - 静态类型永远为
X
- 动态类型变为
总结:调用重写方法所遵循的两条规则
- 编译器仅允许我们根据静态类型进行操作。
- 在编译阶段,编译器会先依据对象的静态类型以及它的父类或接口,来检查方法调用是否合法
- 如果没有在该类及它的父类中找到这个方法,则为 compile time error
- 在编译阶段,编译器会先依据对象的静态类型以及它的父类或接口,来检查方法调用是否合法
- 对于重写方法(非重载方法),实际调用的方法基于调用表达式的动态类型。
- 当试图调用一个被 override 的同名方法时,完全由对象的动态类型来决定最终执行的哪个类中的实现。
- 也就是说,编译阶段先确认能不能调用,运行阶段再决定调用哪个实现。
- 可以使用强制类型转换来覆盖编译器的类型检查,但如果转换不成立,则会在运行时出错。
表达式的动态类型
考虑以下表达式:
1 | SLList<Integer> sl = new VengefulSLList<Integer>(); |
该表达式右侧的编译时类型是 VengefulSLList,随后编译器检查以确保 VengefulSLList “is a“ SLList,接着便允许赋值。
1 | VengefulSLList<Integer> vsl = new SLList<Integer>(); |
上面这个表达式右侧的编译时类型是 SLList,随后编译器检查 SLList 并不符合 “is a” VengefulSLList,因此报错。
小结: 当右侧的编译时类型与左侧满足“is-a”关系时,赋值才会被允许
同样,某个方法也具有它的编译时类型:
1 | Poodle largerPoodle = maxDog(frank, frankJr) |
maxDog(...) 方法声明的返回类型是 Dog,所以整个表达式的 static type 是 Dog,和左侧的 Poodle 类型不符合“is-a“关系,因此报错。
封装
complexity 是编写程序中遇到的最大的困难。因此,程序应该被构建成模块化、可互换的组件,这些组件可以在不破坏系统的情况下进行交换,隐藏不需要的信息是管理大型系统的另一个基本方法。
模块可以被定义为一系列协同工作以完成一项任务或一组相关任务的方法集合。如果模块的实现细节被内部隐藏,并且唯一与之交互的方式是通过一个文档化的接口,那么该模块就被认为是封装的。
继承可能破坏封装,因为你不知道被封装模块内部的细节,擅自使用 implements inheritance 可能会引发错误。
类型转换(Casting)
Poodle largerPoodle = (Poodle) maxDog(frank, frankJr) ,这段表达式将 Dog 类型直接转换为 Poodle 类型。
这只在这个表达式中生效,它并没改变右侧表达式原本的任何性质,只是让它“看上去像”某个其他类型而已。
高阶函数
见 https://joshhug.gitbooks.io/hug61b/content/chap4/chap42.html