Как добавить фабричный метод в существующий класс Java в Scala - PullRequest
9 голосов
/ 03 марта 2012

В чистой среде 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

Так что мне интересно, есть ли способ, какимВышеупомянутый подход мог бы быть реализован - или было бы предпочтительным способом в Scala обеспечить неявное преобразование из Location в GeoPoint, поэтому 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 ]

2 голосов
/ 08 марта 2012

Начиная с версии 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, ...)
}
2 голосов
/ 06 марта 2012

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

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

2 голосов
/ 04 марта 2012

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

Хотелось бы, чтобы меня ошиблись!

Обновление: вы не можете сделать это в Java, и я думаю, что лучше всего создать собственный статический класс, в который можно добавить фабричные методы. Например, рассмотрим Список в Гуава .

Обновление: вот полный пример различий между 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();
}

По сравнению со скалой:

# 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 голос
/ 07 марта 2012

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

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.

0 голосов
/ 07 марта 2012

Это невозможно, но вы можете иметь объект-компаньон с тем же именем класса в объекте пакета и использовать его по своему желанию,

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

}    
0 голосов
/ 03 марта 2012

То, что вы хотите сделать, невозможно.Поскольку класс Java не является объектом Scala, его невозможно расширить методами.

Единственное, что я вижу, чтобы максимально упростить работу, - это создать объект в Scala и выполнить квалифицированный импорт длякласс Java:

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