Scala 3 — Book

Взаимодействие с Java

Language

Введение

В этом разделе рассматривается, как использовать код Java в Scala и, наоборот, как использовать код Scala в Java.

В целом, использование Java-кода в Scala довольно простое. Есть лишь несколько моментов, когда может появиться желание использовать утилиты Scala для преобразования концепций Java в Scala, в том числе:

  • Классы коллекций Java
  • Java класс Optional

Аналогично, если вы пишете код Java и хотите использовать концепции Scala, вам потребуется преобразовать коллекции Scala и Scala класс Option.

В следующих разделах демонстрируются наиболее распространенные преобразования, которые вам могут понадобиться:

  • Как использовать коллекции Java в Scala
  • Как использовать Java Optional в Scala
  • Расширение Java интерфейсов в Scala
  • Как использовать коллекции Scala в Java
  • Как использовать Scala Option в Java
  • Как использовать трейты Scala в Java
  • Как обрабатывать методы Scala, которые вызывают исключения в коде Java
  • Как использовать vararg-параметры Scala в Java
  • Создание альтернативных имен для использования методов Scala в Java

Обратите внимание: примеры Java в этом разделе предполагают, что вы используете Java 11 или более позднюю версию.

Как использовать коллекции Java в Scala

Когда вы пишете код на Scala, а API либо требует, либо создает класс коллекции Java (из пакета java.util), тогда допустимо напрямую использовать или создавать коллекцию, как в Java.

Однако для идиоматического использования в Scala, например, для циклов for по коллекции или для применения функций высшего порядка, таких как map и filter, вы можете создать прокси, который будет вести себя как коллекция Scala.

Вот пример того, как это работает. Учитывая следующий API, который возвращает java.util.List[String]:

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

Вы можете преобразовать этот Java список в Scala Seq, используя утилиты преобразования из Scala объекта scala.jdk.CollectionConverters:

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.

Как использовать Java Optional в Scala

Когда вы взаимодействуете с API, который использует класс java.util.Optional в коде Scala, его можно создавать и использовать, как в Java.

Однако для идиоматического использования в Scala, например использования в for, вы можете преобразовать его в Scala Option.

Чтобы продемонстрировать это, вот Java API, который возвращает значение типа Optional[String]:

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

Расширение Java интерфейсов в Scala

Если вам нужно использовать Java интерфейсы в коде Scala, расширяйте их так, как если бы они были трейтами 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");
  }
}

вы можете создать класс Dog в Scala так же, как если бы вы использовали трейты. Поскольку у run есть реализация по умолчанию, вам нужно реализовать только методы speak и wag:

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.

Как использовать коллекции Scala в Java

Если вам нужно использовать класс коллекции Scala в своем Java-коде, используйте методы Scala объекта scala.jdk.javaapi.CollectionConverters в своем Java-коде, для корректной работы конверсии.

Например, предположим, что Scala API возвращает List[String], как следующем примере:

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

Вы можете получить доступ к Scala List в Java-коде следующим образом:

import scala.jdk.javaapi.CollectionConverters;

// получить доступ к методу `strings` с помощью `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().

Как использовать Scala Option в Java

Если вам нужно использовать Scala Option в коде Java, вы можете преобразовать значение Option в значение Java Optional, используя метод toJava объекта Scala scala.jdk.javaapi.OptionConverters.

Например, предположим, что Scala API возвращает Option[String], как следующем примере:

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

Затем вы можете получить доступ к Scala Option в своем Java-коде следующим образом:

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().

Как использовать трейты Scala в Java

Начиная с Java 8, вы можете использовать трейт Scala точно так же, как Java интерфейс, даже если этот трейт реализует методы. Например, учитывая эти два трейта Scala, один с реализованным методом, а другой только с интерфейсом:

trait ScalaAddTrait {
  def sum(x: Int, y: Int) = x + y     // реализован
}

trait ScalaMultiplyTrait {
  def multiply(x: Int, y: Int): Int   // абстрактный
}
trait ScalaAddTrait:
  def sum(x: Int, y: Int) = x + y     // реализован

trait ScalaMultiplyTrait:
  def multiply(x: Int, y: Int): Int   // абстрактный

Класс 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

Как обрабатывать методы Scala, которые вызывают исключения в коде Java

Когда вы пишете код на 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-коде. Например, этот код не скомпилируется из-за необработанного исключения:

// не скомпилируется, потому что исключение не обработано
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, выдающий исключение.

Как использовать vararg-параметры Scala в Java

Если метод Scala имеет неопределенное количество параметров и вы хотите использовать этот метод в Java, отметьте Scala метод аннотацией @varargs. Например, метод printAll в этом Scala классе объявляет vararg-поле String*:

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

Создание альтернативных имен для использования методов Scala в Java

В 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);

Contributors to this page: