本文將以Spring Boot爲例,介紹下實際工做中的Java/Scala互操做。在提升我的效率、知足自我追求的狀況下更多的照顧到團隊不一樣人的實際。同時也是想說明,在同一個工程裏混用Java和Scala語言是可能的。java
本文源代碼在:http://git.oschina.net/hualongdata/spring-startergit
Java Bean有個特色,就是對於可修改屬性都會有對應的getter和setter方法(final屬性將只有getter方法)。由Java定義的對象在Scala中能夠直接使用,並沒有二樣。而在Scala中定義Java Bean卻有些不一樣。程序員
其實在Scala中能夠像Java同樣來定義Java Bean:github
// Scala中默認爲public訪問權限,包括屬性和方法 class Person { // 下劃線在這裏是一個佔位符,它代碼相應屬性對應類型的默認值 private var id: Int = _ private var name: String = _; def getId: Int = id; def setId(id: Int) { this.id = id } def getName: String = name; def setName(name: String) { this.name = name; } }
這樣寫的話,除了語法上與Java有些差異,其實定義的方式是同樣的。但其實Scala提供了註解來自動生成getter和setter函數:web
import scala.beans.BeanProperty class Person { @BeanProperty var id: Int = _ @BeanProperty var name: String = _ @BeanProperty val createdAt: LocalDateTime = _ }
除了使用傳統的class,在Scala中還可使用case class來定義POJO:spring
case class SignRequest(@BeanProperty account: String = null, @BeanProperty password: String = null, @BeanProperty captcha: String = null, @BeanProperty var smsCode: String = null)
case class的主構造函數聲明的參數將同時作爲SignRequest
的履性,且是val
的(相似Java的public final
)。在這裏,account
、password
和captcha
將只生成getter函數。而smsCode
將生成getter和setter函數,由於它使用var
來修飾。編程
這裏有一個Java裏沒有的特 性:參數默認值,像C++、Python、ES6+ 同樣,Scala的參數是能夠設置默認值的。由於Java Bean規範要求類必需有參數爲空的默認構造函數,而當case class的主構造函數全部參數都設置默認值後,在實例化這個類時將至關於擁有一個空的默認構造函數。api
在Java中調用case class可見:com/hualongdata/springstarter/data/repository/UserRepositoryImpl.java。數組
在Spring開發中,依賴注入是很經常使用的一個特性 。基於屬性的註解注入在Java和Scala中都是同樣的。但基於構造函數的依賴注入在Scala中有些特別,代碼以下:app
class SignController @Autowired()(userService: UserService, webUtils: WebUtils, hlTokenComponent: HlTokenComponent) { ...... }
在Scala中,單註解做用於構造函數上時須要相似方法調用的形式:@Autowired()
。又由於Scala中,主構造函數必需定義在類名以後的小括號內,因此註解須要緊跟在類名之號,主構造函數左括號以前。
在Scala中使用主構造函數的注入組件是一個更好的實踐,它同時擁有注入的 組件爲private final訪問權限。相同效果的Java代碼須要更多:
public SignController { private final UserService userService; private final WebUtils webUtils; private final HlTokenComponent hlTokenComponent; public SignController(UserService userService, WebUtils webUtils, HlTokenComponent hlTokenComponent) { this.userService = userService; this.webUtils = webUtils; this.hlTokenComponent = hlTokenComponent; } }
能夠看到,Scala的版本代碼量更少,同時看起來更簡潔。
數組參數
@RestController @RequestMapping(Array("/sign")) class SignController @Autowired()(userService: UserService, ......
在Scala中,對於註解的數組參數當只設置一個元素時是不能像Java同樣賤一個字符串的,必需顯示的定義一個數組。
參數值必需爲常量
在Scala中,當爲註解的某個參數賤值時必需使用常量,像:@RequestMapping(Array(Constants.API_BASE + "/sign"))
這樣的形式都是非法的。只能像這樣賤值:@RequestMapping(Array("/aip/sign"))
在Scala中變長參數經過星號(*)來定義,代碼以下:
def log(format: String, value: String*)
可是這樣定義出來的變參在Java中是不能訪問的,由於Scala默認實現中value的類型爲: Seq[Any],而Java中的變參類型其實是一個數組( String[]
)。要解決這個問題很是簡單,在函數定義前加上scala.annotation.varargs
註解就能夠強制Scala使用Java的實現來實現變長參數。
Scala有本身的一套集合庫實現: scala.collection
,分爲不可變集合scala.collection.immutable
和可變集合scala.collection.mutable
。二者都實現了不少高階函數,能夠簡化平常編程,同時Scala中推薦使用不可變集合。
Java集合到Scala集合
Scala提供了scala.collection.JavaConverters
來轉換Java集合到Scala集合:
import scala.collection.JavaConverters._ /** * 根據sheet名獲取sheet全部單元格 * * @param workbook Excel [[Workbook]]對象 * @param sheetName sheet 名 * @return 返回全部有效單元格可迭代二維列表 */ def getSheetCells(workbook: Workbook, sheetName: String): Iterable[Iterable[RichCell]] = { workbook.getSheet(sheetName) .asScala .map(row => row.asScala.map(cell => new RichCell(cell))) }
workbook.getSheet
方法返回的Sheet
類型是實現了java.lang.Iterable
接口的可迭代類型。爲了使用Scala集合上提供的map
高階函數,咱們須要把Java集合轉換成Scala集合。能夠經過在Java集合上調用.asScala
函數來將其轉換成Scala集合,這裏運用了Scala裏的隱式轉換特性來實現。
Scala集合到Java集合
接下來咱們看另一個函數:
@varargs def getSheets(workbook: Workbook, sheetNames: String*): java.util.List[Sheet] = { sheets(workbook, sheetNames: _ *).asJava }
這個函數實現的功能是根據傳入的一個或多個Sheet名字從Excel裏獲取Sheet列表。sheets
函數返回的是一個Scala集合:Seq[Sheet]
,經過getSheets
代理函數將其轉換成Java集合,經過在Seq[Sheet]
上調用.asJava
方法來實現自動轉換。一樣的,這裏也運用了Scala的隱式轉換特性。
Java代碼中作集合轉換
以前的例子都是在Scala代碼中實現的,經過隱式轉換這一特性咱們發現作Java/Scala集合的相互轉換是很是方便的。但在Java代碼中作二者的轉換就不那麼直觀了,由於Java沒有隱式轉換這一特性,咱們須要顯示的調用代碼來先生成包裝類,再調用.asScala
或.asJava
方法來轉換集合類型:
import scala.collection.JavaConverters$; import scala.collection.mutable.Buffer; public static void demo() { List<String> list = Arrays.asList("dd", "dd"); // Java List 到 Scala Buffer Buffer<String> scalaBuffer = JavaConverters$.MODULE$.asScalaBufferConverter(list).asScala(); // Scala Buffer 到 Java List List<String> javaList = JavaConverters$.MODULE$.bufferAsJavaListConverter(scalaBuffer).asJava(); }
當在項目中混用Java和Scala語言時,有個問題不得不重視。提供的API是用Java仍是Scala來實現?實現的API是優先考慮兼容Java仍是Scala?
對於API的實現,用Java或Scala都可。若使用Java實現,在Scala中調用是基本無壓力的。而使用Scala實現時,爲了兼容Java你可能不得不做一些折中。一個經常使用的方式是:使用Scala或Java來實現API,而再用Java或Scala來實現一個封裝層(代理)做兼容。好比:Spark、Akka……,它們使用Scala來實現API,但提供了包裝的Java API層。
一個好的實踐是把Scala API放到scalaapi包路徑(或者反之把Java API放到javaapi包路徑)。
若咱們只提供一個API,那就要儘可能同時支持Java和Scala方便的 調用。好比使用@varargs
註解來修飾變長參數。
對於參數須要集合類型,或返回值爲集合類型的函數。咱們除了使用上一節提供的JavaConverters
來作自動/手動轉換之外,也能夠經過裝飾器形式來提供Java或Scala專有的API。這裏,我推薦Scala API函數名直接使用表明操做的名詞/動詞實現,而Java API在以前加上:get
、set
、create
等前綴進行修飾。
def sheets(workbook: Workbook, sheetNames: String*): Seq[Sheet] = { sheetNames.map(sheetName => workbook.getSheet(sheetName)) } @varargs def getSheets(workbook: Workbook, sheetNames: String*): java.util.List[Sheet] = { sheets(workbook, sheetNames: _ *).asJava }
這裏sheets
和getSheets
實現相同的功能,區別是第一個是Scala API,第二個是Java API。
本文較詳細的介紹了Java/Scala的互操做性,以上示例都來自做者及團隊的實際工做。
這篇文章簡單介紹了一些基礎的Java/Scala互操做方法,接下來的文章將介紹些高級的互操做:Future
、Optional/Option
、lamdba函數、類與接口等。