3

假设我想在我控制之外的foo现有类型上提供方法。A据我所知,在 Scala 中执行此操作的规范方法是实现从A某种类型的隐式转换,该类型实现foo. 现在我基本上看到了两种选择。

  1. 为此目的定义一个单独的,甚至可能是隐藏的类:

    protected class Fooable(a : A) {
      def foo(...) = { ... }
    }
    implicit def a2fooable(a : A) = new Fooable(a)
    
  2. 定义一个匿名类内联:

    implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
    

变体 2) 的样板当然更少,尤其是在出现大量类型参数时。另一方面,我认为它应该会产生更多开销,因为(从概念上)每次转换创建一个类,而不是在 1) 中全局创建一个类。

有通用的指导方针吗?有没有区别,因为编译器/VM摆脱了2)的开销?

4

2 回答 2

7

使用单独的类对性能更好,因为替代方案使用反射。

考虑到

new { def foo(...) = { ... } }

是真的

new AnyRef { def foo(...) = { ... } }

现在,AnyRef没有方法foo。在 Scala 中,这种类型实际上是AnyRef { def foo(...): ... },如果您删除AnyRef,您应该将其识别为结构类型

在编译时,这个时间可以来回传递,并且在任何地方都会知道该方法foo是可调用的。但是,JVM 中没有结构类型,添加接口需要代理对象,这会导致一些问题,例如破坏引用相等(即,对象不会与自身的结构类型版本相等)。

解决这个问题的方法是对结构类型使用缓存的反射调用。

因此,如果您想将 Pimp My Library 模式用于任何对性能敏感的应用程序,请声明一个类。

于 2011-03-07T19:36:40.533 回答
6

我相信 1 和 2 被编译成相同的字节码(除了在案例 2 中生成的类名)。如果 Fooable 的存在只是为了让您能够将 A 隐式转换为 Fooable(并且您永远不会直接创建和使用 Fooable),那么我会选择选项 2。

但是,如果您控制 A(意味着 A 不是您不能子类化的 java 库类),我会考虑使用 trait 而不是隐式转换来向 A 添加行为。

更新:我必须重新考虑我的答案。我会使用您的代码的变体 1,因为变体 2 原来是使用反射(Linux 上的 scala 2.8.1)。

我编译了这两个版本的相同代码,使用 jd-gui 将它们反编译为 java,结果如下:

具有命名类的源代码

class NamedClass { def Foo : String = "foo" }

object test {
  implicit def StrToFooable(a: String) = new NamedClass 
  def main(args: Array[String]) { println("bar".Foo) }
}

匿名类的源代码

object test {
  implicit def StrToFooable(a: String) = new { def Foo : String = "foo" } 

  def main(args: Array[String]) { println("bar".Foo) }
}    

使用 java-gui 编译和反编译为 java。“命名”版本生成一个 NamedClass.class,它被反编译成这个 java:

public class NamedClass
  implements ScalaObject
{
  public String Foo()
  {
    return "foo";
  }
}

匿名生成一个 test$$anon$1 类,该类被反编译为以下 java

public final class test$$anon$1
{
  public String Foo()
  {
    return "foo";
  }
}

几乎相同,除了匿名是“最终的”(他们显然想额外确保你不会不顾一切地尝试和子类化一个匿名类......)

但是在呼叫站点上,我得到了“命名”版本的这个java

public void main(String[] args) 
{ 
  Predef..MODULE$.println(StrToFooable("bar").Foo());
}

这是匿名的

  public void main(String[] args) { 
    Object qual1 = StrToFooable("bar"); Object exceptionResult1 = null;
    try { 
      exceptionResult1 = reflMethod$Method1(qual1.getClass()).invoke(qual1, new Object[0]); 
      Predef..MODULE$.println((String)exceptionResult1); 
      return; 
    } catch (InvocationTargetException localInvocationTargetException) { 
      throw localInvocationTargetException.getCause();
    }
  }

我用谷歌搜索了一下,发现其他人也报告了同样的事情,但我没有找到更多关于为什么会这样的见解。

于 2011-03-07T14:15:17.120 回答