7

将自己读入 Lisp,目前在此页面 ( http://landoflisp.com ) 上,我在单击链接CLOS GUILD时显示的页面的倒数第二段中找到了以下语句:

关于该示例需要注意的重要一点是,为了确定在给定情况下调用哪个 mix 方法,CLOS 需要考虑传入该方法的两个对象。它根据多个对象的类型分派到方法的特定实现。这是传统面向对象语言(如 Java 或 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 年的历史)如果不是这样,我在我的例子中忘记考虑什么?

4

5 回答 5

9

如果您将示例更改为:

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 必须使用访问者模式才能进行双重分派(根据调用方法的对象类型和传入的参数类型调用不同的实现)。

于 2015-08-31T13:02:29.043 回答
4

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

您可以利用 JVM 支持的反射机制,编写一个通过参数“注册”方法的类,并将调度数据存储到该类的存储中。有助于推断参数类型的反射不是 JVM 中调度的快速方法,但可能需要实现泛型函数

于 2015-08-31T10:18:17.163 回答
2

在 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但如何提供重载public void mix(Color c)class Blue返回“你做了橙色!” 不修改它?

于 2015-08-31T12:24:24.293 回答
2

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

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

看起来像是方法重载。但有一点不同:如果你这样做(假设“Person”是 Customer、User 和 SuperUser 的超类):

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

,然后 C# 编译器会抱怨,因为它不知道应该调用三个方法中的哪一个。如果 C# 有多方法,这将按预期编译和运行,即第一个“打印”将被调用。这是因为,使用多方法,所有参数的动态(运行时)类型(不仅仅是第一个)在决定调用哪个方法时在运行时考虑。

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

我想您应该阅读以下链接以获得更好的图片:

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

于 2015-09-01T04:54:14.890 回答
1

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

于 2015-08-31T10:05:07.537 回答