cs61b week3--Intro and Interfaces

1.接口继承 Interface Inheritance

回想上周我们所实现的SLList类和AList类,其中包含很多相似的method,比如

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

假设我们在某个类中调用SLList,当需要调用AList的时候要么把代码中的SLList改为AList,要么把AList的class全部搬过来。
事实上,这两者包含很多重复的method,可见它们来自同一个父类:List,它俩都是List的继承,我们只需定义一个父类,然后分别让AList和SLList继承父类的特性即可,从而避免了很多重复的冗长代码。
cs61b week3--Intro and Interfaces_第1张图片
如何定义父类与子类的继承关系呢?
在 Java 中,为了表达这种层次结构,我们需要做两件事:

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

我们使用一直新的类型叫做interface(接口)来表示上位词,与之前我们经常定义类public class 类名不同,接口定义为public interface 接口名
在接口里面我们只需写里面包含的函数声明,而不需要写具体实现的代码:

public interface List61B {
    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 {...}

使用关键字implements

public class AList implements List61B{...}

implements List61B本质上是一个承诺。意味着AList “将拥有并定义 List61B 接口中指定的所有属性和方法”
至此我们完成了父类List61B与子类AList的继承关系


2.@Override标签

我们在上面List61B interface里定义了一些通用的method,不能是private类型,这代表所有的List61B的子类都拥有这些method
在AList继承之后,可以根据需求具体地实现这些method,对于List61B的所有method,在AList中都可以重写并覆盖
子类中继承父类的method实际上是一种Override(覆盖),比如addFirst(),在List61B中:

public interface List61B {
    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类型的

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

是否可以定义AList类型的实例a1并将其作为参数传入longest()?

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

答案是可以,如果X 是 Y 的父类,那么 X 的内存盒可以容纳 Y,也就是两者可以传参与赋值:


   List61B someList = new SLList();    
   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 someList = new SLList();

然后调用

   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等同于该对象的类型
    cs61b week3--Intro and Interfaces_第2张图片

设想我们使用一个变量调用对象的某方法,该变量拥有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
cs61b week3--Intro and Interfaces_第3张图片

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的区别,一个小测验如下:
cs61b week3--Intro and Interfaces_第4张图片
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"

cs61b week3--Intro and Interfaces_第5张图片

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

public static void peek(List61B list) {
    System.out.println(list.getLast());
}
public static void peek(SLList list) {
    System.out.println(list.getFirst());
}

当你运行以下代码:

SLList SP = new SLList();
List61B 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 实现继承

cs61b week3--Intro and Interfaces_第6张图片
implementation Inheritance的缺点:

  • 当子类层级众多时,难以追踪子类Override的方法究竟在哪里,或是在哪里定义
  • 导致复杂的代码,当两个接口之间有相同的default method时,可能引起冲突
  • 封装(encapsulation)崩溃

你可能感兴趣的:(java)