如何为隐式方法实现中间类型?

时间:2011-03-07 13:37:58

标签: scala implicits

假设我想在我控制之外的现有类型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(...) = { ... } }
    
  3. 变体2)肯定是较少的样板,特别是当发生许多类型参数时。另一方面,我认为应该创建更多的开销,因为(概念上)每个转换创建一个类,而不是1)中的一个全局类。

    是否有一般指导原则?没有区别,因为编译器/ VM摆脱了2)的开销?

2 个答案:

答案 0 :(得分:7)

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

考虑一下

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

真的是

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

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

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

在周围找到的方法是对结构类型使用缓存反射调用。

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

答案 1 :(得分:6)

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

但是,如果您控制A(意味着A不是您不能继承的Java库类),我会考虑使用特征而不是隐式转换来向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。 “named”版本生成一个NamedClass.class,它被反编译为这个java:

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

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

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

如此几乎完全相同,除了匿名是“最终”(他们显然想要更加确定你不会试图通过尝试子类化匿名类...)

但是在调用网站上我得到了这个“命名”版本的

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

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