如何实现隐式方法的中间类型?
假设我想在我无法控制的现有类型 A
上提供方法 foo
。据我所知,在 Scala 中执行此操作的规范方法是实现从 A
到某种实现 foo
的类型的隐式转换。现在我基本上看到两个选择。
为此目的定义一个单独的,甚至可能是隐藏的类:
受保护的类 Fooable(a : A) { def foo(...) = { ... } } 隐式 def a2fooable(a : A) = new Fooable(a)
定义一个内联匿名类:
隐式 def a2fooable(a : A) = new { def foo(...) = { ... } }
变体 2) 肯定是更少的样板,尤其是当发生大量类型参数时。另一方面,我认为它应该会产生更多的开销,因为(概念上)每个转换创建一个类,而不是 1) 中全局创建一个类。
有一般准则吗?没有区别吗,因为编译器/VM 消除了 2) 的开销?
Assume I want to offer method foo
on existing type A
outside of my control. As far as I know, the canonical way to do this in Scala is implementing an implicit conversion from A
to some type that implements foo
. Now I basically see two options.
Define a separate, maybe even hidden class for the purpose:
protected class Fooable(a : A) { def foo(...) = { ... } } implicit def a2fooable(a : A) = new Fooable(a)
Define an anonymous class inline:
implicit def a2fooable(a : A) = new { def foo(...) = { ... } }
Variant 2) is certainly less boilerplate, especially when lots of type parameters happen. On the other hand, I think it should create more overhead since (conceptually) one class per conversion is created, as opposed to one class globally in 1).
Is there a general guideline? Is there no difference, because compiler/VM get rid of the overhead of 2)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
使用单独的类可以提高性能,因为替代方案可以使用反射。
考虑一下
,现在
,
AnyRef
没有方法foo
。在 Scala 中,这种类型实际上是AnyRef { def foo(...): ... }
,如果删除AnyRef
,您应该将其识别为 结构类型。在编译时,这个时间可以来回传递,并且在任何地方都知道方法
foo
是可调用的。然而,JVM 中没有结构类型,并且添加接口需要代理对象,这会导致一些问题,例如破坏引用相等(即,对象与其自身的结构类型版本不相等)。解决这个问题的方法是使用结构类型的缓存反射调用。
因此,如果您想对任何性能敏感的应用程序使用 Pimp My Library 模式,请声明一个类。
Using a separate class is better for performance, as the alternative uses reflection.
Consider that
is really
Now,
AnyRef
doesn't have a methodfoo
. In Scala, this type is actuallyAnyRef { def foo(...): ... }
, which, if you removeAnyRef
, you should recognize as a structural type.At compile time, this time can be passed back and forth, and everywhere it will be known that the method
foo
is callable. However, there's no structural type in the JVM, and to add an interface would require a proxy object, which would cause some problems such as breaking referential equality (ie, an object would not be equal with a structural type version of itself).The way found around that was to use cached reflection calls for structural types.
So, if you want to use the Pimp My Library pattern for any performance-sensitive application, declare a class.
我相信 1 和 2 会被编译为相同的字节码(除了在情况 2 中生成的类名之外)。
如果 Fooable 的存在只是为了让您能够将 A 隐式转换为 Fooable(并且您永远不会直接创建和使用 Fooable),那么我会选择选项 2。
但是,如果您控制 A(意味着 A 不是一个不能子类化的 java 库类)我会考虑使用特征而不是隐式转换来向 A 添加行为。
更新:
我必须重新考虑我的答案。我会使用你的代码的变体 1,因为变体 2 结果使用反射(Linux 上的 scala 2.8.1)。
我编译了相同代码的这两个版本,使用 jd-gui 将它们反编译为 java,结果如下:
带有命名类的
源代码 带有匿名类的源代码
用 java-gui 编译并反编译为 java。 “命名”版本生成一个 NamedClass.class,该类被反编译为该 java:
匿名生成一个 test$$anon$1 类,该类被反编译为以下 java
,因此几乎相同,除了匿名是“最终”(他们显然希望为了额外确保你不会特意尝试对匿名类进行子类化...)
但是在调用站点我得到了这个用于“命名”版本的java
,而这个是用于匿名的
我用谷歌搜索了一下并发现其他人也报告了同样的事情但我还没有找到更多关于为什么会出现这种情况的见解。
I believe 1 and 2 get compiled to the same bytecode (except for the class name that gets generated in case 2).
If Fooable exists only for you to be able to convert implicitly A to Fooable (and you're never going to directly create and use a Fooable), then I would go with option 2.
However, if you control A (meaning A is not a java library class that you can't subclass) I would consider using a trait instead of implicit conversions to add behaviour to A.
UPDATE:
I have to reconsider my answer. I would use variant 1 of your code, because variant 2 turns out to be using reflection (scala 2.8.1 on Linux).
I compiled these two versions of the same code, decompiled them to java with jd-gui and here are the results:
source code with named class
source code with anonymous class
compiled and decompiled to java with java-gui. The "named" version generates a NamedClass.class that gets decompiled to this java:
the anonymous generates a test$$anon$1 class that gets decompiled to the following java
so almost identical, except for the anonymous being "final" (they apparently want to make extra sure you won't get out of your way to try and subclass an anonymous class...)
however at the call site I get this java for the "named" version
and this for the anonymous
I googled a little and found that others have reported the same thing but I haven't found any more insight as to why this is the case.