9

在纯 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 类相结合。在我的具体示例中,我想在类中添加一个工厂方法fromLocationcom.google.android.maps.GeoPoint我想每个 Android 开发人员都会知道为什么这会有用 ;-))。

但是,如果我尝试做类似的事情

implicit def fromGeoPoint(t: GeoPoint.type) = RichTest

我收到一条错误消息

类型不匹配; 找到:com.google.android.maps.GeoPoint.type(带有底层类型对象 com.google.android.maps.GeoPoint) 需要:AnyRef

所以我想知道是否有任何方法可以实现上述方法 - 或者提供隐式转换 from LocationtoGeoPoint是 Scala 中的首选方式,以便在Location需要时可以使用 a 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, ...)
    ....
}

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

4

6 回答 6

2

好问题!不幸的是,我认为这是不可能的。由于在 Java 中没有办法将类的静态部分用作值,因此没有理由让 Java 类的静态成员的类型扩展 AnyRef。不幸的是,要使该 Java 对象扩展 AnyRef,您将使用隐式转换,这需要 Java 对象扩展 AnyRef ...

我很想被证明是错误的!

更新:你不能在 Java 中做到这一点,我认为最好的做法是创建你自己的静态类来添加工厂方法。例如,考虑Guava中的List

更新:这里是 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();
}
于 2012-03-04T17:27:36.763 回答
2

这是不可能的,因为在 scala 中,onobject ...将成为一个可以通过 java 类中的静态方法访问的单例实例。例如

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 静态没有底层的单例实例,因此不能传递给要转换为另一种类型的函数。

希望这有助于理解为什么这是不可能的;-)。

于 2012-03-06T11:24:35.380 回答
2

从 Scala 版本 2.9.1 开始,无法使用工厂方法扩展 Java 类。

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

  1. 编写Scala 编译器插件以适应所需的更改,然后扩展 Java 类。
  2. 创建一个独立的工厂方法。
  3. 避免完全使用工厂方法并添加隐式转换,以便始终可以使用 Location 对象代替 GeoPoint。

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, ...)
}
于 2012-03-08T15:16:29.047 回答
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.

于 2012-03-06T21:10:18.903 回答
0

你想做的事是不可能的。因为 Java 类不是 Scala 对象,所以无法用方法来扩大它。

我认为使工作尽可能简单的唯一一点是在 Scala 中创建一个对象并为 Java 类进行合格的导入:

import test.{ Test => JTest }
object Test {
  def anyMethodHere ...
}
于 2012-03-03T15:47:21.853 回答
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(...)

}    
于 2012-03-07T18:48:12.810 回答