在纯Scala环境中,如果我想将工厂方法“添加”到现有对象,我可以执行以下操作:
object Test
object Extensions {
object RichTest {
def someFactory = new Test()
}
implicit def fromTest(t: Test.type) = RichTest
}
...
import Extensions._
val t = Test.someFactory
我需要将这样的功能与现有的Java类结合使用。在我的具体示例中,我想将工厂方法fromLocation
添加到类com.google.android.maps.GeoPoint
(我想每个Android开发人员都会知道为什么这会有用;-))。
但是,如果我尝试做类似
的事情implicit def fromGeoPoint(t: GeoPoint.type) = RichTest
我收到错误说明
类型不匹配;发现:com.google.android.maps.GeoPoint.type(底层类型对象为com.google.android.maps.GeoPoint)必需:AnyRef
所以我想知道上述方法是如何实现的 - 或者提供从Location
到GeoPoint
的隐式转换是否是Scala中的首选方式所以Location
可以在需要GeoPoint
时使用?
根据评论中的要求,使用场景:
// Callback you get from the GPS
override def onLocationChanged(l: Location) {
// You want to put a marker on a map, hence a GeoPoint is required
val gp: GeoPoint = GeoPoint.fromLocation(location)
val item = new OverlayItem(gp, ...)
....
}
但是,请记住,这只是一般问题的一个具体示例; - )
答案 0 :(得分:2)
好问题!不幸的是,我不认为这是可能的。因为在Java中没有办法将类的静态部分用作值,所以Java类的静态成员的类型没有理由扩展AnyRef。不幸的是,要使Java对象扩展AnyRef,您将使用隐式转换,这需要Java对象扩展AnyRef ...
我很想被证明是错的!
更新:你不能用Java做到这一点,我认为最好的做法是创建你自己的静态类来添加工厂方法。例如,请考虑List中的Guava。
更新:这里是Java和Scala之间差异的完整示例(Dario所描述的)。
# vim Test.java
class Test {
public static final int foo = 1;
public final int bar = 2;
}
# javac Test.java
# ls
Test.class Test.java
# javap Test
Compiled from "Test.java"
class Test {
public static final int foo;
public final int bar;
Test();
}
与scala相比:
# vim Test.scala
object Test {
val foo = 1
}
class Test {
val bar = 2
}
# javac Test.scala
# ls
Test$.class Test.class Test.scala
# javap Test
public class Test implements scala.ScalaObject {
public static final int foo();
public int bar();
public Test();
}
# javap Test$
Compiled from "Test.scala"
public final class Test$ implements scala.ScalaObject {
public static final Test$ MODULE$;
public static {};
public int foo();
}
答案 1 :(得分:2)
这是不可能的,因为在object ...
上的scala将成为可通过java类中的静态方法访问的单例实例。 E.g。
object Foo {
def bar = 2
}
会变成:
public final class Foo {
public static int bar() {
return Foo..MODULE$.bar();
}
}
public final class Foo$
implements ScalaObject
{
public static final MODULE$;
static
{
new ();
}
public int bar()
{
return 2;
}
private Foo$()
{
MODULE$ = this;
}
}
传递给隐式转换方法的是这种情况下的Foo..MODULE $,它是Foo $类型的实例。 Java静态不具有基础单例实例,因此不能传递给要转换为其他类型的函数。
希望这有助于理解为什么不可能; - )。
答案 2 :(得分:2)
从Scala版本2.9.1开始,无法使用工厂方法扩展Java类。
但是,有三种替代解决方案:
如果我使用Location
- >个人,我会选择第三个选项。很多时候GeoPoint
或任何其他转换,特别是在转换总是可以无异常且类接口不重叠的情况下。
implicit def geoPointFromLocation(l: Location):GeoPoint = new GeoPoint...
override def onLocationChanged(l: Location) {
val gp: GeoPoint = l // let Scala compiler do the conversion for you
val item = new OverlayItem(gp, ...)
....
// or just pass the location to OverlayItem constructor directly
val item2 = new OverlayItem(l, ...)
}
答案 3 :(得分:1)
虽然您无法通过隐式转换向这种单例对象添加方法,但您可以使用隐式参数来进行实际创建。例如:
trait Factory1[-A,+B] {
def buildFrom( a: A ): B
}
trait Factory2[-A1,-A2,+B] {
def buildFrom( a1: A1, a2: A2 ): B
}
object Factories {
implicit object GeoPointFromLocation extends Factory1[Location,GeoPoint] {
def buildFrom( location: Location ): GeoPoint = //actual code
}
implicit object GeoPointFromXY extends Factory2[Double,Double,GeoPoint] {
def buildFrom( x: Double, y: Double ): GeoPoint = //actual code
}
}
object GeoPoint {
def from[A]( a: A )( implicit fac: Factory1[A,GeoPoint] ) = fac.buildFrom(a)
def from[A,B]( a: A, b: B )( implicit fac: Factory2[A,B,GeoPoint] ) = fac.buildFrom(a,b)
}
import Factories._
val loc = new Location(...)
val gp1 = GeoPoint.from( loc )
val gp2 = GeoPoint.from( 101.0, -12.6 )
因此,您似乎期望它们仍然是“静态的”,但您可以在不必更改GeoPoint
对象的情况下丰富或更改行为。例如,您可以在测试时导入特殊工厂。
此方法在Scala库中使用,例如,在应用Traversable
等通用map
方法时构建正确的集合类型。
答案 4 :(得分:0)
你想做什么是不可能的。因为Java类不是Scala对象,所以无法使用方法扩展它。
我认为让工作变得尽可能简单的唯一方法是在Scala中创建一个对象并为Java类进行限定导入:
import test.{ Test => JTest }
object Test {
def anyMethodHere ...
}
答案 5 :(得分:0)
这是不可能的,但您可以在包对象中使用具有相同类名的伴随对象,并根据需要使用它,
import com.google.android.maps.GeoPoint
package object com.xyz.mappy.maps {
object GeoPoint {
def from(....): GeoPoint = new GeoPoint(...)
}
}
.................................
package com.xyz.mappy.maps
import com.google.android.maps.GeoPoint
class MapRenderer {
val geopoint = GeoPoint.from(...)
}