Scala 3 — 書籍

與 Java 互動

語言

簡介

本節探討如何在 Scala 中使用 Java 程式碼,以及相反地,如何在 Java 中使用 Scala 程式碼。

一般來說,在 Scala 中使用 Java 程式碼非常順暢。只有少數幾個點您會想要使用 Scala 工具程式將 Java 概念轉換為 Scala,包括

  • Java 集合類別
  • Java Optional 類別

同樣地,如果你正在撰寫 Java 程式碼,並想要使用 Scala 概念,你會想要轉換 Scala 集合和 Scala Option 類別。

以下各節說明你會需要的最常見轉換

  • 如何在 Scala 中使用 Java 集合
  • 如何在 Scala 中使用 Java Optional
  • 在 Scala 中擴充 Java 介面
  • 如何在 Java 中使用 Scala 集合
  • 如何在 Java 中使用 Scala Option
  • 如何在 Java 中使用 Scala 特質
  • 如何在 Java 程式碼中處理會擲出例外狀況的 Scala 方法
  • 如何在 Java 中使用 Scala 變數參數
  • 建立替代名稱,以便在 Java 中使用 Scala 方法

請注意,本節中的 Java 範例假設你使用的是 Java 11 或更新版本。

如何在 Scala 中使用 Java 集合

當你撰寫 Scala 程式碼,而 API 需要或產生 Java 集合類別(來自 java.util 套件)時,直接使用或建立集合是有效的,就像你在 Java 中所做的一樣。

不過,對於 Scala 中的慣用語法,例如集合上的 for 迴圈,或套用高階函式,例如 mapfilter,你可以建立一個代理,其行為就像 Scala 集合。

以下是如何運作的範例。針對會傳回 java.util.List[String] 的 API

public interface Foo {
  static java.util.List<String> getStrings() {
    return List.of("a", "b", "c");
  }
}

你可以使用 Scala scala.jdk.CollectionConverters 物件中的轉換公用程式,將該 Java 清單轉換為 Scala Seq

import scala.jdk.CollectionConverters._
import scala.collection.mutable

def testList() = {
  println("Using a Java List in Scala")
  val javaList: java.util.List[String] = Foo.getStrings()
  val scalaSeq: mutable.Seq[String] = javaList.asScala
  for (s <- scalaSeq) println(s)
}
import scala.jdk.CollectionConverters.*
import scala.collection.mutable

def testList() =
  println("Using a Java List in Scala")
  val javaList: java.util.List[String] = Foo.getStrings()
  val scalaSeq: mutable.Seq[String] = javaList.asScala
  for s <- scalaSeq do println(s)

在上述程式碼中,javaList.asScala 建立一個包裝器,將 java.util.List 適配到 Scala 的 mutable.Seq 集合。

如何在 Scala 中使用 Java Optional

當您在 Scala 程式碼中與使用 java.util.Optional 類別的 API 進行互動時,建立並使用時如同在 Java 中一樣即可。

然而,對於 Scala 中的慣用語法,例如與 for 搭配使用,您可以將其轉換為 Scala 的 Option

為了示範,以下是傳回 Optional[String] 值的 Java API

public interface Bar {
  static java.util.Optional<String> optionalString() {
    return Optional.of("hello");
  }
}

首先從 scala.jdk.OptionConverters 物件匯入所有成員,然後使用 toScala 方法將 Optional 值轉換為 Scala 的 Option

import java.util.Optional
import scala.jdk.OptionConverters._

val javaOptString: Optional[String] = Bar.optionalString
val scalaOptString: Option[String] = javaOptString.toScala
import java.util.Optional
import scala.jdk.OptionConverters.*

val javaOptString: Optional[String] = Bar.optionalString
val scalaOptString: Option[String] = javaOptString.toScala

在 Scala 中擴充 Java 介面

如果您需要在 Scala 程式碼中使用 Java 介面,請延伸它們,就像它們是 Scala 特質一樣。例如,給定這三個 Java 介面

public interface Animal {
  void speak();
}

public interface Wagging {
  void wag();
}

public interface Running {
  // an implemented method
  default void run() {
    System.out.println("I’m running");
  }
}

您可以建立 Scala 中的 Dog 類別,就像您使用特質一樣。由於 run 具有預設實作,因此您只需要實作 speakwag 方法

class Dog extends Animal with Wagging with Running {
  def speak = println("Woof")
  def wag = println("Tail is wagging")
}

def useJavaInterfaceInScala = {
  val d = new Dog()
  d.speak
  d.wag
  d.run
}
class Dog extends Animal, Wagging, Running:
  def speak = println("Woof")
  def wag = println("Tail is wagging")

def useJavaInterfaceInScala =
  val d = Dog()
  d.speak
  d.wag
  d.run

另外請注意,在 Scala 中,定義為空參數清單的 Java 方法可以像在 Java 中一樣呼叫,.wag(),或者您可以選擇不使用括號 .wag

如何在 Java 中使用 Scala 集合

當您需要在 Java 程式碼中使用 Scala 集合類別時,請在 Java 程式碼中使用 Scala 的 scala.jdk.javaapi.CollectionConverters 物件的方法,以進行轉換。

例如,假設一個 Scala API 回傳一個 List[String] 如下所示

object Baz {
  val strings: List[String] = List("a", "b", "c")
}
object Baz:
  val strings: List[String] = List("a", "b", "c")

您可以在 Java 程式碼中像這樣存取該 Scala List

import scala.jdk.javaapi.CollectionConverters;

// access the `strings` method with `Baz.strings()`
scala.collection.immutable.List<String> xs = Baz.strings();

java.util.List<String> listOfStrings = CollectionConverters.asJava(xs);

for (String s: listOfStrings) {
  System.out.println(s);
}

該程式碼可以縮短,但顯示完整的步驟以說明處理程序式的運作方式。請務必注意,雖然 Baz 有名為 strings 的欄位,但從 Java 來看,該欄位顯示為方法,因此必須用括號 .strings() 呼叫。

如何在 Java 中使用 Scala Option

當您需要在 Java 程式碼中使用 Scala Option 時,可以使用 Scala scala.jdk.javaapi.OptionConverters 物件的 toJava 方法將 Option 轉換為 Java Optional 值。

例如,假設一個 Scala API 回傳一個 Option[String] 如下所示

object Qux {
  val optString: Option[String] = Option("hello")
}
object Qux:
  val optString: Option[String] = Option("hello")

然後您可以在 Java 程式碼中像這樣存取該 Scala Option

import java.util.Optional;
import scala.Option;
import scala.jdk.javaapi.OptionConverters;

Option<String> scalaOptString = Qux.optString();
Optional<String> javaOptString = OptionConverters.toJava(scalaOptString);

該程式碼可以縮短,但顯示完整的步驟以說明處理程序式的運作方式。請務必注意,雖然 Qux 有名為 optString 的欄位,但從 Java 來看,該欄位顯示為方法,因此必須用括號 .optString() 呼叫。

如何在 Java 中使用 Scala 特質

從 Java 8 開始,您可以使用 Scala 特質就像 Java 介面一樣,即使該特質已實作方法。例如,給定這兩個 Scala 特質,一個具有實作方法,一個只有介面

trait ScalaAddTrait {
  def sum(x: Int, y: Int) = x + y     // implemented
}

trait ScalaMultiplyTrait {
  def multiply(x: Int, y: Int): Int   // abstract
}
trait ScalaAddTrait:
  def sum(x: Int, y: Int) = x + y     // implemented

trait ScalaMultiplyTrait:
  def multiply(x: Int, y: Int): Int   // abstract

Java 類別可以實作這兩個介面,並定義 multiply 方法

class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait {
  public int multiply(int a, int b) {
    return a * b;
  }
}

JavaMath jm = new JavaMath();
System.out.println(jm.sum(3,4));        // 7
System.out.println(jm.multiply(3,4));   // 12

如何在 Java 程式碼中處理會擲出例外狀況的 Scala 方法

當您使用 Scala 程式慣例撰寫 Scala 程式碼時,您永遠不會撰寫會擲回例外狀況的方法。但如果出於某種原因,您有會擲回例外狀況的 Scala 方法,而且您希望 Java 開發人員能夠使用該方法,請將 @throws 註解新增到您的 Scala 方法,這樣 Java 使用者就會知道它可以擲回哪些例外狀況。

例如,這個 Scala exceptionThrower 方法加上註解,宣告它會擲回 Exception

object SExceptionThrower {
  @throws[Exception]
  def exceptionThrower =
    throw new Exception("Idiomatic Scala methods don’t throw exceptions")
}
object SExceptionThrower:
  @throws[Exception]
  def exceptionThrower =
    throw Exception("Idiomatic Scala methods don’t throw exceptions")

因此,您需要在 Java 程式碼中處理例外狀況。例如,這段程式碼無法編譯,因為我沒有處理例外狀況

// won’t compile because the exception isn’t handled
public class ScalaExceptionsInJava {
  public static void main(String[] args) {
    SExceptionThrower.exceptionThrower();
  }
}

編譯器會產生這個錯誤

[error] ScalaExceptionsInJava: unreported exception java.lang.Exception;
        must be caught or declared to be thrown
[error] SExceptionThrower.exceptionThrower()

這很好,這就是您想要的:註解會告訴 Java 編譯器 exceptionThrower 可以擲回例外狀況。現在,當您撰寫 Java 程式碼時,您必須使用 try 區塊處理例外狀況,或宣告您的 Java 方法會擲回例外狀況。

相反地,如果您沒有在 Scala exceptionThrower 方法上加上註解,Java 程式碼會編譯。這可能不是您想要的,因為 Java 程式碼可能沒有考量到 Scala 方法會擲回例外狀況。

如何在 Java 中使用 Scala 變數參數

當 Scala 方法有 varargs 參數,而且您要在 Java 中使用該方法時,請用 @varargs 註解標記 Scala 方法。例如,這個 Scala 類別中的 printAll 方法宣告 String* varargs 欄位

import scala.annotation.varargs

object VarargsPrinter {
  @varargs def printAll(args: String*): Unit = args.foreach(println)
}
import scala.annotation.varargs

object VarargsPrinter:
  @varargs def printAll(args: String*): Unit = args.foreach(println)

由於 printAll 是使用 @varargs 註解宣告的,因此可以從 Java 程式呼叫它,並使用變數個數的參數,如這個範例所示

public class JVarargs {
  public static void main(String[] args) {
    VarargsPrinter.printAll("Hello", "world");
  }
}

當執行此程式碼時,會產生下列輸出

Hello
world

建立替代名稱,以便在 Java 中使用 Scala 方法

在 Scala 中,你可能會想使用符號字元建立方法名稱

def +(a: Int, b: Int) = a + b

該方法名稱在 Java 中無法正常運作,但你可以在 Scala 中使用 targetName 註解提供方法的「替代」名稱,這將會是從 Java 使用時的方法名稱

import scala.annotation.targetName

object Adder {
  @targetName("add") def +(a: Int, b: Int) = a + b
}
import scala.annotation.targetName

object Adder:
  @targetName("add") def +(a: Int, b: Int) = a + b

現在,你可以在 Java 程式碼中使用別名方法名稱 add

int x = Adder.add(1,1);
System.out.printf("x = %d\n", x);

此頁面的貢獻者