Java Scala interop: прозрачное преобразование списков и карт
Я изучаю Scala, и у меня есть проект Java для миграции в Scala. Я хочу перенести его, переписывая классы один за другим и проверяя, что новый класс не сломал проект.
Этот проект Java использует множество java.util.List
и java.util.Map
. В новых классах Scala я хотел бы использовать Scala List
и Map
, чтобы иметь красивый Scala-код.
Проблема в том, что новые классы (которые являются wtitten в Scala) не интегрируются без стыка с существующим кодом Java: Java needs java.util.List
, Scala нуждается в своем собственном scala.List
.
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 являются классами фреймворка (мне не нужно их переносить). КлассLogic -это бизнес-логика, и она будет очень полезна благодаря классным функциям Scala.
Мне нужно переписать класс логику в 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 автоматически вводит неявные преобразования, необходимые для работы вызова метода. Это может иметь некоторые действительно печальные последствия. Например:scala.collection.jcl.Conversions
. На практике это вызывает много головных болей.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]
соответствовало второму. Однако для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