如何将工厂方法添加到Scala中的现有Java类

时间:2012-03-03 14:43:05

标签: java scala implicits

在纯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

所以我想知道上述方法是如何实现的 - 或者提供从LocationGeoPoint的隐式转换是否是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, ...)
    ....
}

但是,请记住,这只是一般问题的一个具体示例; - )

6 个答案:

答案 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类。

但是,有三种替代解决方案:

  1. 编写Scala compiler plugin以适应所需的更改,然后扩展Java类。
  2. 创建一个独立的工厂方法。
  3. 完全避免使用工厂方法并添加隐式转换,以便始终可以使用Location对象代替GeoPoint。
  4. 如果我使用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(...)

}