4.1.接口
\(4.1\)接口
1.上位词、下位词
在日常语言中,词语、对象之间具有逻辑上的层次性,例如:狗(\(dog\))是贵宾犬、哈士奇等的上位词(\(hypernym\)),而贵宾犬、哈士奇等是狗的下位词(\(hyponyms\))。
上位词与下位词描述的是一种\(is-a\)的层次关系:
- 贵宾犬\(is-a\)狗
- 狗\(is-a\)动物
同样地,我们前面讲解的\(SLList\)与\(AList\)也是链表的下位词。在\(Java\)中,\(SLList\)是\(List61B\)的子类(\(subclass\)),\(List61B\)是\(SLList\)的主类(\(superclass\))。
2.接口
在\(Java\)中,为了表达上述的层次结构,我们会进行以下操作:
- 为通用列表(\(hypernym\))定义一个类型——我们将选择名称\(List61B\)。
- 指定\(SLList\)与\(ALList\)是\(List61B\)的下位词。
这里的\(List61B\)被称为接口(\(interface\))。它本质上是一个契约(\(contract\)),记录了一个链表必须能够做的方法,但它不提供任何方法的实现。下面是\(List61B\)的接口:
1 | public interface List61B<Item> { |
\(AList\)等可以如下定义:
1
public class AList<Item> implements List61B<Item>{...}
这里的implements List61B<Item>
实际上是一种承诺(\(promise\)),保证\(AList\)实现了接口要求实现的方法。
3.覆盖
在实现接口所需的方法时,在方法前面涵盖一个@Override
的标记是很有用的:
1
2
3
4
public void addFirst(Item x) {
insert(x, 0);
}
4.接口的继承
接口的继承(\(inheritance\))指代了一种关系,即子类继承了主类的所有的方法与行为。在我们定义的\(List61B\)中,接口继承了所有的方法签名(\(signature\)),但不包括实现。
5.\(GRoE\)
在定义了\(is-a\)关系后,我们就可以实现以下语句了:
1
SLList<String> someList = new List61B<String>();
这是因为\(SLList\)\(is-a\)\(List61B\),这说明\(SLList\)可以放进\(List61B\)的内存盒子中,因此可以这么写。但反过来则不对。
5.实现的继承
之前对接口的讲解中,我们说\(List61B\)中只有标识\(List61B\)做什么(\(what\))的方法名称(\(method\;header\))。接下来我们在\(List61B\)中实现具体的方法。
如果我们在\(List61B\)中加入如下方法: 1
2
3
4
5
6default public void print() {
for (int i = 0; i < size(); i += 1) {
System.out.print(get(i) + " ");
}
System.out.println();
}
那么,所有对\(List61B\)的实现(\(implements\))都可以使用这个方法。
但是,该方法中的\(get(i)\)函数对\(SLList\)来说效率过低,于是我们可以在\(SLList\)子类中覆盖这一方法:
1
2
3
4
5
6
public void print() {
for (Node p = sentinel.next; p != null; p = p.next) {
System.out.print(p.item + " ");
}
}
这样,在对\(SLList\)调用\(print()\)时,\(SLList\)就会使用覆盖后的方法了。
6.动态方法选择
我们知道,\(Java\)中的每一个变量都有一个类型:
1
List61B<String> lst = new SLList<String>();
在上面实例化的声明中,\(lst\)的类型是\(List61B\),这叫作静态类型(\(static\;type\))。
然而,对象自己也有类型。\(lst\)指向的对象具有\(SLList\)类型,同时它也是\(List61B\)类型,这是由于两者的\(is-a\)关系。我们称它的\(List61B\)类型为动态类型(\(dynamic\;type\))。
Aside: the name “dynamic type” is actually quite semantic in its origin! Should lst be reassigned to point to an object of another type, say a AList object, lst’s dynamic type would now be AList and not SLList! It’s dynamic because it changes based on the type of the object it’s currently referring to.
考虑下面的例子: 1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void peek(List61B<String> list) {
System.out.println(list.getLast());
}
public static void peek(SLList<String> list) {
System.out.println(list.getFirst());
}
SLList<String> SP = new SLList<String>();
List61B<String> LP = SP;
SP.addLast("elk");
SP.addLast("are");
SP.addLast("cool");
peek(SP);
peek(LP);
第一个\(peek\)调用的是第一个,而第二个\(peek\)调用的是第二个。\(Java\)在相同名称的方法间选择时,会检查与静态变量类型相同的方法,然后调用它。