1.接口继承 Interface Inheritance

回忆上周咱们所实现的SLList类和AList类,其中蕴含很多类似的method,比方

  • addLast()
  • addFirst()
  • removeLast().....

假如咱们在某个类中调用SLList,当须要调用AList的时候要么把代码中的SLList改为AList,要么把AList的class全副搬过去。
事实上,这两者蕴含很多反复的method,可见它们来自同一个父类:List,它俩都是List的继承,咱们只需定义一个父类,而后别离让AList和SLList继承父类的个性即可,从而防止了很多反复的简短代码。

如何定义父类与子类的继承关系呢?
在 Java 中,为了表白这种层次结构,咱们须要做两件事:

  • 步骤 1:定义通用列表上位词的类型——咱们将抉择名称 List61B。
  • 步骤 2:指定 SLList 和 AList 是该类型的下位词

咱们应用始终新的类型叫做interface(接口)来示意上位词,与之前咱们常常定义类public class 类名不同,接口定义为public 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();}

第 2 步,咱们须要指定 AList 和 SLList 是 List61B 类的下位词,在

public class AList<Item> {...}

应用关键字implements

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

implements List61B<Item>实质上是一个承诺。意味着AList “将领有并定义 List61B 接口中指定的所有属性和办法”
至此咱们实现了父类List61B与子类AList的继承关系


2.@Override标签

咱们在下面List61B interface里定义了一些通用的method,不能是private类型,这代表所有的List61B的子类都领有这些method
在AList继承之后,能够依据需要具体地实现这些method,对于List61B的所有method,在AList中都能够重写并笼罩
子类中继承父类的method实际上是一种Override(笼罩),比方addFirst(),在List61B中:

public interface List61B<Item> {    public void addFirst(Item x);}

在子类中为该method编写具体的实现代码时,在method右上方增加@Override

@Overridepublic void addFirst(Item x) {    insert(x, 0);}

这示意子类将笼罩父类的addFirst()办法,应用@Override有以下益处:

  • 查看拼写错误,假如你把 addFirst() 写成了 addFrist(),那么@Override会报红,提醒你并没有笼罩该办法,更加有利于Debug
  • 揭示程序员此办法是继承父类的,并曾经对其笼罩

即便你没有增加@Override标签,编译器也会主动将子类中addFirst()的实现代码笼罩掉父类中的addFirst()

Gold Rule Of Equal

回忆咱们第一周所学的黄金判等法令,假如

int a = 5;int b = a;

当咱们进行赋值操作时,咱们都会将 b 中的bit复制到 a 中,并要求 b 与 a 的类型雷同。你不能赋值Dog b = 1 或者Dog b = new Cat() 因为 三者类型并不相同
那么假如有一个办法的传参是List61B<String>类型的

public static String longest(List61B<String> list) {      int maxDex = 0;   for (int i = 0; i < list.size(); i += 1)   ...}

是否能够定义AList<String>类型的实例a1并将其作为参数传入longest()?

public static void main(String[] args) {   AList<String> a1 = new AList<String>();   a1.addLast("horse");   WordUtils.longest(a1);}

答案是能够,如果X 是 Y 的父类,那么 X 的内存盒能够包容 Y,也就是两者能够传参加赋值:

   List61B<String> someList = new SLList<String>();       someList.addFirst("elk");

3.实现继承 Implementation Inheritance

除了(Interface Inheritance)的之外,还有一种继承办法是实现继承(Implementation Inheritance)
在接口继承中,父类中所有的method均只有申明,而没有具体的代码实现,与之不同的是,在实现继承中,咱们能够为method编写具体的实现代码,在申明前增加关键字default

default public void method() { ... }

例如,为61BList编写一个print()函数:

default public void print() {    for (int i = 0; i < size(); i += 1) {        System.out.print(get(i) + " ");    }    System.out.println();}

当子类SLList运行时,只管子类中并没有申明该办法,因为继承个性,会默认执行61BList的print()函数,从而实现打印

