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
2
3
4
5
6
7
8
9
10
public interface List61B<Item> {
public void addFirst(Item x);
public void add Last(Item y);
public Item getFirst();
public Item getLast();
public Item removeLast();
public Item get(int i);
public void insert(Item x, int position);
public int size();
}

  \(AList\)等可以如下定义:

1
public class AList<Item> implements List61B<Item>{...}

  这里的implements List61B<Item>实际上是一种承诺(\(promise\)),保证\(AList\)实现了接口要求实现的方法。

3.覆盖

  在实现接口所需的方法时,在方法前面涵盖一个@Override的标记是很有用的:

1
2
3
4
@Override
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
6
default 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
@Override
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
14
public 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\)在相同名称的方法间选择时,会检查与静态变量类型相同的方法,然后调用它。