假设我想在我控制之外的现有类型foo
上提供方法A
。据我所知,在Scala中执行此操作的规范方法是实现从A
到实现foo
的某种类型的隐式转换。现在我基本上看到两个选项。
为此目的定义一个单独的,甚至是隐藏的类:
protected class Fooable(a : A) {
def foo(...) = { ... }
}
implicit def a2fooable(a : A) = new Fooable(a)
定义内联匿名类:
implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
变体2)肯定是较少的样板,特别是当发生许多类型参数时。另一方面,我认为应该创建更多的开销,因为(概念上)每个转换创建一个类,而不是1)中的一个全局类。
是否有一般指导原则?没有区别,因为编译器/ VM摆脱了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报告了同样的事情,但我还没有找到更多的见解,为什么会这样。