头图

As we all know, subtype-based polymorphism is supported in the Java language. For example, an encyclopedia gave an example based on Animal and its two subclasses (the code was slightly adjusted by me)

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());
  }
}

Based on the requirements of polymorphic subtypes in the run program according to the type of the parameter, select a different particular methods - for example, in the above example, when the method letsHear call parameters a method talk time is variable in accordance with a at runtime The type (the first time is Cat , the second time is Dog ) to select the corresponding talk method, instead of the type Animal compile time.

But in different languages, the number of parameters selected is different when searching for methods at runtime. For Java, it only takes the first parameter of the method (the receiver). This strategy is called single dispatch.

Java single dispatch

To demonstrate why Java is single dispatch, the method in the sample code must receive two parameters (in addition to the receiver of the method, one more parameter)

// 演示 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);
        }
    }
}

Obviously, the instance method resize Resizer receives two parameters-the first is an Resizer class, and the second may be an instance object of one of the Shape and its three subclasses. If Java's polymorphic strategy is multiple dispatch, then the resize methods of the three different versions should be called, but it is not actually

javap provided in the JDK, you can see which version of the class Resizer is used when calling the resize main method. Run the command javap -c -l -s -v Trial1 , you can see that the JVM bytecode corresponding to the resize invokevirtual

JVM specification document to find the explanation

Obviously, because in the bytecode of the JVM, the parameter type of the method called by invokevirtual LShape represents a Shape , so when looking up in the method receiver, that is, the class Resizer , it will only Hit resize(Shape s) this version of the method. The runtime type of the variable s is not used at all when looking for methods, so Java's polymorphism is single dispatch.

It is not difficult to print different content according to the runtime type of the parameter. The simple and rude way can be 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("缩放任意图形");
    }
  }
}

Or use Visitor mode.

What is multiple dispatch?

I knew the term multiple dispatch for the first time. I actually saw it when I was looking for CLOS related information. In Common Lisp, the syntax for defining classes and methods is different from the common language style. For example, the following code defines four classes like 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)))

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

Because defmethod supports declaring the corresponding class for each parameter is so intuitive that I did not realize that it has a special name called multiple dispatch, and it is not supported in most languages.

postscript

Smart you should have found, on top of Common Lisp code, in fact, the abstract class in Java AbstractResizer corresponding class abstract-resizer is completely unnecessary, defgeneric itself is a used to define abstract interfaces means.

In addition, in the third version of the resize method, you can see that the identifier shape is used as the name of the parameter and the name of the class to which the parameter belongs-yes, in Common Lisp, a symbol can not only represent one at the same time Variables and a function can also serve as a type at the same time. It is more than just a Lisp-2 language.

Read the original text


用户bPGfS
169 声望3.7k 粉丝