2009-10-05 83 views
33

我正在學習Scala,我有一個Java項目遷移到斯卡拉。我想通過逐個重寫類並檢查新類沒有破壞項目來遷移它。Java <->斯卡拉interop:透明列表和地圖轉換

該Java項目使用大量的java.util.Listjava.util.Map。在新的Scala課程中,我希望使用Scala的ListMap以獲得好看的Scala代碼。

問題是,新類(這些是Scala中的wtitten)沒有與現有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); 
    } 
} 

在我的情況,班主要是框架類(我不需要遷移它們)。類邏輯是商業邏輯,將從Scala很酷的功能中受益匪淺。

我需要重寫類邏輯 Scala中,同時保留與類主要完整性。最好重寫會是什麼樣子(不工作):

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

理想行爲:列表內Logic2是本地斯卡拉列出。全部進出java.util.Lists自動獲得裝箱/取消裝箱。但這不起作用。

相反,這樣沒有問題(感謝scala-javautilsGitHub)):

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和Java之間的列表和地圖的透明魔術轉換 - > Scala(不需要做toScala/toJava)?

如果不可能,遷移Java的最佳實踐是什麼 - >使用java.util.List和朋友的Scala代碼是什麼?

回答

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是未修改的(因爲它確實是不可變的)。

  • 添加成員(方法,字段等)時,他說,你應該只定義隱式轉換爲兩個目的一個

    豪爾赫·奧爾蒂斯說得最好。這些轉換應該是與新的不相關的到範圍內的任何其他類型。

  • 「修復」破碎的類層次結構。因此,如果你有一些類型AB這是不相關的。你可以定義一個轉換A => B if和只有如果你願意有A <: B<:表示「子類型」)。

由於java.util.Map顯然不是一個與我們的層次結構中的任何東西無關的新類型,我們不能置於第一個限制條件之下。因此,我們唯一的希望是我們的轉換Map[A, B] => java.util.Map[A, B]有資格獲得第二個。但是,Scala的Map繼承自java.util.Map絕對沒有意義。它們是完全正交的接口/特徵。如上所示,試圖忽略這些準則幾乎總是會導致奇怪和意外的行爲。

事實是,javautils asScalaasJava方法被設計來解決這個確切的問題。從Map[A, B] => RichMap[A, B]開始,在javautils中有一個隱式轉換(實際上有一些轉換)。 RichMap是由javautils定義的全新類型,因此其唯一目的是將成員添加到Map。特別是,它增加了asJava方法,該方法返回一個包裝圖,該包裝圖實現了java.util.Map,並委託給您的原始Map實例。這使得這個過程更加明確,並且更不容易出錯。

換句話說,使用asScalaasJava的最佳實踐。在生產應用程序中獨立完成了這兩條路,我可以直接告訴你,javautils方法更安全,更容易處理。不要試圖繞過它的保護,僅僅是爲了拯救你自己的8個角色!

+1

非常好,+1 – geowa4 2009-10-05 15:33:31

+1

(+1)非常好地陳述。我希望這篇文章已經出現,當我在一段時間後與之奮鬥時。 – Shaun 2009-10-05 15:46:57

+1

感謝您的詳細報道。通過谷歌找到了(當然!)所以已經救了一個人很多的痛苦! – 2009-10-15 06:59:36

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