简体   繁体   中英

Signature of whenComplete from CompletableFuture

Does anyone know to translate the whenComplete method of java CompletableFuture to Scala? I really don't know how to do it and I'm kinda' stuck. Thank you

Here's an example that shows you how it might work (inside the scala REPL)...

$ scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_181).
Type in expressions for evaluation. Or try :help.

scala> import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletableFuture

Create a future that waits for 5 seconds, then completes returning a string value. (See also the note below explaining how this works.)

scala> val future = CompletableFuture.supplyAsync {() =>
     |   Thread.sleep(5000) // Wait 5,000 ms (5 seconds).
     |   "We done did it!"
     | }
future: java.util.concurrent.CompletableFuture[String] = java.util.concurrent.CompletableFuture@5a466dd[Not completed]

Now have some code execute when the future has finished. (This is where you should begin with your own whenComplete implementation.)

scala> future.whenComplete {(result, error) =>
     |   println(s"Result was: '$result', error was: '$error'")
     | }
Result was 'We done did it!'; error was 'null'
res0: java.util.concurrent.CompletableFuture[String] = java.util.concurrent.CompletableFuture@3ea9a091[Completed normally]

(Note that, in this case, the future completed before I was able to type in the whenComplete method. This is clearly a race condition, so if you paste the whole lot into the REPL at once, you may see that res0 is determined to have "Not completed", and then see the output of the whenComplete function.)

So what's going on here, because this code doesn't look much like the JavaDoc for the associated classes?

It's a little Scala magic called single abstract methods . Essentially, if an asbtract class (or trait) has a single abstract method, then you can replace an instance of that class with the definition of the abstract method. Further, Scala knows which class is relevant from the argument list of the associated function.

Let's start with CompletableFuture.supplyAsync , which takes a single Supplier[T] argument. In Scala terms, this type looks like this:

trait Supplier[T] {
  def get(): T
}

So we could have written the creation of the future element as follows instead:

scala> import java.util.function.Supplier
import java.util.function.Supplier

scala> val future = CompletableFuture.supplyAsync {
     |   new Supplier[String] {
     |     override def get(): String = {
     |       Thread.sleep(5000) // Wait 5,000 ms (5 seconds).
     |       "We done did it!"
     |     }
     |   }
     | }
future: java.util.concurrent.CompletableFuture[String] = java.util.concurrent.CompletableFuture@35becbd4[Not completed]

Because the Scala compiler knows that supplyAsync takes a Supplier[T] , and because Supplier[T] has a single abstract method , get , the compiler is able to accept the abbreviated form that uses a function literal as the definition of both Supplier and its get method.

We then use the same approach with the whenComplete method. Here, the type of argument is a BiConsumer[T, U] (where T is the type of value returned by the future, and U is an exception type), which takes a single abstract method accept . (This type also has an andThen method, but that's not abstract, so it doesn't matter to Scala .) So, to have been more explicit, we could have written the following:

scala> import java.util.function.BiConsumer

scala> future.whenComplete {
     |   new BiConsumer[String, Throwable] {
     |     override def accept(result: String, error: Throwable): Unit = {
     |       println(s"Result was: '$result', error was: '$error'")
     |     }
     |   }
     | }
Result was 'We done did it!'; error was 'null'
res0: java.util.concurrent.CompletableFuture[String] = java.util.concurrent.CompletableFuture@3ea9a091[Completed normally]

Both approaches are valid, so feel free to use whichever one makes sense to you...

Note that whenComplete is a lot uglier than conventional Scala code typically requires: If the future threw an exception instead of successfully finishing, then error will be non- null ; otherwise, result will contain the result of the future, which could also be null .

If at all possible, I would strongly recommend using Scala Future s instead. Far more functional, and far more elegant than those in Java .

You can convert a Java CompletableFuture into a Scala Future with the following implicit conversion:

import scala.concurrent.{Future, Promise}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.language.implicitConversion
import scala.util.{Failure, Success}

implicit def completableFutureToFuture[T](cf: CompletableFuture[T]): Future[T] = {
  val p = Promise[T]() // Promise monitoring result of java cf.

  // Handle completion of Java future.
  cf.whenComplete {(result, error) =>

    // If error is not null, then future failed.
    if(error ne null) p.failure(error)

    // Otherwise, it succeeded with result.
    else p.success(result)
  }

  // Return the Scala future associated with the promise.
  p.future
}

You can then handle completion of the Java future far more elegantly (again, in the REPL, with the above defined):

scala> val scalaFuture = future // Implicit conversion called.
scalaFuture: scala.concurrent.Future[String] = Future(Success(We done did it!))

scala> scalaF.onComplete {
     |   case Success(s) => println(s"Succeeded with '$s'")
     |   case Failure(e) => println(s"Failed with '$e'...")
     | }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM