1

Clojure协议的文档指出,每个协议都会生成一个对应的 Java 接口。然而,你可以用协议做的事情(将它们扩展到任意类型等)看起来不像任何在 Java 接口方面具有直接实现的东西。协议和协议方法如何在内部工作?为什么以及如何需要每个协议的 Java 接口?

4

1 回答 1

6

对 Clojure 平台源代码的简要检查表明:

  1. 对于每个协议方法,都有一个从 Java 映射Class到实现该协议的 Clojure 函数的哈希表Class。(它实际上是一个名为 的自定义类的实例MethodImplCache。)
  2. 当调用协议方法的调度函数时,它会获取Class第一个参数并在哈希表中进行查找。
  3. 如果没有找到任何东西,它会遍历第一个参数的继承链,直到找到查找成功的超类。然后将dispatch插入到Classhashtable中,这样下次就可以直接找到了。
  4. 此外,每当查找成功时,最近找到Class的及其对应的方法实现都会缓存在实例变量中,如果Class下次调度相同,则用于绕过哈希查找。
  5. 当协议扩展到任意 Java 类时,生成的接口不会以任何方式发挥作用。对协议方法的调用MethodImplCache按照上述方法进行解析。
  6. 当您用于获取扩展协议的匿名类的实例,或者如果您使用或创建扩展协议的新类,将使用生成的接口。在任何一种情况下,您都会得到一个实现生成接口的对象。reifydeftypedefrecordClass
  7. 当 Clojure 编译器为调用协议方法发出 JVM 字节码时,它会插入一些字节码来检查第一个参数是否是生成接口的实例。如果是,它使用普通的 Java 方法调用接口的适当方法。在这种情况下,我上面描述的协议调度函数被完全绕过并且永远不会被调用。

这一切似乎都意味着,当扩展到任意类时,协议方法调用应该比普通的 Clojure 函数调用慢。如果您扩展协议内联,而不是单独调用or来这样做,与reify//一起使用时的性能应该会更好。deftypedefrecordextend-protocolextend-type

于 2013-09-02T16:19:29.853 回答