33 votes

Java <-> Scala interop: conversion transparente de listes et de cartes

Je suis en train d'apprendre Scala et j'ai un projet en Java pour migrer à la Scala. Je veux migrer en réécrivant les classes une par une, et en vérifiant que la nouvelle classe ne rompt pas le projet.

Ce projet Java utilise beaucoup d' java.util.List et java.util.Map. Dans les nouvelles classes Scala je voudrais utiliser Scala List et Map d'avoir de bonne à la recherche de la Scala de code.

Le problème est que les nouvelles classes (ceux qui sont wtitten en Scala) de ne pas intégrer seamelessly avec Java code: Java besoins en java.util.List, Scala a besoin de sa propre scala.List.

Voici un exemple simplifié du problème. Il y a des classes Principales, la Logique, Dao. Ils s'appellent les uns les autres dans une ligne: Principal -> Logique -> 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 Principales et Dao sont les classes du framework (je n'ai pas besoin de migrer). Classe Logique est la logique de gestion et profitera à beaucoup de Scala fonctionnalités intéressantes.

J'ai besoin de réécrire la classe de Logique dans Scala, tout en préservant l'intégrité des classes Principales et Dao. Le meilleur de réécriture ressemblerait (ne fonctionne pas):

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

Le comportement idéal: des Listes à l'intérieur de Logic2 sont indigènes Scala Listes. Tous les in/out java.util.Lists obtenir boxed/ "unboxed" automagiquement. Mais cela ne fonctionne pas.

Au lieu de cela, ce n'travail (grâce à la 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 transparent magie de conversion de Listes et Cartes entre Java <-> Scala (sans besoin de faire toScala/toJava)?

Si il n'est pas possible, quelles sont les meilleures pratiques en matière de migration Java -> Scala code qui utilise java.util.List et les amis?

65voto

Daniel Spiewak Points 30706

Croyez-moi; vous n'avez pas envie de conversion transparentes d'avant en arrière. C'est précisément ce que l' scala.collection.jcl.Conversions fonctions tenté de le faire. Dans la pratique, il provoque beaucoup de maux de tête.

La racine du problème avec cette approche est à la Scala va injecter automatiquement les conversions implicites nécessaires pour faire un appel de méthode de travail. Ce qui peut avoir des vraiment des conséquences 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 totalement hors de caractère pour quelqu'un qui est nouveau à la Scala collections cadre ou même juste le concept de immuable collections. Malheureusement, il est complètement faux. Le résultat de cette fonction est la même carte. L'appel à l' put déclenche une conversion implicite en java.util.Map<String, Int>, qui accepte avec plaisir les nouvelles valeurs et est rapidement écarté. L'original de l' map n'est pas modifié (tel qu'il est, en effet, immuable).

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

  • L'ajout de membres (méthodes, champs, etc). Ces conversions doivent être à un nouveau type non lié à quoi que ce soit d'autre dans le champ d'application.
  • De "fixer" une fracture de la hiérarchie de classe. Ainsi, si vous avez certains types d' A et B qui ne sont pas liés. Vous pouvez définir une conversion A => B si et seulement si vous auraient préféré A <: B (<: signifie "sous-type").

Depuis java.util.Map n'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 de notre conversion Map[A, B] => java.util.Map[A, B] de se qualifier pour le second. Cependant, il ne fait absolument aucun sens pour Scala Map d'hériter d' java.util.Map. Ils sont vraiment complètement orthogonale interfaces/traits. Comme démontré ci-dessus, tentant d'ignorer ces lignes directrices seront presque toujours bizarre et un comportement inattendu.

La vérité est que la javautils asScala et asJava méthodes ont été conçues pour résoudre ce problème exact. 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, de sorte que son seul but est d'ajouter des membres à l' Map. En particulier, il ajoute l' asJava méthode, qui renvoie un wrapper de la carte qui implémente java.util.Map et les délégués à l'original de votre Map de l'instance. Cela rend le processus beaucoup plus explicite et beaucoup moins sujettes à erreur.

En d'autres termes, en utilisant des asScala et asJava est la meilleure pratique. Après avoir descendu deux de ces routes de façon indépendante dans une application de production, je peux vous dire de première main que le javautils approche est beaucoup plus sûr et plus facile de travailler avec. N'essayez pas de contourner ses protections uniquement pour le plaisir de vous sauver de 8 caractères!

3voto

David Carlson Points 899

Voici quelques exemples rapides utilisant la bibliothèque 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]
 

le projet javautils est disponible depuis le dépôt central maven

2voto

Somatik Points 2440

Avec Scala 2.8, cela pourrait être fait comme ceci:

 import scala.collection.JavaConversions._

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X