乐趣区

关于java: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()办法

总结一下 ”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(), 能够返回任何类型的数组中的最大值元素

对于红色方框内的代码,哪一行会编译谬误?
显然是

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<T> c

def print_larger(T x, T y, comparator<T> 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<T> {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<Dog> {
    ...
    public int compareTo(Dog uddaDog) {return this.size - uddaDog.size;}

    public static class NameComparator implements Comparator<Dog> {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<Dog> {public int compare(Dog a, Dog b) {return a.name.compareTo(b.name);
        }
    }

    public static Comparator<Dog> getNameComparator() {return new NameComparator();
    }

如此一来在 DogLauncher.java 中调用则:

Comparator<Dog> 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 在须要比拟的时候 “ 回调 ”。
    • 相似于在他人须要信息时把你的号码给他们。
退出移动版