Domanda

Sto imparando Scala e ho un progetto Java da migrare a Scala.Voglio migrarlo riscrivendo le classi una per una e verificando che la nuova classe non abbia interrotto il progetto.

Questo progetto Java ne utilizza molti java.util.List E java.util.Map.Nelle nuove lezioni di Scala vorrei usare quelle di Scala List E Map per avere un codice Scala di bell'aspetto.

Il problema è che le nuove classi (quelle scritte in Scala) non si integrano perfettamente con il codice Java esistente:Java ha bisogno java.util.List, Scala ha bisogno del suo scala.List.

Ecco un esempio semplificato del problema.Ci sono lezioni Principale, Logica, Dao.Si chiamano in fila: Principale -> Logica -> 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);
    }
}

Nella mia situazione, lezioni Principale E Dao sono classi framework (non ho bisogno di migrarle).Classe Logica è una logica aziendale e trarrà grandi benefici dalle interessanti funzionalità di Scala.

Devo riscrivere la lezione Logica in Scala preservando l'integrità delle classi Principale E Dao.La migliore riscrittura sarebbe simile (non funziona):

class Logic2 {
  def calculate(ints: List[Integer]) : List[Integer] = {
      val together: List[Integer] = new Dao().getSomeInts()
      together ++ ints
  }
}

Comportamento ideale:Elenchi all'interno Logica2 sono liste Scala native.Tutto dentro/fuori java.util.Lists ottenere boxed/unboxed automaticamente.Ma questo non funziona.

Invece, questo funziona (grazie 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
  }
}

Ma sembra brutto.

Come posso ottenere una conversione magica trasparente di elenchi e mappe tra Java <-> Scala (senza bisogno di fare toScala/toJava)?

Se non è possibile, quali sono le migliori pratiche per la migrazione del codice Java -> Scala che utilizza java.util.List e amici?

È stato utile?

Soluzione

Fidati di me;tu no Volere conversione trasparente avanti e indietro.Questo è esattamente ciò che scala.collection.jcl.Conversions funzioni tentate di eseguire.In pratica provoca non pochi mal di testa.

La radice del problema con questo approccio è che Scala inserirà automaticamente le conversioni implicite necessarie per far funzionare una chiamata al metodo.Ciò può avere conseguenze davvero sfortunate.Per esempio:

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
}

Questo codice non sarebbe del tutto fuori dal comune per chi è nuovo al framework delle raccolte Scala o anche solo al concetto di raccolte immutabili.Sfortunatamente, è completamente sbagliato.Il risultato di questa funzione è il Stesso carta geografica.La chiamata a put attiva una conversione implicita in java.util.Map<String, Int>, che accetta volentieri i nuovi valori e viene prontamente scartata.L'originale map non è modificato (poiché è, infatti, immutabile).

Jorge Ortiz lo spiega meglio quando dice che dovresti definire le conversioni implicite solo per uno dei due scopi:

  • Aggiunta di membri (metodi, campi, ecc.).Queste conversioni dovrebbero essere in un nuovo tipo non correlato a qualsiasi altra cosa nell'ambito.
  • "Riparare" una gerarchia di classi rotta.Quindi, se hai alcuni tipi A E B che non sono correlati.Puoi definire una conversione A => B se e soltanto se avresti preferito averlo A <: B (<: significa "sottotipo").

Da java.util.Map ovviamente non è un nuovo tipo estraneo a qualsiasi cosa nella nostra gerarchia, non possiamo rientrare nella prima condizione.Pertanto, la nostra unica speranza è la nostra conversione Map[A, B] => java.util.Map[A, B] qualificarsi per la seconda.Tuttavia, per Scala non ha assolutamente senso Map ereditare da java.util.Map.Sono davvero interfacce/tratti completamente ortogonali.Come dimostrato sopra, tentare di ignorare queste linee guida si tradurrà quasi sempre in comportamenti strani e inaspettati.

La verità è che javautils asScala E asJava metodi sono stati progettati per risolvere questo problema esatto.Esiste una conversione implicita (alcune in realtà) in javautils from Map[A, B] => RichMap[A, B]. RichMap è un tipo completamente nuovo definito da javautils, quindi il suo unico scopo è aggiungere membri a Map.In particolare, aggiunge il asJava metodo, che restituisce una mappa wrapper che implementa java.util.Map e delega al tuo originale Map esempio.Ciò rende il processo molto più esplicito e molto meno soggetto a errori.

In altre parole, utilizzando asScala E asJava È la migliore pratica.Avendo percorso entrambe queste strade in modo indipendente in un'applicazione di produzione, posso dirti in prima persona che l'approccio Javautils è molto più sicuro e più facile da utilizzare.Non cercare di aggirare le sue protezioni solo per il gusto di risparmiare 8 personaggi!

Altri suggerimenti

Ecco alcuni rapidi esempi utilizzando scalaj-collezione della biblioteca di 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]

il progetto javautils è disponibile dal Maven centrale repository

Con Scala 2.8, potrebbe essere fatto in questo modo:

import scala.collection.JavaConversions._

val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top