Как добавить фабричный метод к существующему классу Java в 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.гуглить.андроид.карты.Геопоинт.тип (с базовым типом object com.гуглить.андроид.карты.GeoPoint) требуется: AnyRef

поэтому я задаюсь вопросом, есть ли какой - либо способ, как вышеуказанный подход может быть реализован-или будет обеспечивать неявное преобразование из Location to GeoPoint быть предпочтительным способом в Scala так 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, ...)
    ....
}

Однако, имейте в виду, что это лишь один конкретный пример для общей проблемы ;-)

6 ответов


начиная с версии Scala 2.9.1 невозможно расширить класс Java заводским методом.

однако есть три альтернативных решения:

  1. написать плагин компилятора Scala чтобы учесть необходимые изменения, а затем расширить класс Java.
  2. создайте автономный заводской метод.
  3. избегайте использования Заводского метода и добавьте неявное преобразование, чтобы объект Location всегда можно было использовать вместо Геопоинт.

лично я бы пошел на третий вариант, если бы я использовал 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, ...)
}

большой вопрос! К сожалению, я не думаю, что это возможно. Поскольку в Java нет способа использовать статические части класса в качестве значения, нет причин для типа статических членов класса Java расширять AnyRef. И, к сожалению, чтобы этот объект Java расширил AnyRef, вы бы использовали неявное преобразование, которое требует, чтобы объект Java расширил AnyRef...

Я хотел бы, чтобы было доказано, что это не так!

Update: вы не можете сделать это на Java, и там Я думаю, что лучше всего создать свой собственный статический класс, в который можно добавить заводские методы. Рассмотрим, например,список на гуавы.

Update: вот полный пример различий между Java и Scala (то, что описывал Дарио).

# 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..Модуль$, который является экземпляром типа Foo$. Статика Java не имеет базового одноэлементного экземпляра и поэтому не может быть передана в функцию для преобразования в другую тип.

надеюсь, это поможет немного понять, почему это невозможно ; -).


хотя вы не можете добавить метод неявным преобразованием к такого рода одноэлементным объектам, вы можете использовать неявный параметр для создания фактического создания. Например:

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 )

таким образом, фабрики по-прежнему" статичны", как вы, похоже, ожидаете, но вы можете обогатить или изменить поведение без необходимости изменять


то, что вы хотите сделать, невозможно. Поскольку класс 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(...)

}