Java <-> Scala interop: прозрачное преобразование списка и карты
Я изучаю Scala, и у меня есть проект Java для перехода на Scala. Я хочу перенести его, переписав классы один за другим и убедившись, что новый класс не сломал проект.
Этот проект Java использует много java.util.List
а также java.util.Map
, В новых классах Scala я бы хотел использовать Scala List
а также Map
иметь красивый код Scala.
Проблема в том, что новые классы (такие как Scitt в Scala) не интегрируются без шва с существующим кодом Java: потребности Java java.util.List
Скала нуждается в своем собственном scala.List
,
Вот упрощенный пример проблемы. Есть классы Main, Logic, Dao. Они называют друг друга в строке: 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);
}
}
В моей ситуации классы Main и Dao являются базовыми (мне не нужно их переносить). Class Logic - это бизнес-логика, которая очень выиграет от интересных возможностей Scala.
Мне нужно переписать класс Logic в Scala, сохраняя при этом целостность с классами Main и Dao. Лучшее переписывание будет выглядеть (не работает):
class Logic2 {
def calculate(ints: List[Integer]) : List[Integer] = {
val together: List[Integer] = new Dao().getSomeInts()
together ++ ints
}
}
Идеальное поведение: списки внутри Logic2 являются собственными списками Scala. Все в / из java.util.Lists
получить в штучной упаковке / распакованы автоматически. Но это не работает.
Вместо этого это работает (благодаря 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
}
}
Но это выглядит некрасиво.
Как мне добиться прозрачного волшебного преобразования списков и карт между Java <-> Scala (без необходимости делать toScala/toJava)?
Если это невозможно, каковы лучшие практики для миграции кода Java -> Scala, который использует java.util.List
и друзья?
3 ответа
Доверьтесь мне; Вы не хотите прозрачного преобразования назад и вперед. Это именно то, что scala.collection.jcl.Conversions
функции пытались сделать. На практике это вызывает много головных болей.
Корень проблемы этого подхода в том, что Scala автоматически внедрит неявные преобразования по мере необходимости, чтобы заставить вызов метода работать. Это может иметь некоторые действительно печальные последствия. Например:
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
}
Этот код не был бы совершенно не характерным для тех, кто не знаком с фреймворком коллекций Scala или даже с концепцией неизменяемых коллекций. К сожалению, это совершенно неправильно. Результатом этой функции является та же карта. Призыв к put
запускает неявное преобразование в java.util.Map<String, Int>
, который с радостью принимает новые значения и быстро отбрасывается. Оригинал map
неизменен (как, впрочем, и неизменен).
Хорхе Ортис считает, что лучше всего указывать неявные преобразования только для одной из двух целей:
- Добавление членов (методы, поля и т. Д.). Эти преобразования должны относиться к новому типу, не связанному ни с чем другим в области видимости.
- "Исправление" нарушенной иерархии классов. Таким образом, если у вас есть несколько типов
A
а такжеB
которые не связаны. Вы можете определить конверсиюA => B
если и только если бы вы предпочли иметьA <: B
(<:
означает "подтип").
поскольку java.util.Map
очевидно, это не новый тип, не связанный ни с чем в нашей иерархии, мы не можем попасть под первое условие. Таким образом, наша единственная надежда на наше обращение Map[A, B] => java.util.Map[A, B]
чтобы претендовать на второй. Тем не менее, это не имеет абсолютно никакого смысла для Scala Map
наследовать от java.util.Map
, Они действительно полностью ортогональные интерфейсы / черты. Как показано выше, попытка игнорировать эти рекомендации почти всегда приводит к странному и неожиданному поведению.
Правда заключается в том, что javautils asScala
а также asJava
методы были разработаны для решения именно этой проблемы. Существует неявное преобразование (на самом деле их несколько) в javautils из Map[A, B] => RichMap[A, B]
, RichMap
это совершенно новый тип, определенный javautils, поэтому его единственная цель - добавить членов в Map
, В частности, он добавляет asJava
метод, который возвращает карту-обертку, которая реализует java.util.Map
и делегаты на ваш оригинал Map
пример. Это делает процесс намного более явным и намного менее подверженным ошибкам.
Другими словами, используя asScala
а также asJava
это лучшая практика Пройдя обе эти дороги независимо друг от друга в производственном приложении, я могу сказать вам из первых рук, что подход javautils намного безопаснее и с ним легче работать. Не пытайтесь обойти его защиту только ради спасения себя 8 символов!
Вот несколько быстрых примеров использования библиотеки scalaj-collection Хорхе Ортиса:
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]
проект javautils доступен из центрального репозитория maven
С Scala 2.8 это можно сделать так:
import scala.collection.JavaConversions._
val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList