Java/Scala互操做實踐 1:基礎操做

本文將以Spring Boot爲例,介紹下實際工做中的Java/Scala互操做。在提升我的效率、知足自我追求的狀況下更多的照顧到團隊不一樣人的實際。同時也是想說明,在同一個工程裏混用Java和Scala語言是可能的。java

本文源代碼在:http://git.oschina.net/hualongdata/spring-startergit

Java Bean

Java Bean有個特色,就是對於可修改屬性都會有對應的gettersetter方法(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提供了註解來自動生成gettersetter函數: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)。在這裏,accountpasswordcaptcha將只生成getter函數。而smsCode將生成gettersetter函數,由於它使用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?

對於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在以前加上:getsetcreate等前綴進行修飾。

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
  }

這裏sheetsgetSheets實現相同的功能,區別是第一個是Scala API,第二個是Java API。

結語

本文較詳細的介紹了Java/Scala的互操做性,以上示例都來自做者及團隊的實際工做。

這篇文章簡單介紹了一些基礎的Java/Scala互操做方法,接下來的文章將介紹些高級的互操做:FutureOptional/Optionlamdba函數、類與接口等。

相關文章
相關標籤/搜索