It is well known that subtype-based polymorphism is supported in the Java language, for example an encyclopedia gives an example based on Animal and its two subclasses (the code has been tweaked slightly by me)

abstract class Animal {
  abstract String talk(a);
}

class Cat extends Animal {
  String talk(a) {
    return "Meow!"; }}class Dog extends Animal {
  String talk(a) {
    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(newDog()); }}Copy the code

Based on the requirement of the subtype polymorphism in program running period, depending on the type of parameters of the choice of different specific methods, such as in the example above, when method letsHear invoked the parameters in a way to talk, is in accordance with the variable a type at run time (for the first time for the Cat, the second for the Dog) to select an instance of the corresponding talk method, Not according to compile time type Animal.

However, in different languages, the number of parameters selected during the run-time method lookup varies. In the case of Java, it takes only the first argument to the method (the receiver), a strategy known as single Dispatch.

Java 的 single dispatch

To demonstrate why Java is single Dispatch, the method in the sample code must take two arguments (one in addition to the receiver of the method)

// Demonstrate that Java is 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("Zoom circle"); }
	public void resize(Rectangle r) { System.out.println("Scaled rectangle"); }
	public void resize(Shape s) { System.out.println("Scale any shape"); }
    public void resize(Triangle t) { System.out.println("Scaled triangle"); }}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); }}}Copy the code

Obviously, an instance method of class Resizer, resize, takes two arguments — the first is an instance object of class Resizer, and the second may be an instance object of Shape or one of its three subclasses. If Java’s polymorphic strategy were multiple Dispatch, three versions of the resize method would be called separately, but it is not

To see which version of the Resizer class is used when calling the resize method in the main method, run javap -c -L -s -v Trial1. You can see that the JVM bytecode for calling the resize method is Invokevirtual

Consult the JVM specification documentation to find an explanation for the Invokevirtual directive

Obviously, because in the JVM bytecode, the parameter type of the method called by Invokevirtual has been resolved — LShape means a class called Shape, so when looking up the method receiver, the class Resizer, Only the resize(Shape s) version will be hit. The runtime type of the variable S is not at all useful when looking up methods, so Java polymorphism is single dispatch.

It is also not difficult to print different content depending on the runtime type of the parameter. A simple and crude way is to choose 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("Zoom circle");
    } else if (s instanceof Rectangle) {
      System.out.println("Scaled rectangle");
    } else if (s instanceof Triangle) {
      System.out.println("Scaled triangle");
    } else {
      System.out.println("Scale any shape"); }}}Copy the code

Or use the Visitor pattern.

What is multiple Dispatch?

I first came across the term Multiple dispatch when I happened to look up CLOS information. In Common Lisp, the syntax for defining classes and methods is a bit different from the Common language style. For example, the following code defines four classes as Java does

(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 "Zoom circle ~%"(a))defmethod resize ((resizer resizer) (shape rectangle))
  (format t "Scale rectangle ~%"(a))defmethod resize ((resizer resizer) (shape shape))
  (format t "Scale any graph ~%"(a))defmethod resize ((resizer resizer) (shape triangle))
  (format t "Scale triangle ~%"(a))let ((resizer (make-instance 'resizer))
      (shapes (list
               (make-instance 'circle)
               (make-instance 'rectangle)
               (make-instance 'triangle))))
  (dolist (shape shapes)
    (resize resizer shape)))
Copy the code

Executing the code above calls different versions of the resize method to print the content

Defmethod’s support for declaring a class for each parameter is so intuitive that I had no idea it had a multiple Dispatch name and was not supported in most languages.

Afterword.

As you are smart enough to have noticed in the Common Lisp code above, the class Abstract -resizer, which corresponds to the Java abstract class AbstractResizer, is completely unnecessary. Defgeneric itself is a means of defining abstract interfaces.

In addition, in the third version of the resize method, you can see that the shape identifier is used as both the name of the argument and the name of the class to which the argument belongs — yes, in Common Lisp, a symbol can represent not only a variable and a function, but also a type. It’s not just a language that’s commonly known as Lisp-2.

Read the original