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:
@Override
public 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
@Override
public 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) 解体