Java 是否支持像 Lisp 那样根据多个对象的类型分派到特定的实现?

Does Java support dispatching to specific implementations based on types of multiple objects like Lisp does?

阅读 Lisp,目前在这个页面上 (http://landoflisp.com),我在单击 link 时显示的页面倒数第二段中找到以下语句CLOS 公会:

The important thing to note about the example is that in order to figure out which mix method to call in a given situation, the CLOS needs to take into account both of the objects passed into the method. It is dispatching to a specific implementation of the method based on the types of multiple objects. This is a feature that is not available in traditional object-oriented languages, such as Java or C++.

这是示例 Lisp 代码:

(defclass color () ())
(defclass red (color) ())
(defclass blue (color) ())
(defclass yellow (color) ())

(defmethod mix ((c1 color) (c2 color))
    "I don't know what color that makes")

(defmethod mix ((c1 blue) (c2 yellow))
    "you made green!")

(defmethod mix ((c1 yellow) (c2 red))
    "you made orange!")

不,我认为最后一句话是错误的。我实际上可以使用以下 Java 代码做到这一点:

public class Main {
    public static void main(String[] args) {
        mix(new Red(), new Blue());
        mix(new Yellow(), new Red());
    }

    public static void mix(Color c1, Color c2) {
        System.out.println("I don't know what color that makes");
    }
    public static void mix(Blue c1, Yellow c2) {
        System.out.println("you made green!");
    }
    public static void mix(Yellow c1, Red c2) {
        System.out.println("you made orange!");
    }
}

class Color {}
class Red extends Color {}
class Blue extends Color {}
class Yellow extends Color {}

当我 运行 时,它给了我相同的输出:

I don't know what color that makes
you made orange!

所以我的问题是:该页面上的这句话实际上是错误的吗?在 Java / C++ 中可能吗? 如果是这样,也许在 Java 的旧版本中是不可能的? (虽然我非常怀疑,因为这本书只有 5 年历史) 如果不是这样,我在示例中忘记考虑什么?

这里的区别在于,在 Java 中,对于一个参数的分派,您将使用常规的 class 方法(在 class 文件中),而对于许多参数的分派,您将使用一个助手 class (在它自己的文件中),就像你的例子一样。在 CLOS 中它是统一的,您可以使用一种形式将所有内容拆分为适合您的文件。

C++ 和 Java 都可以通过多个参数重载方法,但它是静态完成的,并且仅用于方法(对于 Java,是第二 class 公民classes 和对象)。它本身并不能解决表达式问题:您不能引入具有一组新的支持的参数组合的新 class 并让它与现有参数一起使用。

你可以利用JVM支持的反射机制,通过参数写一个class那个"registers"方法,然后把dispatch数据存储到那个class的一个storage中。有助于推断参数类型的反射不是在 JVM 中分派的快速方法,但可能需要实现 泛型函数.

您在 Java 中执行 OO 的实际方式更像这样:

class Color {
  public static void main(String[] args){
    Color red = new Red();
    Color blue = new Blue();
    Color yellow = new Yellow();
    red.printMix(blue);     // prints "I don't know what color that makes"
    red.printMix(yellow);   // prints "you made orange!"
    blue.printMix(yellow);  // prints "you made green!"
    // but these should work as well
    yellow.printMix(red);   // prints "you made orange!"
    yellow.printMix(blue);  // prints "you made green!"
  }
  public void printMix(Color c) {
    System.out.println(mix(c));
  }
  public String mix(Color c){
    return "I don't know what color that makes";
  }
}
class Red extends Color {
  @Override
  public String mix(Color c){
    if( c instanceof Yellow )
      return "you made orange!";
    return super.mix(c);
  }
}
class Blue extends Color {
  @Override
  public String mix(Color c){
    if( c instanceof Yellow )
      return "you made green!";
    return super.mix(c);
  }
}
class Yellow extends Color {
  @Override
  public String mix(Color c){
    if( c instanceof Red )
      return "you made orange!";
    if( c instanceof Blue )
      return "you made green!";
    return super.mix(c);
  }
}

现在如果你想象 Yellow 是你做的,但其余的是别人做的。在 CLOS 中,您可以提供 (defmethod mix ((c2 red) (c1 yellow)) 作为 yellow 实现的一部分,但是如何将 class Blue 中的重载 public void mix(Color c) 提供给 return "you made orange!" 不修改?

如果您将示例更改为:

public static void main(String[] args) {
    Color red = new Red();
    Color blue = new Blue();
    Color yellow = new Yellow();
    mix(red, blue);
    mix(yellow, red);
}

那么你会得到

I don't know what color that makes
I don't know what color that makes

Java 正在使用编译时类型来确定要调用的方法重载,其中 Lisp 正在调度运行时类型。 Java 必须使用访问者模式才能进行双重分派(根据调用方法的对象类型和传入参数的类型调用不同的实现)。

您将从以下示例中获得更好的图片:

    class PrettyPrint{
       public void Print(Customer aCustomer){}
       public void Print(User aUser){}
       public void Print(SuperUser aUser){}
  }

看起来方法overloading.But有区别: 如果这样做(假设 "Person" 是 Customer、User 和 SuperUser 的超类):

 PrettyPrint pp = ...;
 Person p = new Customer(...);
 pp.Print(p);

,那么C#编译器就会报错,因为它不知道应该调用这三个方法中的哪一个。 如果 C# 有多种方法,这将按预期进行编译和 运行,即第一个 "Print" 将是 called.This,因为对于多种方法,dynamic (运行-time) 类型的所有参数(不仅仅是第一个)在决定调用哪个方法时被视为 at 运行time

多方法调用使用基类型作为参数,但仍为派生类型找到合适的实现。

我想你应该阅读下面的内容 link 以获得更好的图片:

http://c2.com/cgi/wiki?MultiMethods