(注:本篇文章有点难理解,所以我直接大部分翻译原文了......标题:Subtype Polymorphism vs. Explicit Higher Order Functions)
1.热身:Dynamic method selection
假设我们有两个类,Dog和ShowDog,其中showDog implement Dog,且二者都有bark()方法,showDog Override bark()方法
总结一下"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()?还是语法错误
复习一下规则:
- 编译器允许内存盒存放任何子类型的变量
- 编译器检查某方法是否可以调用取决于变量的静态类型
- Dynamic method selection只对子类Overridden 非静态方法管用,且发生在运行时(runtime),此时对方法的调用取决于变量的动态类型
- 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(),可以返回任何类型的数组中的最大值元素
对于红色方框内的代码,哪一行会编译错误?
显然是
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
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
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.则会报错
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 相似,且支持泛型
使用:只需将 T 替换成 Dog 即可
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
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();
}
作用是一样的,后者为现代code风格
接口为我们提供了进行函数回调的能力。
- 有时一个函数需要另一个可能还没有被写出来的函数的帮助。
-
- 例如:max需要compareTo
-
- 这个辅助函数有时被称为 "回调"。
- 有些语言使用显式函数传递来处理这个问题,例如python,javascript。
- 在Java中,我们通过将所需的函数封装在一个接口中来实现(例如,Arrays.sort需要compare,它位于comparator接口中)。
- Arrays.sort在需要比较的时候 "回调"。
-
- 类似于在别人需要信息时把你的号码给他们。