cs61b week3 -- Subtype Polymorphism vs. HoFs

(注:本篇文章有点难理解,所以我直接大部分翻译原文了......标题:Subtype Polymorphism vs. Explicit Higher Order Functions)
1.热身:Dynamic method selection
假设我们有两个类,Dog和ShowDog,其中showDog implement Dog,且二者都有bark()方法,showDog Override bark()方法
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

总结一下"is-a"关系,我们可以得到:

  • 每条showDog都是Dog
  • 每条Dog都是Object
    • Java中所有类型均是Object的子类型
现在,考虑一下以下代码:
Object o2 = new ShowDog("Mortimer","Corgi",25,512.2); ShowDog sdx = ((ShowDog)o2); sdx.bark(); Dog dx = ((Dog)o2); dx.bark(); ((Dog)o2).bark(); Object o3 = (Dog) o2; o3.bark();

对于每一行赋值,考虑是否会引起编译错误
每一次对bark()的调用,考虑是调用的ShowDog.bark()呢?还是Dog.bark()?还是语法错误
复习一下规则:
  1. 编译器允许内存盒存放任何子类型的变量
  2. 编译器检查某方法是否可以调用取决于变量的静态类型
  3. Dynamic method selection只对子类Overridden 非静态方法管用,且发生在运行时(runtime),此时对方法的调用取决于变量的动态类型
  4. casting(强制类型转换)并非长时间有效,只瞬间作用于使用强制类型转换的某一特定的表达式(相当于在该行代码有效,下一行即失效,变量还原成原来的类型),相当于哄骗编译器:"相信我,该变量就是这种类型",从而绕过编译器的类型检查,本质上强制类型转换并不会改变变量所指向的真实类型
hiding hiding是一种bad style,例如:
  • 子类中的变量与父类中的变量同名
  • 子类中有静态方法与父类中的静态方法声明一致(包括参数名)
    • 对于静态方法,我们不叫Override,称为hiding
以上称为hiding,josh并不打算在61B中教授,因为他认为这是一种语法错误,if you interested it,see this link
2.子类多态(Subtype Polymorphism)
多态:为不同类型的实体提供单一接口
在Java中,多态性是指对象可以有多种形式或类型。在面向对象编程中,多态性涉及到一个对象如何被视为其自身类的实例、其超类的实例、其超类的超类的实例,等等。
3.DIY Comparison
假设我们现在需要写一个程序,其功能是打印出两个Object中较大的一个
在python中,
1.使用高阶函数显式调用的方法写:
def print_larger(x, y, compare, stringify): if compare(x, y): return stringify(x) return stringify(y)

有时,compare()也叫做回调(callback)
2.使用子类型多态性的方法写:
def print_larger(x, y): if x.largerThan(y): return x.str() return y.str()

使用高阶函数显式调用的方法,你能以一种常见的方式打印出结果,与此相反,在子类型多态性的方法中,对象对largeFunction()的调用取决于x和y实际上是什么
假设我们想写一个max(),可以返回任何类型的数组中的最大值元素
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

对于红色方框内的代码,哪一行会编译错误?
显然是
items[i] > items[maxDex]

因为Java中,Object不能使用 > 进行相互比较,除此之外,Java中不支持运算符重载,这也意味着Java不能像C++与python重载 >
一种native的解决方法是,在Dog class里面定义max()方法:
public static Dog maxDog(Dog[] dogs) { if (dogs == null || dogs.length == 0) { return null; } Dog maxDog = dogs[0]; for (Dog d : dogs) { if (d.size > maxDog.size) { maxDog = d; }} return maxDog; }

然后就可以调用:
Dog[] dogs = new Dog[]{d1, d2, d3}; Dog largest = Dog.maxDog(dogs);

但是这样做的坏处是,假如当前Object不是Dog呢?是Cat , Fish , or Lion?我们岂不是要为每一个Animal class写一个max()吗?
我们希望能够创造一个适用于一般类型的max(),可以比较任何Animals,因此,Solution is
  • 创造一个interface,并在其中声明一个comparison method
  • 创造Dog class ,implements interface
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

