From e4d56fa7e26ec1687e68b6f9b2ea7a068a8ebc4f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 14 Jul 2023 22:25:44 +0200 Subject: [PATCH 1/2] port java interaction to scala 2 --- .../scala3-book/interacting-with-java.md | 329 ++++++++++++------ 1 file changed, 224 insertions(+), 105 deletions(-) diff --git a/_overviews/scala3-book/interacting-with-java.md b/_overviews/scala3-book/interacting-with-java.md index 06a5a8fa62..96e10731ca 100644 --- a/_overviews/scala3-book/interacting-with-java.md +++ b/_overviews/scala3-book/interacting-with-java.md @@ -39,209 +39,277 @@ Note that the Java examples in this section assume that you’re using Java 11 o ## How to use Java collections in Scala -When you’re writing Scala code and need to use a Java collection class, you _can_ just use the class as-is. -However, if you want to use the class in a Scala `for` loop, or want to take advantage of the higher-order functions on the Scala collections classes, you’ll want to convert the Java collection to a Scala collection. +When you’re writing Scala code and an API either requires or produces a Java collection class (from the `java.util` package), then it is valid to directly use or create the collection as you would in Java. + +However, for idiomatic usage in Scala, such as `for` loops over the collection, or to apply higher-order functions such as `map` and `filter`, you can create a proxy that behaves like a Scala collection. Here’s an example of how this works. -Given this Java `ArrayList`: +Given this API that returns `java.util.List[String]`: +{% tabs foo-definition %} +{% tab Java %} ```java -// java -public class JavaClass { - public static List getStrings() { - return new ArrayList(List.of("a", "b", "c")); - } +public interface Foo { + static java.util.List getStrings(); } ``` +{% endtab %} +{% endtabs %} + +You can convert that Java list to a Scala `Seq`, using the conversion utilities in the Scala `scala.jdk.CollectionConverters` object: -You can convert that Java list to a Scala `Seq`, using the conversion utilities in the Scala _scala.jdk.CollectionConverters_ package: +{% tabs foo-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +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) +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala import scala.jdk.CollectionConverters.* +import scala.collection.mutable -def testList() = +def testList() = println("Using a Java List in Scala") - val javaList: java.util.List[String] = JavaClass.getStrings() - val scalaSeq: Seq[String] = javaList.asScala.toSeq - scalaSeq.foreach(println) + val javaList: java.util.List[String] = Foo.getStrings() + val scalaSeq: mutable.Seq[String] = javaList.asScala for s <- scalaSeq do println(s) ``` +{% endtab %} +{% endtabs %} -Of course that code can be shortened, but the individual steps are shown here to demonstrate exactly how the conversion process works. - +In the above code `scalaSeq` is a wrapper over `javaList` that adapt's it to Scala's `mutable.Seq` collection. ## How to use Java `Optional` in Scala -When you need to use the Java `Optional` class in your Scala code, import the _scala.jdk.OptionConverters_ object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`. +When you are interacting with an API that uses the `java.util.Optional` class in your Scala code, it is fine to construct and use as in Java. -To demonstrate this, here’s a Java class with two `Optional` values, one containing a string and the other one empty: +However, for idiomatic usage in Scala, such as use with `for`, you can convert it to a Scala `Option`. -```java -// java -import java.util.Optional; +To demonstrate this, here’s a Java API that returns an `Optional[String]` value: -public class JavaClass { - static Optional oString = Optional.of("foo"); - static Optional oEmptyString = Optional.empty(); +{% tabs bar-definition %} +{% tab Java %} +```java +public interface Bar { + static java.util.Optional optionalString(); } ``` +{% endtab %} +{% endtabs %} -Now in your Scala code you can access those fields. -If you just access them directly, they’ll both be `Optional` values: +First import the `scala.jdk.OptionConverters` object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`: +{% tabs bar-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala import java.util.Optional +import scala.jdk.OptionConverters._ -val optionalString = JavaClass.oString // Optional[foo] -val eOptionalString = JavaClass.oEmptyString // Optional.empty +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala ``` - -But by using the _scala.jdk.OptionConverters_ methods, you can convert them to Scala `Option` values: - +{% endtab %} +{% tab 'Scala 3' %} ```scala import java.util.Optional import scala.jdk.OptionConverters.* -val optionalString = JavaClass.oString // Optional[foo] -val optionString = optionalString.toScala // Some(foo) - -val eOptionalString = JavaClass.oEmptyString // Optional.empty -val eOptionString = eOptionalString.toScala // None +val javaOptString: Optional[String] = Bar.optionalString +val scalaOptString: Option[String] = javaOptString.toScala ``` - - +{% endtab %} +{% endtabs %} ## Extending Java interfaces in Scala If you need to use Java interfaces in your Scala code, extend them just as though they are Scala traits. For example, given these three Java interfaces: +{% tabs animal-definition %} +{% tab Java %} ```java -// java -interface Animal { +public interface Animal { void speak(); } -interface Wagging { +public interface Wagging { void wag(); } -interface Running { +public interface Running { // an implemented method default void run() { System.out.println("I’m running"); } } ``` +{% endtab %} +{% endtabs %} you can create a `Dog` class in Scala just as though you were using traits. All you have to do is implement the `speak` and `wag` methods: +{% tabs animal-usage class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -class Dog extends Animal, Wagging, Running: +class Dog extends Animal with Wagging with Running { def speak = println("Woof") def wag = println("Tail is wagging") +} -@main def useJavaInterfaceInScala = - val d = new Dog +def useJavaInterfaceInScala = { + val d = new Dog() d.speak d.wag + d.run +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +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 +``` +{% endtab %} +{% endtabs %} ## How to use Scala collections in Java When you need to use a Scala collection class in your Java code, use the methods of Scala’s `scala.jdk.javaapi.CollectionConverters` object in your Java code to make the conversions work. -For example, if you have a `List[String]` like this in a Scala class: +For example, suppose that a Scala API returns a `List[String]` like this: + +{% tabs baz-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -class ScalaClass: - val strings = List("a", "b") +object Baz { + val strings: List[String] +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Baz: + val strings: List[String] +``` +{% endtab %} +{% endtabs %} You can access that Scala `List` in your Java code like this: +{% tabs baz-usage %} +{% tab Java %} ```java -// java import scala.jdk.javaapi.CollectionConverters; -// create an instance of the Scala class -ScalaClass sc = new ScalaClass(); - -// access the `strings` field as `sc.strings()` -scala.collection.immutable.List xs = sc.strings(); +// access the `strings` method with `Baz.strings()` +scala.collection.immutable.List xs = Baz.strings(); -// convert the Scala `List` a Java `List` java.util.List listOfStrings = CollectionConverters.asJava(xs); -listOfStrings.forEach(System.out::println); + +for (String s: listOfStrings) { + System.out.println(s); +} ``` +{% endtab %} +{% endtabs %} That code can be shortened, but the full steps are shown to demonstrate how the process works. -Here are a few things to notice in that code: - -- In your Java code, you create an instance of `ScalaClass` just like an instance of a Java class -- `ScalaClass` has a field named `strings`, but from Java you access that field as a method, i.e., as `sc.strings()` - +Be sure to notice that while `Baz` has a field named `strings`, from Java the field appears as a method, so must be called with parentheses `.strings()`. ## How to use Scala `Option` in Java When you need to use a Scala `Option` in your Java code, you can convert the `Option` to a Java `Optional` value using the `toJava` method of the Scala `scala.jdk.javaapi.OptionConverters` object. -To demonstrate this, create a Scala class with two `Option[String]` values, one containing a string and the other one empty: +For example, suppose that a Scala API returns an `Option[String]` like this: +{% tabs qux-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -object ScalaObject: - val someString = Option("foo") - val noneString: Option[String] = None +object Qux { + val optString: Option[String] +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object Qux: + val optString: Option[String] +``` +{% endtab %} +{% endtabs %} -Then in your Java code, convert those `Option[String]` values into `java.util.Optional[String]` using the `toJava` method from the `scala.jdk.javaapi.OptionConverters` object: +Then you can access that Scala `Option` in your Java code like this: +{% tabs qux-usage %} +{% tab Java %} ```java -// java import java.util.Optional; -import static scala.jdk.javaapi.OptionConverters.toJava; +import scala.Option; +import scala.jdk.javaapi.OptionConverters; -public class JUseScalaOptionInJava { - public static void main(String[] args) { - Optional stringSome = toJava(ScalaObject.someString()); // Optional[foo] - Optional stringNone = toJava(ScalaObject.noneString()); // Optional.empty - System.out.printf("stringSome = %s\n", stringSome); - System.out.printf("stringNone = %s\n", stringNone); - } -} +Option scalaOptString = Qux.optString(); +Optional javaOptString = OptionConverters.toJava(scalaOptString); ``` +{% endtab %} +{% endtabs %} -The two Scala `Option` fields are now available as Java `Optional` values. - - +That code can be shortened, but the full steps are shown to demonstrate how the process works. +Be sure to notice that while `Qux` has a field named `optString`, from Java the field appears as a method, so must be called with parentheses `.optString()`. ## How to use Scala traits in Java -With Java 11 you can use a Scala trait just like a Java interface, even if the trait has implemented methods. +From Java 8 you can use a Scala trait just like a Java interface, even if the trait has implemented methods. For example, given these two Scala traits, one with an implemented method and one with only an interface: +{% tabs scala-trait-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +trait ScalaAddTrait { + def sum(x: Int, y: Int) = x + y // implemented +} + +trait ScalaMultiplyTrait { + def multiply(x: Int, y: Int): Int // abstract +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala trait ScalaAddTrait: - def sum(x: Int, y: Int) = x + y // implemented + def sum(x: Int, y: Int) = x + y // implemented trait ScalaMultiplyTrait: def multiply(x: Int, y: Int): Int // abstract ``` +{% endtab %} +{% endtabs %} A Java class can implement both of those interfaces, and define the `multiply` method: +{% tabs scala-trait-usage %} +{% tab Java %} ```java -// java class JavaMath implements ScalaAddTrait, ScalaMultiplyTrait { public int multiply(int a, int b) { return a * b; @@ -252,6 +320,8 @@ JavaMath jm = new JavaMath(); System.out.println(jm.sum(3,4)); // 7 System.out.println(jm.multiply(3,4)); // 12 ``` +{% endtab %} +{% endtabs %} @@ -262,29 +332,45 @@ But if for some reason you have a Scala method that does throw an exception, and For example, this Scala `exceptionThrower` method is annotated to declare that it throws an `Exception`: +{% tabs except-throw-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} ```scala -// scala -object SExceptionThrower: - @throws(classOf[Exception]) - def exceptionThrower = +object SExceptionThrower { + @throws[Exception] + def exceptionThrower = throw new Exception("Idiomatic Scala methods don’t throw exceptions") +} ``` +{% endtab %} +{% tab 'Scala 3' %} +```scala +object SExceptionThrower: + @throws[Exception] + def exceptionThrower = + throw Exception("Idiomatic Scala methods don’t throw exceptions") +``` +{% endtab %} +{% endtabs %} As a result, you’ll need to handle the exception in your Java code. For instance, this code won’t compile because I don’t handle the exception: +{% tabs except-throw-usage %} +{% tab Java %} ```java -// java: won’t compile because the exception isn’t handled +// won’t compile because the exception isn’t handled public class ScalaExceptionsInJava { public static void main(String[] args) { SExceptionThrower.exceptionThrower(); } } ``` +{% endtab %} +{% endtabs %} The compiler gives this error: -```` +````plain [error] ScalaExceptionsInJava: unreported exception java.lang.Exception; must be caught or declared to be thrown [error] SExceptionThrower.exceptionThrower() @@ -303,28 +389,43 @@ This is probably not what you want, because the Java code may not account for th When a Scala method has a varargs parameter and you want to use that method in Java, mark the Scala method with the `@varargs` annotation. For example, the `printAll` method in this Scala class declares a `String*` varargs field: +{% tabs vararg-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.annotation.varargs + +object VarargsPrinter { + @varargs def printAll(args: String*): Unit = args.foreach(println) +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala -// scala import scala.annotation.varargs object VarargsPrinter: - @varargs def printAll(args: String*): Unit = args.foreach(println) + @varargs def printAll(args: String*): Unit = args.foreach(println) ``` +{% endtab %} +{% endtabs %} Because `printAll` is declared with the `@varargs` annotation, it can be called from a Java program with a variable number of parameters, as shown in this example: -```scala -// java +{% tabs vararg-usage %} +{% tab Java %} +```java public class JVarargs { public static void main(String[] args) { VarargsPrinter.printAll("Hello", "world"); } } ``` +{% endtab %} +{% endtabs %} When this code is run, it results in the following output: -```` +````plain Hello world ```` @@ -335,25 +436,43 @@ world In Scala you might want to create a method name using a symbolic character: +{% tabs add-definition %} +{% tab 'Scala 2 and 3' %} ```scala def +(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} + +That method name won’t work well in Java, but what you can do in Scala is provide an “alternate” name for the method with the `targetName` annotation, which will be the name of the method when used from Java: -That method name won’t work well in Java, but what you can do in Scala 3 is provide an “alternate” name for the method---an alias---that will work in Java: +{% tabs add-2-definition class=tabs-scala-version %} +{% tab 'Scala 2' %} +```scala +import scala.annotation.targetName +object Adder { + @targetName("add") def +(a: Int, b: Int) = a + b +} +``` +{% endtab %} +{% tab 'Scala 3' %} ```scala import scala.annotation.targetName -class Adder: +object Adder: @targetName("add") def +(a: Int, b: Int) = a + b ``` +{% endtab %} +{% endtabs %} Now in your Java code you can use the aliased method name `add`: -```scala -var adder = new Adder(); -int x = adder.add(1,1); +{% tabs add-2-usage %} +{% tab Java %} +```java +int x = Adder.add(1,1); System.out.printf("x = %d\n", x); ``` - - +{% endtab %} +{% endtabs %} From 3826700f6224ffbf70870e4f83adc42fffcb19ca Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 17 Jul 2023 16:22:19 +0200 Subject: [PATCH 2/2] address comments --- .../scala3-book/interacting-with-java.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/_overviews/scala3-book/interacting-with-java.md b/_overviews/scala3-book/interacting-with-java.md index 96e10731ca..20eefda406 100644 --- a/_overviews/scala3-book/interacting-with-java.md +++ b/_overviews/scala3-book/interacting-with-java.md @@ -50,7 +50,9 @@ Given this API that returns `java.util.List[String]`: {% tab Java %} ```java public interface Foo { - static java.util.List getStrings(); + static java.util.List getStrings() { + return List.of("a", "b", "c"); + } } ``` {% endtab %} @@ -87,7 +89,7 @@ def testList() = {% endtab %} {% endtabs %} -In the above code `scalaSeq` is a wrapper over `javaList` that adapt's it to Scala's `mutable.Seq` collection. +In the above code `javaList.asScala` creates a wrapper that adapts a `java.util.List` to Scala's `mutable.Seq` collection. ## How to use Java `Optional` in Scala @@ -102,13 +104,15 @@ To demonstrate this, here’s a Java API that returns an `Optional[String]` valu {% tab Java %} ```java public interface Bar { - static java.util.Optional optionalString(); + static java.util.Optional optionalString() { + return Optional.of("hello"); + } } ``` {% endtab %} {% endtabs %} -First import the `scala.jdk.OptionConverters` object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`: +First import all members from the `scala.jdk.OptionConverters` object, and then use the `toScala` method to convert the `Optional` value to a Scala `Option`: {% tabs bar-usage class=tabs-scala-version %} {% tab 'Scala 2' %} @@ -158,7 +162,7 @@ public interface Running { {% endtabs %} you can create a `Dog` class in Scala just as though you were using traits. -All you have to do is implement the `speak` and `wag` methods: +Because `run` has a default implementation, you only need to implement the `speak` and `wag` methods: {% tabs animal-usage class=tabs-scala-version %} {% tab 'Scala 2' %} @@ -191,6 +195,7 @@ def useJavaInterfaceInScala = {% endtab %} {% endtabs %} +Also notice that in Scala, Java methods defined with empty parameter lists can be called either as in Java, `.wag()`, or you can choose to not use parentheses `.wag`. ## How to use Scala collections in Java @@ -202,14 +207,14 @@ For example, suppose that a Scala API returns a `List[String]` like this: {% tab 'Scala 2' %} ```scala object Baz { - val strings: List[String] + val strings: List[String] = List("a", "b", "c") } ``` {% endtab %} {% tab 'Scala 3' %} ```scala object Baz: - val strings: List[String] + val strings: List[String] = List("a", "b", "c") ``` {% endtab %} {% endtabs %} @@ -247,14 +252,14 @@ For example, suppose that a Scala API returns an `Option[String]` like this: {% tab 'Scala 2' %} ```scala object Qux { - val optString: Option[String] + val optString: Option[String] = Option("hello") } ``` {% endtab %} {% tab 'Scala 3' %} ```scala object Qux: - val optString: Option[String] + val optString: Option[String] = Option("hello") ``` {% endtab %} {% endtabs %}