2009-10-05 2 views
33

Я изучаю Scala, и у меня есть проект Java для перехода на Scala. Я хочу перенести его путем перезаписи классов один за другим и проверки того, что новый класс не разбил проект.Java <-> Scala interop: transparent Преобразование списка и карты

В этом Java-проекте используется множество java.util.List и java.util.Map. В новых классах Scala я хотел бы использовать Scala's List и Map, чтобы иметь красивый код Scala.

Проблема в том, что новые классы (те, что написаны в Scala) не интегрируются без использования существующего кода Java: Java нуждается в 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); 
    } 
} 

В моей ситуации, классы Главная и Dao рамочные классы (не нужно переносить их). Класс Логика является бизнес-логикой и принесет много пользы от замечательных функций Scala.

мне нужно переписать класс Logic в Scala при сохранении целостности с классами Главная и 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 и друзей?

ответ

65

Поверьте мне; вы не хотите прозрачный переход туда и обратно. Это именно то, что делали функции 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 не изменен (так как он действительно является неизменным).

Jorge Ortiz ставит его лучше всего, когда он говорит, что вы должны определить только неявные преобразования для одной из двух целей:

  • Добавление членов (методы, поля и т.д.). Эти преобразования должны быть для нового типа , не связанного, с чем-либо еще в области.
  • «Фиксирование» сломанной иерархии классов. Таким образом, если у вас есть некоторые типы 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 символов!

+1

очень хорошо положил, +1 – geowa4

+1

(+1) очень красиво заявлено. Я бы хотел, чтобы этот пост был вокруг, когда я некоторое время назад боролся с ним. – Shaun

+1

Спасибо за такую ​​полную запись. Нашли это через google (конечно!), так что уже сэкономил кому-то много боли! –

3

Вот несколько примеров использования scalaj-collection library Хорхе Ортис:

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 доступен из central maven repository

2

С Scala 2.8, это можно сделать так:

import scala.collection.JavaConversions._ 

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

 Смежные вопросы

  • Нет связанных вопросов^_^