簡體   English   中英

如何將工廠方法添加到Scala中的現有Java類

[英]How to add a factory method to an existing Java class in Scala

在純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的首選方式,因此只要需要GeoPoint就可以使用Location


根據評論中的要求,使用場景:

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

但是,請記住,這只是一般問題的一個具體示例;-)

好問題! 不幸的是,我不認為這是可能的。 因為在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();
}

這是不可能的,因為在scala on 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靜態不具有基礎單例實例,因此無法傳遞到要轉換為其他類型的函數。

希望這有助於理解為什么不可能;-)。

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

雖然您無法通過隱式轉換向這種單例對象添加方法,但您可以使用隱式參數來進行實際創建。 例如:

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庫中使用,例如在應用map等通用Traversable方法時構建正確的集合類型。

你想做什么是不可能的。 因為Java類不是Scala對象,所以無法使用方法擴展它。

我認為使工作變得盡可能簡單的唯一一點是在Scala中創建一個對象並為Java類進行限定導入:

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

這是不可能的,但您可以在包對象中使用具有相同類名的伴隨對象並根據需要使用它,

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

}    

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM