titledborder - setborder java
Java<-> Scala interoperabilidad: conversión transparente de listas y mapas (3)
Estoy aprendiendo sobre Scala y tengo un proyecto de Java para migrar a Scala. Quiero migrarlo reescribiendo las clases una a una y verificando que la nueva clase no rompió el proyecto.
Este proyecto de Java usa muchos java.util.List
y java.util.Map
. En las nuevas clases de Scala me gustaría usar la List
y el Map
Scala para tener un código Scala atractivo.
El problema es que las nuevas clases (las que están escritas en Scala) no se integran perfectamente con el código Java existente: Java necesita java.util.List
, Scala necesita su propia scala.List
.
Aquí hay un ejemplo simplificado del problema. Hay clases Main , Logic , Dao . Se llaman entre sí en una línea: Principal -> Lógica -> Dao .
public class Main {
public void a() {
List<Integer> res = new Logic().calculate(Arrays.asList(1, 2, 3, 4, 5));
}
}
public class Logic {
public List<Integer> calculate(List<Integer> ints) {
List<Integer> together = new Dao().getSomeInts();
together.addAll(ints);
return together;
}
}
public class Dao {
public List<Integer> getSomeInts() {
return Arrays.asList(1, 2, 3);
}
}
En mi situación, las clases Main y Dao son clases de framework (no es necesario migrarlas). Class Logic es lógica para el negocio y se beneficiará mucho de las funciones increíbles de Scala.
Necesito reescribir la clase Logic en Scala mientras mantengo la integridad con las clases Main y Dao . La mejor reescritura sería (no funciona):
class Logic2 {
def calculate(ints: List[Integer]) : List[Integer] = {
val together: List[Integer] = new Dao().getSomeInts()
together ++ ints
}
}
Comportamiento ideal: las listas dentro de Logic2 son listas de Scala nativas. Todas las entradas / salidas java.util.Lists
se almacenan en cajas / unboxed automágicamente. Pero esto no funciona.
En cambio, esto funciona (gracias a scala-javautils ( GitHub )):
import org.scala_tools.javautils.Implicits._
class Logic3 {
def calculate(ints: java.util.List[Integer]) : java.util.List[Integer] = {
val together: List[Integer] = new Dao().getSomeInts().toScala
(together ++ ints.toScala).toJava
}
}
Pero se ve feo.
¿Cómo logro una conversión mágica transparente de Listas y Mapas entre Java <-> Scala (sin necesidad de hacer a Escala / aJava)?
Si no es posible, ¿cuáles son las mejores prácticas para migrar Java -> código de Scala que usa java.util.List
y amigos?
Aquí hay algunos ejemplos rápidos que usan la biblioteca scalaj-collection de Jorge Ortiz:
import org.scala_tools.javautils.Implicits._
val sSeq = java.util.Collections.singletonList("entry") asScala
// sSeq: Seq[String]
val sList = sSeq toList // pulls the entire sequence into memory
// sList: List[String]
val sMap = java.util.Collections.singletonMap("key", "value") asScala
// sMap: scala.collection.Map[String, String]
val jList = List("entry") asJava
// jList: java.util.List[String]
val jMap = Map("key" -> "value") asJava
// jMap: java.util.Map[String, String]
el proyecto javautils está disponible en el repositorio central de maven
Con Scala 2.8, podría hacerse así:
import scala.collection.JavaConversions._
val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList
Créeme; no desea una conversión transparente de ida y vuelta. Esto es precisamente lo que intentaron hacer las funciones scala.collection.jcl.Conversions
. En la práctica, causa muchos dolores de cabeza.
La raíz del problema con este enfoque es que Scala inyectará automáticamente las conversiones implícitas según sea necesario para que funcione una llamada a método. Esto puede tener algunas consecuencias realmente desafortunadas. Por ejemplo:
import scala.collection.jcl.Conversions._
// adds a key/value pair and returns the new map (not!)
def process(map: Map[String, Int]) = {
map.put("one", 1)
map
}
Este código no estaría totalmente fuera de lugar para alguien que es nuevo en el marco de las colecciones de Scala o incluso para el concepto de colecciones inmutables. Desafortunadamente, está completamente equivocado. El resultado de esta función es el mismo mapa. La llamada a put
activa una conversión implícita a java.util.Map<String, Int>
, que acepta felizmente los nuevos valores y se descarta rápidamente. El map
original no está modificado (ya que es, de hecho, inmutable).
Jorge Ortiz lo explica mejor cuando dice que solo debe definir las conversiones implícitas para uno de dos propósitos:
- Agregar miembros (métodos, campos, etc.). Estas conversiones deberían ser de un tipo nuevo no relacionado con ninguna otra cosa en el alcance.
- "Arreglando" una jerarquía de clase rota. Por lo tanto, si tiene algunos tipos
A
yB
que no están relacionados. Puede definir una conversiónA => B
si y solo si hubiera preferido tenerA <: B
(<:
significa "subtipo").
Como java.util.Map
obviamente no es un tipo nuevo que no esté relacionado con nada en nuestra jerarquía, no podemos caer bajo la primera condición. Por lo tanto, nuestra única esperanza es nuestra conversión Map[A, B] => java.util.Map[A, B]
para calificar para el segundo. Sin embargo, no tiene ningún sentido que el Map
de Scala herede de java.util.Map
. Son interfaces / rasgos completamente ortogonales. Como se demostró anteriormente, intentar ignorar estas pautas casi siempre dará como resultado un comportamiento extraño e inesperado.
La verdad es que los métodos javautils asScala
y asJava
fueron diseñados para resolver este problema exacto. Hay una conversión implícita (algunos de ellos en realidad) en javautils de Map[A, B] => RichMap[A, B]
. RichMap
es un nuevo tipo definido por javautils, por lo que su único propósito es agregar miembros a Map
. En particular, agrega el método asJava
, que devuelve un mapa contenedor que implementa java.util.Map
y delega en su instancia de Map
original. Esto hace que el proceso sea mucho más explícito y mucho menos propenso a errores.
En otras palabras, usar asScala
y asJava
es la mejor práctica. Al haber bajado por estas dos carreteras de forma independiente en una aplicación de producción, puedo decirles de primera mano que el enfoque de javautils es mucho más seguro y fácil de usar. ¡No intente eludir sus protecciones simplemente por ahorrar 8 caracteres!