Published 2014-03-10.
Last modified 2019-07-20.
Time to read: 6 minutes.
This lecture concludes the material begun in the previous lecture, and covers DurationConverters
, FunctionConverters
and FutureConverters
.
DurationConverters
It is easy to convert between Scala and Java durations, and back again.
To do this, of course, we must import the implicit conversions defined in the DurationConverters
object.
scala> scala> import scala.jdk.DurationConverters._ import scala.jdk.DurationConverters._
scala> scala> java.time.Duration.oftab of ofDays ofHours ofMillis ofMinutes ofNanos ofSeconds
scala> scala> val duration = java.time.Duration.ofSeconds(1) duration: java.time.Duration = PT1S
scala> scala> import scala.jdk.DurationConverters._ import scala.jdk.DurationConverters._
scala> scala> duration.toScala res7: scala.concurrent.duration.FiniteDuration = 1 second
scala> scala> duration.toScala.toJava res8: java.time.Duration = PT1S
Scala supports a nicer syntax for durations, if postfixOps
is enabled and the implicit conversions defined in the concurrent.duration
package are imported.
scala> scala> import scala.language.postfixOps import scala.language.postfixOps
scala> scala> import scala.concurrent.duration._ import scala.concurrent.duration._
scala> scala> val duration = 100 millis duration: scala.concurrent.duration.FiniteDuration = 100 milliseconds
We can convert any Java duration back to Scala.
scala> scala> duration.toJava res1: java.time.Duration = PT0.1S
scala> scala> duration.toJava.toScala res2: scala.concurrent.duration.FiniteDuration = 100000000 nanoseconds
Scala provides methods like toMillis
that obtain the scalar quantity from a duration.
scala> scala> duration.toJava.totab toDays toHours toMillis toMinutes toNanos toScala toSecondsPart toDaysPart toHoursPart toMillisPart toMinutesPart toNanosPart toSeconds toString
scala> scala> duration.toJava.toMillis res11: Long = 100
Java does not support infinite durations, so Scala's infinite duration cannot be converted to Java.
scala> scala> Duration.Inf.toJava ^ error: value toJava is not a member of scala.concurrent.duration.Duration.Infinite
FunctionConverters
Scala/Java interop is a big topic, especially the aspects related to FunctionConverters
.
During the time of Java 6 and 7, Scala 2.9 and 2.10 we had an entire course on it.
The topic became a lot more complex with the advent of Java 8, and when Scala 2.13 came out it subsumed the scala-java-interop
package.
Happily, the versions of Java up to Java 12 do not change the Java side of the equation much.
Unfortunately, the official documentation pertaining to Scala/Java interop is currently out of date, the new features have not been properly documented, and most available code examples no longer work or are no longer recommended practice.
This section cannot cover all that material, and so Scala/Java interop is out of scope for this course.
Asking "why" about the material presented in this section is like pulling on a thread from a sweater ...
the thread just keeps getting longer.
Scala 3, due early in 2020, will change the interop API yet again, rendering this lecture out of date in just a few months.
For those reasons, we won't answer questions on FunctionConverters
except to fix errors.
As you know, this is a Scala course, not a Java course, so Java is not explained here and we won't answer questions about Java.
Please let us know if Scala-Java interop is of interest, and if there is sufficient demand we'll initiate a research project that results in updated, usable material in a new course dedicated to that topic.
In the event that we republish a Scala-Java interop course, we would address and support both languages, and could provide information on interop from any other language that runs on the JVM.
The Scaladoc for FunctionConverters
tells part of the story.
I include it here, with a complete REPL transcript and some extra code and commentary.
This code creates a lambda expression called f
.
We discussed lambda functions in the More Fun With Functions and Lambda Review & Drill lectures of the Introduction to Scala course.
The lambda function is then converted to a java.util.function.IntUnaryOperator
by calling the asJava
method.
This is not a Java course.
If you need to get Java/Scala interoperating, you need to know Java and Scala.
This course only teaches Scala.
Scala REPLscala> scala> import scala.jdk.FunctionConverters._ import scala.jdk.FunctionConverters._
scala> scala> val f = (x: Int) => x + 1 f: Int => Int = $$Lambda$986/0x00000008405df040@6cf50207
scala> scala> val jf1 = f.asJava jf1: java.util.function.IntUnaryOperator = AsJavaIntUnaryOperator($$Lambda$986/0x00000008405df040@6cf50207)
A round trip yields the original function.
scala> scala> val jf1 = f.asJava.asScala jf1: Int => Int = $$Lambda$986/0x00000008405df040@6cf50207
More generic Java function types can be created using the corresponding
asJavaXYZ
extension method.Scala REPLscala> scala> val jf2 = f.asJavaFunction jf2: java.util.function.Function[Int,Int] = AsJavaFunction($$Lambda$986/0x00000008405df040@6cf50207)
Again, round trips yield the original function.
scala> scala> val jf2 = f.asJavaFunction.asScala jf2: Int => Int = $$Lambda$986/0x00000008405df040@6cf50207
Scala REPLscala> scala> val jf3 = f.asJavaUnaryOperator jf3: java.util.function.UnaryOperator[Int] = AsJavaUnaryOperator($$Lambda$986/0x00000008405df040@6cf50207Converting a Java function to Scala is done using the
asScala
extension method.Scala REPLscala> scala> List(1,2,3).map(jf2.asScala) res1: List[Int] = List(2, 3, 4)
Now the problem I alluded to earlier: both of these imports define methods called asJava
and asScala
, but the definitions conflict.
Here is an example; this Scala method, which splits an incoming Java List[Int]
into even and odd lists, is meant to be called from Java.
The yellow portion creates a Scala tuple containing two Java List[Int]
s by calling asJava
.
That asJava
method is defined by importing CollectionConverters
.
The orange portion converts the Scala lambda function into a Java function.Function[JList[Int], (JList[Int], JList[Int])]
- or it would, if the problem I am about to show you did not arise.
import java.util.{function, List => JList} import scala.collection._ import scala.jdk.CollectionConverters._ import scala.jdk.FunctionConverters._
val intoEvenOdd: JList[Int] => (JList[Int], JList[Int]) = { javaList: JList[Int] => javaList.asScala.partition(_ % 2 == 0) match { case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) } }.asJava
The error message is.
Error:(66, 72) type mismatch; found : even.type (with underlying type scala.collection.mutable.Buffer[Int]) required: ?{def asJava: ?} Note that implicit conversions are not applicable because they are ambiguous: both method BufferHasAsJava in trait AsJavaExtensions of type [A](b: scala.collection.mutable.Buffer[A])jdk.CollectionConverters.BufferHasAsJava[A] and method enrichAsJavaIntUnaryOperator in trait Priority0FunctionExtensions of type [A0](sf: A0 => Int)(implicit evA0: A0 =:= Int)scala.jdk.FunctionWrappers.RichFunction1AsIntUnaryOperator are possible conversion functions from even.type to ?{def asJava: ?} case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) Error:(66, 77) value asJava is not a member of scala.collection.mutable.Buffer[Int] case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) Error:(66, 85) type mismatch; found : odd.type (with underlying type scala.collection.mutable.Buffer[Int]) required: ?{def asJava: ?}
both method BufferHasAsJava in trait AsJavaExtensions of type [A](b: scala.collection.mutable.Buffer[A])jdk.CollectionConverters.BufferHasAsJava[A] and method enrichAsJavaIntUnaryOperator in trait Priority0FunctionExtensions of type [A0](sf: A0 => Int)(implicit evA0: A0 =:= Int)scala.jdk.FunctionWrappers.RichFunction1AsIntUnaryOperator are possible conversion functions from odd.type to ?{def asJava: ?} case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) Error:(66, 89) value asJava is not a member of scala.collection.mutable.Buffer[Int] case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) Error:(64, 84) type mismatch; found : java.util.List[Int] => <error> required: ?{def asJava: ?} Note that implicit conversions are not applicable because they are ambiguous: both method enrichAsJavaConsumer in trait Priority1FunctionExtensions of type [T](sf: T => Unit)scala.jdk.FunctionWrappers.RichFunction1AsConsumer[T] and method enrichAsJavaPredicate in trait Priority1FunctionExtensions of type [T](sf: T => Boolean)scala.jdk.FunctionWrappers.RichFunction1AsPredicate[T] are possible conversion functions from java.util.List[Int] => <error> to ?{def asJava: ?} val intoEvenOdd: JList[Int] => (JList[Int], JList[Int]) = { javaList: JList[Int] =>
The solution is to avoid having both imports in scope at the same time.
object FunctionConverterFun extends App { import java.util.{function, List => JList} import scala.collection._
val intoEvenOdd: JList[Int] => (JList[Int], JList[Int]) = { import scala.jdk.CollectionConverters._ javaList: JList[Int] => javaList.asScala.partition(_ % 2 == 0) match { case (even: mutable.Buffer[Int], odd: mutable.Buffer[Int]) => (even.asJava, odd.asJava) } }
val intoEvenOddForJava: function.Function[JList[Int], (JList[Int], JList[Int])] = { import scala.jdk.FunctionConverters._ intoEvenOdd.asJava }
val jList: java.util.List[Int] = { import scala.jdk.CollectionConverters._ (1 to 10).asJava } println(intoEvenOddForJava(jList)) }
Calling Scala from Java
When calling Scala code from Java, Scala objects (which are implemented as singleton classes) must be accessed by appending a $
(dollar sign) to the name of the class, followed by the word MODULE$
.
Given a Scala object called FunctionConverterFromJava
in package collection
with a method intoEvenOddForJava
, you would access it from Java by writing collection.FunctionConverterFromJava$.MODULE$
.intoEvenOddForJava()
.
You would invoke the Java Function
and pass in parameters with the apply
method like this.
collection.FunctionConverterFromJava$.MODULE$.intoEvenOddForJava().apply(list1)
Breaking the above down.
- The Scala package is called
collection
- The Scala
object
is calledFunctionConverterFromJava
, and it is accessed from Java asFunctionConverterFromJava$.MODULE$
- The function is called
intoEvenOddForJava
, and it is dereferenced by adding parentheses after its name:intoEvenOddForJava()
- The function is invoked by calling
apply()
and passing parameters to the function. Java 8 introduced theapply
method for functions, and it was inspired by Scala'sapply
method. We discussed theapply
method in the Objects lecture of the Introduction to Scala course.
Here is a portion of the Java program that invokes the above Scala code.
Notice that Scala tuples can be used in Java, here we see a scala.Tuple2
in action..
import collections.FunctionConverterFromJava$; import scala.Tuple2; import java.util.Arrays; import java.util.List;
public class FunctionConverterFun { public static void main(String[] args) { List<Object> list1 = Arrays.asList(1, 2); Tuple2<List<Object>, List<Object>> list2 = FunctionConverterFromJava$.MODULE$.intoEvenOddForJava1().apply(list1); System.out.println("list2 = " + list2); } }
Notice that FunctionConverters
as written requires the input collection to be declared as java.util.List<Object>
instead of java.util.List<Integer>
, and the returned collections must be declared as java.util.List<Object>
, which is annoying.
We can return the desired type by declaring the types to be java.util.List<java.lang.Integer>
.
The Scala code for that looks like this.
import java.util.{function, List => JList} import java.lang.{Integer => JInt}
val intoEvenOddForJava2: function.Function[JList[JInt], (JList[JInt], JList[JInt])] = { import scala.jdk.FunctionConverters._ intoEvenOdd2.asJava }
and the Java code which invokes the Scala code looks like this.
List<Integer> list3 = Arrays.asList(1, 2); Tuple2<List<Integer>, List<Integer>> list4 = FunctionConverterFromJava$.MODULE$.intoEvenOddForJava2().apply(list3); System.out.println("list2 = " + list4);
This complete example, which includes more methods demonstrating interoperability between Java and Scala is provided in FunctionConverterFun.java
and ConverterFun.scala
.
Clearly this behavior is due to type erasure.
Here is a StackOverflow answer that discusses a very similar issue.
Explicitly declaring the types of the items in Scala collections to be Java primitive types makes the interop work.
Since that approach works for scala.Int
/java.lang.Integer
, it is reasonable to assume similar issues and solutions for interoperability between Java and Scala for collections of scala.String
/java.lang.String
, collections of scala.Long/java.lang.Long
, etc.
FutureConverters
This is not a course on Java, so if you are unfamiliar with Java Future
s you should find another source of information to bring you up to speed before you attempt to make sense of the material presented here.
Quick Historical Recap
The Java Future
interface was introduced in Java 5 to support asynchronous computation, but it did not have any methods for combining these computations or handling errors.
The CompletableFuture
class was introduced in Java 8.
It implemented the Future
interface and added the CompletionStage
interface, which defines the contract for an asynchronous computation step that can be combined with other steps.
CompletableFuture
has about 50 different methods for composing, combining, executing asynchronous computation steps and error handling.
Akka 2.0 introduced its an implementation of Future during Scala 2.9's reign, then it was migrated and updated for Scala 2.10.
I (Mike Slinn) published a book in 2012 entitled "Composable Futures with Akka 2.0 - Featuring Java, Scala and Akka Code Examples".
Small improvements to Future
have been made for each successive release of Scala.
For the Present State See the Scaladoc
The Scaladoc has a good overview of FutureConverters
.
This object provides extension methods that convert between Scala
Future
and JavaCompletionStage
.When writing Java code, use the explicit conversion methods defined in
javaapi.FutureConverters
instead.Note that the bridge is implemented at the read-only side of asynchronous handles, namely
Future
(instead ofscala.concurrent.Promise
) andCompletionStage
(instead ofjava.util.concurrent.CompletableFuture
). This is intentional, as the semantics of bridging the write-handles would be prone to race conditions; if both ends (CompletableFuture
andPromise
) are completed independently at the same time, they may contain different values afterwards. For this reason,toCompletableFuture
is not supported on the createdCompletionStages
.
Scala Future to Java CompletionStage
Before we can work with Scala Future
s, we need an ExecutionContext
in scope.
This course introduced ExecutionContext
in the MultiThreading lecture.
scala> scala> import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
Let's compute 1+2 in a Scala Future
.
Scala Future
s was discussed extensively in this course over several lectures, starting with the Futures & Promises lecture.
scala> scala> import scala.jdk.FutureConverters._ import scala.jdk.FutureConverters._
scala> scala> scala.concurrent.Future(1+2).asJava res1: java.util.concurrent.CompletionStage[Int] = scala.concurrent.impl.FutureConvertersImpl$CF@186a08b9[Not completed]
Look at all the methods that Java provides for CompletionStage
.
scala> scala> scala.concurrent.Future(1+2).asJava.tab acceptEither asScala runAfterBoth thenAccept thenApply thenCompose toCompletableFuture acceptEitherAsync exceptionally runAfterBothAsync thenAcceptAsync thenApplyAsync thenComposeAsync whenComplete applyToEither handle runAfterEither thenAcceptBoth thenCombine thenRun whenCompleteAsync applyToEitherAsync handleAsync runAfterEitherAsync thenAcceptBothAsync thenCombineAsync thenRunAsync
scala> scala> scala.concurrent.Future(1+2).asJava.tabtab != acceptEitherAsync eq handleAsync runAfterBoth thenAcceptAsync thenCombineAsync toString ## applyToEither equals hashCode runAfterBothAsync thenAcceptBoth thenCompose wait + applyToEitherAsync exceptionally isInstanceOf runAfterEither thenAcceptBothAsync thenComposeAsync whenComplete -> asInstanceOf formatted ne runAfterEitherAsync thenApply thenRun whenCompleteAsync == asScala getClass notify synchronized thenApplyAsync thenRunAsync → acceptEither ensuring handle notifyAll thenAccept thenCombine toCompletableFuture
Java CompletableFuture to Scala Future
Before we use these converters, note that Java's CompletableFuture<T>
is an implementation of two interfaces in the java.util.concurrent
package: Future<T>
and
.
Thus a CompletionStage<T>
CompleteableFuture
can be provided any place that a CompletionStage
or a Java Future
is required.
Here is some Java code that creates a Java CompletableFuture<T>
.
The Java CompletableFuture
is converted to a Scala Future
.
I don't do anything with the Scala Future
, which is not helpful, I admit.
The real purpose of this code example is to set you up for the next code example.
This code is provided in the git repository as FutureCallable.java
.
import java.util.concurrent.*; import scala.jdk.javaapi.FutureConverters;
public class FutureCallable { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = new CompletableFuture<>(); scala.concurrent.Future<String> scalaFuture = FutureConverters.asScala(future);
System.out.println("Do something else while callable executes");
future.complete("All done!");
System.out.println("Retrieve the result of the future"); String result = future.get(); // Future.get() blocks until the result is available System.out.println(result);
executorService.shutdown(); } }
Here is a more useful example of how to work with Java Futures in Scala.
This code is provided as FutureConverterFun.scala
.
Note how the Scala ExecutionContext
is derived from the Java ExecutorService
.
Also note that the Scala andThen
callback is used to pick up the result of the Java CompletableFuture
.
We discussed these techniques in the Getting Results from Futures & Signaling with Promises lecture.
You could also use Await.result
to obtain the value.
package multi.futures
import java.util.concurrent.{CompletableFuture, ExecutorService, Executors} import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} import scala.jdk.javaapi.FutureConverters import scala.util.{Failure, Success}
class FutureConverterFun { val executorService: ExecutorService = Executors.newFixedThreadPool(10) implicit val ec: ExecutionContextExecutor = ExecutionContext.fromExecutor(executorService)
val future = new CompletableFuture[String] val scalaFuture: Future[String] = FutureConverters.asScala(future)
scalaFuture.andThen { case Success(result) => println(s"Success: $result") case Failure(exception) => println(s"Failure: ${ exception.getMessage }") }.andThen { case _ => System.exit(0) } future.complete("All done!") synchronized { wait() } }
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.