然而父类中的print()并非对所有子类都高效,对于AList来说,get()是O(1)级的,因为能够间接通过数组下标取得该项的值,print()的工夫复杂度则是O(n),而对于SLList来说,因为是链表构造,当要找链表中的第i项时,需从sentinal开始沿着指针遍历前i-1项,复杂度是O(n),则对于print()是O(n²),反而会使SLList的打印十分低效
因而父类的print()并不适用于所有子类,解决方案是在SLList中重写并笼罩父类的print(),应用@Override

@Overridepublic void print() {    for (Node p = sentinel.next; p != null; p = p.next) {        System.out.print(p.item + " ");    }}

笼罩之后再次执行print()则是应用子类自定义的print()


4.Dynamic Method Selection

回忆一下咱们之前说的,如果 X 是 Y 的父类,那么 X 能够保留对 Y 的援用,那么有一个问题:
如果申明一个

 List61B<String> someList = new SLList<String>();

而后调用

   someList.print();

则someList会执行哪一个print()呢?是父类里的还是子类里的?
在解决此问题之前,须要理解一些对于Static and Dynamic Type的常识
在Java中,每个变量都领有两种类型,别离是compile-time type(static type)和run-time type(dynamic type):

  • static type 在变量申明时定义的类型,永远不会扭转
  • dynamic type 在编译运行时的类型,当应用new实例化时一个对象时,dynamic type等同于该对象的类型

构想咱们应用一个变量调用对象的某办法,该变量领有static type X和dynamic type Y

如果Y override 该办法,那么Y中重写的method将会替换掉原来X中的method
This is known as “dynamic method selection”.因而,print()实际上是调用的SLList外面的method, 而不是61BList外面的

Override VS Overload

如果在子类中有一个办法与父类齐全同名(包含参数与参数类型),那么咱们就称为子类对父类的该办法Override
如果子类有一个办法与父类同名,然而参数或参数类型不同,那么就称为Overload

The Method Selection Algorithm

Dynamic Selection Method算法原理:

思考一个函数调用 foo.bar(x1),其中 foo 具备动态类型 TPrime,而 x1 具备动态类型 T1。

在编译时,编译器会验证 TPrime 是否具备能够解决 T1 的办法。 如果存在,则记录此办法的签名。
留神:如果有多个办法能够解决T1,编译器会记录“最具体”的一个。 比方T1=Dog,TPrime有bar(Dog)和bar(Animal),就会记录bar(Dog)。

在运行时,如果 foo 的动静类型 Override了记录的签名,则应用Override的办法。 否则,应用 TPrime 版本的办法。

为验证您是否搞懂Override与Overload的区别,一个小测验如下:

what's the answer of red box?

首先a的动态类型是Animal,动静类型是Dog
d的动态类型和动静类型都是Dog,根据上述算法:

a.greet(d);步骤:验证Animal是否有greet()办法-->是,下一步验证greet()是否被子类Dog Override-->否,调用Animal的greet()输入hellow animal
a.sniff(d);步骤:验证Animal是否有sniff()办法-->是,下一步验证sniff()是否被子类Override-->是,调用Dog的sniff()办法输入dog sniff animal
d.flatter(d);d动态类型和动静类型均为Dog,间接调用Dog的flatter输入dog sniff animal
a.flatter(d);步骤:验证Animal是否有flatter()办法-->是,下一步验证flatter()是否被子类Override-->否,Dog类的flatter()和Animal类的flatter()同名,但参数类型不同,属于Overload,而不是Override!调用Animal的flatter()办法,不调用Dog的flatter(),因为不是Override输入"u r cool animal"

对于Overload的练习:
假如以下method均在同一个class之中:

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);

会先后输入什么?
答案是
elk
cool
因为peek()是Overload,并不进行Dynamic Selection Algorithm,因而编译器会抉择动态类型对应的peek()括号内的参数类型,并传入执行


5.接口继承 VS 实现继承


implementation Inheritance的毛病:

  • 当子类层级泛滥时,难以追踪子类Override的办法到底在哪里,或是在哪里定义
  • 导致简单的代码,当两个接口之间有雷同的default method时,可能引起抵触
  • 封装(encapsulation)解体