2012-03-03 16 views
9

En un entorno Scala pura, podría hacer lo siguiente si quería "añadir" un método de fábrica a un objeto existente:¿Cómo añadir un método de fábrica a una clase Java existente en Scala

object Test 

object Extensions { 

    object RichTest { 
     def someFactory = new Test() 
    } 
    implicit def fromTest(t: Test.type) = RichTest 

} 

... 

import Extensions._ 
val t = Test.someFactory 

I necesitaría tal funcionalidad en combinación con una clase Java existente. En mi ejemplo concreto, me gustaría agregar un método de fábrica fromLocation a la clase com.google.android.maps.GeoPoint (y supongo que cada desarrollador de Android sabrá por qué sería útil ;-)).

Sin embargo, si trato de hacer algo como

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

me sale un error que indica

coincidencia de tipos; encontrado: com.google.android.maps.GeoPoint.type (con tipo de objeto com.google.android.maps.GeoPoint subyacente) requerido: AnyRef

así que me pregunto si hay alguna manera de cómo el enfoque anterior podría ser implementado - o proporcionar una conversión implícita de Location a GeoPoint ser la forma preferida en Scala para que se pueda usar Location siempre que se requiera GeoPoint?


a lo solicitado en los comentarios, un escenario de uso:

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

Sin embargo, tenga en cuenta que esto es sólo un ejemplo específico para el problema general ;-)

+0

¿Podría dar un ejemplo de código que usaría este método de fábrica? Por ejemplo, con GeoPoints y ubicaciones. Realmente no entiendo esta pregunta. – Fixpoint

+0

@Fixpoint hecho ;-) – Leo

Respuesta

2

A partir de la versión 2.9.1 Scala no es posible extender la clase Java con un método de fábrica.

Sin embargo, hay tres soluciones alternativas:

  1. Escribir Scala compiler plugin para acomodar los cambios requeridos y luego extender la clase Java.
  2. Cree un método de fábrica independiente.
  3. Evite utilizar un método de fábrica por completo y agregue una conversión implícita para que el objeto Ubicación siempre se pueda usar en lugar de un GeoPoint.

Personalmente me gustaría ir por la tercera opción si utilicé Location ->GeoPoint o cualquier otra conversión de una gran parte del tiempo, sobre todo en el escenario cuando la conversión se puede hacer siempre y sin excepciones e interfaces de clase no lo hacen superposición.

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

lo que quieres hacer es imposible Como una clase Java no es un objeto Scala, no hay forma de ampliarla con métodos.

El único punto que veo para hacer el trabajo más fácil posible es la creación de un objeto en Scala y hacer una importación clasificado para la clase Java:

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

... que definitivamente NO es lo que quiero. Un método de fábrica en Java es un método estático; el equivalente de Scala son métodos de objeto, ¿verdad? Puedo llamar a métodos estáticos de Java en Scala, por lo que tiene que haber algún tipo de conversión de estático a objeto. Al final, quiero agregar un método estático a una clase Java a través de Scala. Lo que significa que la nueva palabra clave es lo que NO quiero. – Leo

+0

@Mef: Lamento haberme olvidado de ese punto. Actualicé mi respuesta aunque realmente no puedo ayudarte. – sschaef

2

Muy buena pregunta! Lamentablemente, no creo que sea posible. Como en Java no hay forma de usar las partes estáticas de una clase como valor, no hay ninguna razón para que el tipo de miembros estáticos de una clase Java extienda AnyRef. Y desafortunadamente, para hacer que ese objeto Java se extienda AnyRef, se usaría la conversión implícita, que requiere que el objeto Java extienda AnyRef ...

¡Aunque me encantaría probar que estoy equivocado!

Actualización: no puede hacer esto en Java, y creo que la mejor práctica es crear su propia clase estática para agregar los métodos de fábrica. Por ejemplo, considere List en Guava.

Actualización: aquí hay un ejemplo completo de las diferencias entre Java y Scala (lo que Dario estaba describiendo).

# 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(); 
} 

En comparación con, con 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(); 
} 
2

Esto no es posible, porque en Scala en object ... se convertirá en una instancia singleton accesible a través de métodos estáticos de la clase java. P.ej.

object Foo { 
    def bar = 2 
} 

se convertirá en algo así como:

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; 
    } 
} 

Lo que se introduce en el método de conversión implícita es en este caso el Foo..MODULE $, que es una instancia de tipo Foo $. La estática de Java no tiene una instancia singleton subyacente y, por lo tanto, no se puede pasar a una función para convertirla a otro tipo.

Espero que esto ayude un poco a entender por qué no es posible ;-).

1

Aunque no puede agregar un método mediante la conversión implícita a este tipo de objetos singleton, puede usar un parámetro implícito para realizar la creación real. Por ejemplo:

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) 

Así fábricas siguen siendo "estática" como parece que esperar, pero se pueden enriquecer o cambiar el comportamiento sin tener que cambiar GeoPoint objeto. Puede, por ejemplo, importar fábricas especiales durante la prueba.

Este enfoque se utiliza en la biblioteca de Scala, por ejemplo para crear el tipo de colección correcto al aplicar los métodos genéricos Traversable como map.

+0

... que lleva a un 'GeoPoint importado está permanentemente oculto por la definición del objeto GeoPoint en el paquete ...' así que supongo que realmente no funciona en la práctica :-( – Leo

0

esto no es posible, pero puede tener un objeto acompañante con el mismo nombre de la clase en un objeto de paquete y utilizarlo como lo hubiera querido,

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

}  
Cuestiones relacionadas