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)解体