public interface OurComparable { public int compareTo(Object o); //参数Obejct 也能改成 OurComparable,无区别 }

对于compareTo()函数,定义其返回值为:
  • if 当前对象this < 对象o, 返回负数
  • if 二者相等,返回0
  • if 当前对象this > 对象o, 返回正数
    我们使用size作为比较的基准
public class Dog implements OurComparable { private String name; private int size; public Dog(String n, int s) { name = n; size = s; }public void bark() { System.out.println(name + " says: bark"); }public int compareTo(Object o) { Dog uddaDog = (Dog) o; return this.size - uddaDog.size; } }

请注意,因为compareTo(Object o) 的传参是任意的Object o,所以我们需要将Object类型的 o 强制类型转换为 Dog 类型(使用uddaDog存放o),以保证可以使用 size 变量进行比较,否则Object class 中没有 size
完成以上工作之后,我们就可以一般化max()方法了,将适用于任何类型的Animals,而不再单一对Dog有效:
public class Maximizer {public static OurComparable max(OurComparable[] items) { int maxDex = 0; //OurComparable也可以改成Object for (int i = 0; i < items.length; i += 1) { int cmp = items[i].compareTo(items[maxDex]); if (cmp > 0) { maxDex = i; } } return items[maxDex]; } }

对Dog进行max():
Dog[] dogs = new Dog[]{d1, d2, d3}; Dog largest = (Dog) Maximizer.max(dogs);

此外,如果我们想比较Cat class,就创造一个Cat class去implement ourComparable interface,因此,此时的max()是对所有Animals适用
4.Interfaces Quiz
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

Q1:如果我们忽视 Dog 类里面的 compareTo() ,哪个文件会编译错误?
A: Dog.java 会编译错误,因为 Dog 声明了 implements OurComparable 接口,就表示Dog class 承诺会实现 OurComparable 所声明的所有 method(),而当 Dog class 中并未包含这些方法时,便会报错
而且 DogLauncher.java 也会报错,因为 Dog.java 的编译错误导致 javac 不能生成 Dog.class 文件,那么在 DogLaucher.java 中使用 Dog.则会报错
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

Q2:如果我们忽视 Dog class header 中的 implements OurComparable,下列哪个文件会报错?
A: DogLauncher.java 会报错,因为当 Dog class 并未声明 implements 时, Dog class 并不属于 OurComparable 的子类,那么 OurComparable 的内存盒并不能容纳 Dog 类型,因此当试图向 Maximizer.max() 传入 Dog 类型的 dogs 数组作为参数时,会报错
5.Comparables接口
我们完成了 OurComparable 接口,但其仍有许多不完美之处,例如:
  • 每次都要在 Object 之间进行强制类型转换:
Dog uddaDog = (Dog) o;

Dog[] dogs = new Dog[]{d1, d2, d3}; Dog largest = (Dog) Maximizer.max(dogs);

  • No existing classes implement OurComparable (e.g. String, etc.)
  • No existing classes use OurComparable (e.g. no built-in max function that uses OurComparable)
Java有一个内置的接口更加完善与强大,其与我们实现的 OurComparable 相似,且支持泛型
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

使用:只需将 T 替换成 Dog 即可
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

6. Comparator
考虑一下Java如何实现函数回调(callback),在python中,使用高阶函数显示回调,compare作为参数传入
def print_larger(x, y, compare, stringify): if compare(x, y): return stringify(x) return stringify(y)

如何修改子类型多态的方式实现回调呢?
def print_larger(T x, T y): if x.largerThan(y): return x.str() return y.str()

加入一个参数 comparator c
def print_larger(T x, T y, comparator c): if c.compare(x, y): return x.str() return y.str()

也就是我们要介绍的Java另一个内置的接口 Comparator ,上文中我们比较的是两条狗的 size 假如我们按字母的字典序对它们的名字作比较呢?就可以用到 Comparator
因为 Comparator 是一个 Object,我们使用 Comparator 的方式是在 Dog class 内嵌套另一个 class 去 implements the Comparator 接口
public interface Comparator { int compare(T o1, T o2); }

其规则与 compareTo() 类似:
  • Return negative number if o1 < o2.
  • Return 0 if o1 equals o2.
  • Return positive number if o1 > o2.
import java.util.Comparator; public class Dog implements Comparable { ... public int compareTo(Dog uddaDog) { return this.size - uddaDog.size; }public static class NameComparator implements Comparator { public int compare(Dog a, Dog b) { return a.name.compareTo(b.name); } }}

由于嵌套类NameComparator并没有使用外部类 Dog 的任何成员变量(实例变量),因此可以改为 static 优化内存空间
在DogLauncher.java中调用的形式为:
Dog.NameComparator nc = new Dog.NameComparator();

但是现代Java代码风格并非这样调用,我们考虑将 class NameComparator 封装,修改为 private:
private static class NameComparator implements Comparator { public int compare(Dog a, Dog b) { return a.name.compareTo(b.name); } }public static Comparator getNameComparator() { return new NameComparator(); }

如此一来在DogLauncher.java中调用则:
Comparator cd = new Dog.NameComparator(); if (cd.compare(d1, d3) > 0) { d1.bark(); } else { d3.bark(); }

【cs61b week3 -- Subtype Polymorphism vs. HoFs】作用是一样的,后者为现代code风格
cs61b week3 -- Subtype Polymorphism vs. HoFs
文章图片

接口为我们提供了进行函数回调的能力。
  • 有时一个函数需要另一个可能还没有被写出来的函数的帮助。
    • 例如:max需要compareTo
    • 这个辅助函数有时被称为 "回调"。
  • 有些语言使用显式函数传递来处理这个问题,例如python,javascript。
  • 在Java中,我们通过将所需的函数封装在一个接口中来实现(例如,Arrays.sort需要compare,它位于comparator接口中)。
  • Arrays.sort在需要比较的时候 "回调"。
    • 类似于在别人需要信息时把你的号码给他们。

    推荐阅读