Java ↔ Scala interop: прозрачный список и преобразование карты
Я изучаю Scala, и у меня есть проект Java для перехода на Scala. Я хочу перенести его путем перезаписи классов один за другим и проверки того, что новый класс не нарушил проект.
В этом Java-проекте используются много java.util.List
и java.util.Map
. В новых классах Scala я хотел бы использовать Scala s List
и Map
, чтобы иметь красивый код Scala.
Проблема в том, что новые классы (те, что написаны в Scala) не интегрируются без использования существующего Java-кода: Java нуждается в java.util.List
, Scala нуждается в собственном scala.List
.
Вот упрощенный пример проблемы. Есть классы 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
и друзей?
Ответы
Ответ 1
Поверь мне; вы не хотите прозрачного преобразования взад и вперед. Это то, что пытались выполнить функции 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 символов!
Ответ 2
Вот несколько быстрых примеров с использованием Jorge Ortiz библиотека коллекции scalaj:
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
Ответ 3
С Scala 2.8 это можно сделать следующим образом:
import scala.collection.JavaConversions._
val list = new java.util.ArrayList[String]()
list.add("test")
val scalaList = list.toList