2009-10-05 6 views
33

J'apprends Scala et j'ai un projet Java à migrer vers Scala. Je veux le migrer en réécrivant les classes une par une et en vérifiant que cette nouvelle classe n'a pas brisé le projet. Ce projet Java utilise beaucoup de java.util.List et java.util.Map. Dans les nouvelles classes Scala, j'aimerais utiliser les codes List et Map de Scala pour avoir un code Scala attrayant.Java <-> Scala interop: liste transparente et conversion de carte

Le problème est que les nouvelles classes (celles qui sont wtitten dans Scala) ne s'intègrent pas sans faille avec le code Java existant: Java a besoin de java.util.List, Scala a besoin de ses propres scala.List.

Voici un exemple simplifié du problème. Il existe des classes Principal, Logique, Dao. Ils s'appellent l'un l'autre dans une ligne: Main -> Logic -> 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); 
    } 
} 

Dans ma situation, les classes principal et Dao sont des classes cadres (je ne suis pas besoin de les migrer). Classe Logique est la logique métier et bénéficiera beaucoup de fonctionnalités cool Scala.

J'ai besoin de réécrire la classe Logic à Scala, tout en préservant l'intégrité des classes principal et Dao. La meilleure rewrite ressemblerait (ne fonctionne pas):

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

comportement Idéal: Listes intérieur LOGIC2 native Scala sont cotés. All in/out java.util.Lists être mis en boîte/unboxed automagically. Mais ça ne marche pas.

, cela ne au lieu de travail (grâce à 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 
    } 
} 

Mais il semble laid. Comment puis-je obtenir une conversion magique transparente des listes et des cartes entre Java < -> Scala (sans besoin de faire toScala/toJava)?

Si ce n'est pas possible, quelles sont les meilleures pratiques pour migrer Java -> code Scala qui utilise java.util.List et les amis?

Répondre

65

Faites-moi confiance; vous ne voulez pas voulez conversion transparente en arrière et en avant. C'est précisément ce que les fonctions scala.collection.jcl.Conversions ont tenté de faire. En pratique, cela provoque beaucoup de maux de tête.

La racine du problème avec cette approche est que Scala injectera automatiquement les conversions implicites nécessaires pour faire fonctionner un appel de méthode. Cela peut avoir des conséquences vraiment malheureuses.Par exemple:

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 
} 

Ce code ne serait pas tout à fait hors de caractère pour quelqu'un qui est nouveau à la collection Scala cadre ou même le concept des collections immuables. Malheureusement, c'est complètement faux. Le résultat de cette fonction est la même carte. L'appel à put déclenche une conversion implicite en java.util.Map<String, Int>, qui accepte volontiers les nouvelles valeurs et est rapidement rejetée. L'original map n'est pas modifié (car il est, en effet, immuable).

Jorge Ortiz met le mieux quand il dit que vous ne devez définir les conversions implicites pour l'un des deux objectifs:

  • membres Ajout (méthodes, champs, etc.). Ces conversions doivent être à un nouveau type sans rapport à toute autre chose dans la portée.
  • "Correction" d'une hiérarchie de classe brisée. Ainsi, si vous avez des types A et B qui ne sont pas liés. Vous pouvez définir une conversion A => B si et seulement si vous auriez préféré avoir A <: B (<: signifie "sous-type").

Depuis java.util.Map est évidemment pas un nouveau type sans rapport avec quoi que ce soit dans notre hiérarchie, nous ne pouvons pas tomber sous la première condition. Ainsi, notre seul espoir est que notre conversion Map[A, B] => java.util.Map[A, B] se qualifie pour le second. Cependant, cela n'a absolument aucun sens que le Map de Scala hérite de java.util.Map. Ce sont vraiment des interfaces/traits complètement orthogonaux. Comme démontré ci-dessus, tenter d'ignorer ces directives entraînera presque toujours un comportement étrange et inattendu. La vérité est que les méthodes javautils asScala et asJava ont été conçues pour résoudre ce problème. Il y a une conversion implicite (un certain nombre d'entre eux en fait) dans javautils de Map[A, B] => RichMap[A, B]. RichMap est un tout nouveau type défini par javautils, donc son seul but est d'ajouter des membres à Map. En particulier, il ajoute la méthode asJava, qui renvoie une carte wrapper qui implémente java.util.Map et délègue à votre instance Map d'origine. Cela rend le processus beaucoup plus explicite et beaucoup moins sujette aux erreurs. En d'autres termes, en utilisant asScala et asJavaest la meilleure pratique. Ayant descendu ces deux routes de manière indépendante dans une application de production, je peux vous dire que l'approche de javautils est beaucoup plus sûre et plus facile à travailler. N'essayez pas de contourner ses protections simplement pour sauver 8 caractères!

+1

très bien mis, +1 – geowa4

+1

(+1) très joliment déclaré. J'aurais aimé que ce message soit publié quand je me suis débattu avec lui il y a quelque temps. – Shaun

+1

Merci pour cet article si détaillé. Trouvé ceci via google (bien sûr!) donc déjà sauvé quelqu'un beaucoup de douleur! –

3

Voici quelques exemples rapides avec Jorge Ortiz scalaj-collection library: projet

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] 

Les javautils est disponible à partir de la central maven repository

2

Avec Scala 2.8, il pourrait se faire comme ceci:

import scala.collection.JavaConversions._ 

val list = new java.util.ArrayList[String]() 
list.add("test") 
val scalaList = list.toList 
Questions connexes