Interface Inheritance
为了实现不同种类对象对同一上位的继承,Java使用了接口(interface)。
实现接口有两个步骤:
-
定义这个上位
包含所有方法签名,但不包含实现,子类负责提供所有这些实现。
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();
}
- 使用
implements明确指出这些对象是它的下位
public class AList<Item> implements List61B<Item>{...}
public class SList<Item> implements List61B<Item>{...}
//implements 说明该类拥有并定义了 List61B 接口中指定的所有属性和行为。
所以具体而言使用接口有什么好处呢?
-
当我们实现了
List61B这个接口,此时AList和SLList都implements List61B即承诺自己有 get 和 size 方法。 -
那么在 WordUtils 中定义寻找最长字符串的方法时就只需要写一遍:
public class WordUtils {
// 只要是 List61B 的下位都能直接使用
public static String longest(List61B<String> list) {
String max = "";
for (int i = 0; i < list.size(); i++) {
if (list.get(i).length() > max.length()) {
max = list.get(i);
}
}
return max;
}
}
子类不仅继承上位的方法,也继承了上位上面的所有其他类,一直到最高父类。
Overriding
什么是重写?
-
当一个子类中实现了一个和上位中有完全相同签名的方法,我们就说这个子类重写了这个方法,并使用
@override作为标记。 -
这个标记并不会实现什么具体功能,只是在编译时确保上位存在这个方法,如果拼写不对就会认为这个方法没有被重写而报错,同时也提醒程序员这是对来自上位方法的重写。
那么重载(Overloading)呢?简单举例如下
public class Math{
public int abs(int a){}
public double abs(double a){}
}
Implementation Inheritance
我们可以在上位中编写已经实现好的方法,这些方法定义了上位词的行为方式,此时子类就是不仅继承上位的方法签名,也继承具体实现。
在编写时特别的是需要在方法签名前加上 default 关键字,否则会报错。
default public void print() {
for (int i = 0; i < size(); i += 1) {
System.out.print(get(i) + " ");
}
System.out.println();
}
此时会产生一个问题,就是这个实现可能不是对所有子类而言都很高效,但是没关系,对于那些不太高效的子类,我们可以在这个子类中重写这个方法。
@Override
public void print() {
for (Node p = sentinel.next; p != null; p = p.next) {
System.out.print(p.item + " ");
}
}
那么面对一个这样的对象List61B<String> list = new SLList<String>(); ,在调用 print() 方法时 Java会使用List61B 的还是 SLList 的呢?
-
首先我们要知道 Java 中所有变量有 编译时类型(静态类型)同时也有一个 运行时类型(动态类型)。在这里静态类型是
List61B动态类型是SLList,变量在没有实例化之前动态类型为null,在实例化时获得对应类型并且会根据当前所指对象的类型而变化。 -
同时 Java 采用了一种叫做动态方法选择(dynamic method selection)的技术,在调用时如果对象的动态类型重写了该方法,则会使用动态类型的方法。
-
综上,Java 会调用
SLList中重写的print()方法。
IMPORTANT 动态方法选择不会发生在重载
public static void peek(List61B<String> list) {
System.out.println(list.getLast());
}
public static void peek(SLList<String> list) {
System.out.println(list.getFirst());
}
因为两个重载方法之间的唯一区别在于参数类型,Java 在判断要调用哪个方法时,会检查静态类型,并调用参数类型相同的方法。
参考: