【问题标题】:How to add a factory method to an existing Java class in Scala如何将工厂方法添加到 Scala 中的现有 Java 类
【发布时间】:2012-03-21 18:19:30
【问题描述】:

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

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

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

我收到一条错误提示

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

所以我想知道是否有任何方式可以实现上述方法 - 或者提供从 LocationGeoPoint 的隐式转换是 Scala 中的首选方式,因此 Location 可以在任何时候使用 @需要987654328@吗?


根据cmets的要求,一个使用场景:

// 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, ...)
    ....
}

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

【问题讨论】:

  • 您能举一个使用这种工厂方法的代码示例吗?例如,使用 GeoPoints 和 Locations。我不太明白这个问题。

标签: java scala implicits


【解决方案1】:

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

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

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

如果我经常使用Location -> GeoPoint 或任何其他转换,我个人会选择第三个选项,特别是在转换总是可以无异常地完成并且类接口没有的情况下' t 重叠。

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, ...)
}

【讨论】:

    【解决方案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();
    }
    

    【讨论】:

      【解决方案3】:

      这是不可能的,因为在 scala 中 object ... 将成为一个可以通过 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 静态没有底层的单例实例,因此不能传递给函数以转换为另一种类型。

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

      【讨论】:

        【解决方案4】:

        虽然不能通过隐式转换为这种单例对象添加方法,但可以使用隐式参数进行实际创建。例如:

        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)时构建正确的集合类型。

        【讨论】:

        • ... 这导致imported GeoPoint is permanently hidden by definition of object GeoPoint in package ... 所以我想它在实践中并没有真正奏效:-(
        【解决方案5】:

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

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

        import test.{ Test => JTest }
        object Test {
          def anyMethodHere ...
        }
        

        【讨论】:

        • ... 这绝对不是我想要的。 Java 中的工厂方法是静态方法; Scala 等价物是对象方法,对吗?我可以在 Scala 中调用静态 Java 方法,因此必须进行某种静态到对象的转换。最后,我想通过Scala为Java类添加一个静态方法。这意味着 new-keyword 是我不想要的东西。
        • @Mef:很抱歉我错过了这一点。我更新了我的答案,即使我不能真正帮助你。
        【解决方案6】:

        这是不可能的,但是你可以在一个包对象中拥有一个具有相同类名的伴随对象,并根据需要使用它,

        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(...)
        
        }    
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-24
          • 1970-01-01
          • 1970-01-01
          • 2012-08-21
          • 1970-01-01
          • 1970-01-01
          • 2011-07-10
          相关资源
          最近更新 更多