共计 3388 个字符,预计需要花费 9 分钟才能阅读完成。
家喻户晓,在 Java 语言中反对基于子类型的多态,例如某百科全书中就给了一个基于 Animal
及其两个子类的例子(代码通过我微微调整)
abstract class Animal {abstract String talk();
}
class Cat extends Animal {String talk() {return "Meow!";}
}
class Dog extends Animal {String talk() {return "Woof!";}
}
public class Example {static void letsHear(final Animal a) {System.out.println(a.talk());
}
public static void main(String[] args) {letsHear(new Cat());
letsHear(new Dog());
}
}
基于子类型的多态要求在程序的运行期依据参数的类型,抉择不同的具体方法——例如在上述例子中,当办法 letsHear
中调用了参数 a
的办法 talk
时,是按照变量 a
在运行期的类型(第一次为 Cat
,第二次为Dog
)来抉择对应的talk
办法的实例的,而不是按照编译期的类型Animal
。
但在不同的语言中,在运行期查找办法时,所抉择的参数的个数是不同的。对于 Java 而言,它只取办法的第一个参数(即接收者),这个策略被称为 single dispatch。
Java 的 single dispatch
要演示为什么 Java 是 single dispatch 的,必须让示例代码中的办法接管两个参数(除了办法的接收者之外再来一个参数)
// 演示 Java 是 single dispatch 的。abstract class Shape {}
class Circle extends Shape {}
class Rectangle extends Shape {}
class Triangle extends Shape {}
abstract class AbstractResizer
{public abstract void resize(Circle c);
public abstract void resize(Rectangle r);
public abstract void resize(Shape s);
public abstract void resize(Triangle t);
}
class Resizer extends AbstractResizer
{public void resize(Circle c) {System.out.println("缩放圆形"); }
public void resize(Rectangle r) {System.out.println("缩放矩形"); }
public void resize(Shape s) {System.out.println("缩放任意图形"); }
public void resize(Triangle t) {System.out.println("缩放三角形"); }
}
public class Trial1
{public static void main(String[] args)
{AbstractResizer resizer = new Resizer();
Shape[] shapes = {new Circle(), new Rectangle(), new Triangle()};
for (Shape shape : shapes)
{resizer.resize(shape);
}
}
}
显然,类 Resizer
的实例办法 resize
就是接管两个参数的——第一个为 Resizer
类的实例对象,第二个则可能是 Shape
及其三个子类中的一品种的实例对象。如果 Java 的多态策略是 multiple dispatch 的,那么该当别离调用不同的三个版本的 resize
办法,但实际上并不是
通过 JDK 中提供的程序 javap
能够看到在 main
办法中调用 resize
办法时到底用的是类 Resizer
中的哪一个版本,运行命令 javap -c -l -s -v Trial1
,能够看到调用resize
办法对应的 JVM 字节码为invokevirtual
翻阅 JVM 规格文档能够找到对 invokevirtual 指令的解释
显然,因为在 JVM 的字节码中,invokevirtual
所调用的办法的参数类型曾经解析结束——LShape
示意是一个叫做 Shape
的类,因而在办法接收者,即类 Resizer
中查找的时候,也只会命中 resize(Shape s)
这个版本的办法。变量 s
的运行期类型在查找办法的时候,丝毫没有派上用场,因而 Java 的多态是 single dispatch 的。
想要根据参数的运行期类型来打印不同内容也不难,简略粗犷的方法能够抉择instanceOf
abstract class AbstractResizer
{public abstract void resize(Shape s);
}
class Resizer extends AbstractResizer
{public void resize(Shape s) {if (s instanceof Circle) {System.out.println("缩放圆形");
} else if (s instanceof Rectangle) {System.out.println("缩放矩形");
} else if (s instanceof Triangle) {System.out.println("缩放三角形");
} else {System.out.println("缩放任意图形");
}
}
}
或者动用 Visitor 模式。
什么是 multiple dispatch?
我第一次晓得 multiple dispatch 这个词语,其实就是在偶然间查找 CLOS 的相干材料时看到的。在 Common Lisp 中,定义类和办法的语法与常见的语言画风不太一样。例如,下列代码跟 Java 一样定义了四个类
(defclass shape ()
())
(defclass circle (shape)
())
(defclass rectangle (shape)
())
(defclass triangle (shape)
())
(defclass abstract-resizer ()
())
(defclass resizer (abstract-resizer)
())
(defgeneric resize (resizer shape))
(defmethod resize ((resizer resizer) (shape circle))
(format t "缩放圆形~%"))
(defmethod resize ((resizer resizer) (shape rectangle))
(format t "缩放矩形~%"))
(defmethod resize ((resizer resizer) (shape shape))
(format t "缩放任意图形~%"))
(defmethod resize ((resizer resizer) (shape triangle))
(format t "缩放三角形~%"))
(let ((resizer (make-instance 'resizer))
(shapes (list
(make-instance 'circle)
(make-instance 'rectangle)
(make-instance 'triangle))))
(dolist (shape shapes)
(resize resizer shape)))
执行上述代码会调用不同版本的 resize
办法来打印内容
因为 defmethod
反对给每一个参数都申明对应的类这一做法是在太合乎直觉了,以至于我丝毫没有意识到它有一个专门的名字叫做 multiple dispatch,并且在大多数语言中是不反对的。
后记
聪慧的你应该曾经发现了,在下面的 Common Lisp 代码中,其实与 Java 中的抽象类 AbstractResizer
对应的类 abstract-resizer
是齐全没有必要的,defgeneric
自身就是一种用来定义形象 接口 的伎俩。
此外,在第三个版本的 resize
办法中,能够看到标识符 shape
同时作为了参数的名字和该参数所属的类的名字——没错,在 Common Lisp 中,一个符号不仅仅能够同时代表一个变量和一个函数,同时还能够专任一个类型,它不仅仅是一门通常所说的 Lisp-2 的语言。
浏览原文