TeeKai
TeeKai

Reputation: 691

How to convert a Java Stream to a Scala Stream?

As a part of an effort of converting Java code to Scala code, I need to convert the Java stream Files.walk(Paths.get(ROOT)) to Scala. I can't find a solution by googling. asScala won't do it. Any hints?

Here is the related code:

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;

// ...snip...

Files.walk(Paths.get(ROOT))
     .filter(path -> !path.equals(Paths.get(ROOT)))
     .map(path -> Paths.get(ROOT).relativize(path))
     .map(path -> linkTo(methodOn(FileUploadController.class).getFile(path.toString())).withRel(path.toString()))
     .collect(Collectors.toList()))

The Files.walk(Paths.get(ROOT)) return type is Stream<Path> in Java.

Upvotes: 39

Views: 25494

Answers (3)

Xavier Guihot
Xavier Guihot

Reputation: 61666

Starting Scala 2.13, the standard library includes scala.jdk.StreamConverters which provides Java to Scala implicit stream conversions:

import scala.jdk.StreamConverters._

val javaStream = Files.walk(Paths.get("."))
// javaStream: java.util.stream.Stream[java.nio.file.Path] = java.util.stream.ReferencePipeline$3@51b1d486
javaStream.toScala(LazyList)
// scala.collection.immutable.LazyList[java.nio.file.Path] = LazyList(?)
javaStream.toScala(Iterator)
// Iterator[java.nio.file.Path] = <iterator>

Note the usage of LazyList (as opposed to Stream) as Streams are deprecated in Scala 2.13. LazyList is the supported replacement type.

Upvotes: 32

mirosval
mirosval

Reputation: 6822

There is a slightly nicer way without needing the compat layer or experimental 2.11 features mentioned here by @marcospereira

Basically just use an iterator:

import java.nio.file.{Files, Paths}
import scala.collection.JavaConverters._

Files.list(Paths.get(".")).iterator().asScala

Upvotes: 48

knutwalker
knutwalker

Reputation: 5974

Java 8 Streams and Scala Streams are conceptually different things; the Java 8 Stream is not a collection, so the usual collection converter won't work. You can use the scala-java8-compat (github) library to add a toScala method to Java Streams:

import scala.compat.java8.StreamConverters._
import java.nio.file.{ Files, Path, Paths }

val scalaStream: Stream[Path] = Files.walk(Paths.get(".")).toScala[Stream]

You can't really use this conversion (Java->Scala) from Java, so if you have to do this from Java, it's easier (but still awkward) to just run the stream and build the Scala Stream yourself (which is what the aforementioned library is doing under the hood):

import scala.collection.immutable.Stream$;
import scala.collection.mutable.Builder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

final Stream<Path> stream = Files.walk(Paths.get("."));
final Builder<Path, scala.collection.immutable.Stream<Path>> builder = Stream$.MODULE$.newBuilder();
stream.forEachOrdered(builder::$plus$eq);
final scala.collection.immutable.Stream<Path> result = builder.result();

However, both ways will fully consume the Java Stream, so you don't get the benefit of the lazy evaluation by converting it to a Scala Stream and might as well just convert it directly to a Vector. If you just want to use the Scala function literal syntax, there different ways to achieve this. You could use the same library to use function converters, similar to collection converters:

import scala.compat.java8.FunctionConverters._
import java.nio.file.{ Files, Path, Paths }

val p: Path => Boolean = p => Files.isExecutable(p)
val stream: java.util.stream.Stream[Path] = Files.walk(Paths.get(".")).filter(p.asJava)

Alternatively since 2.11, Scala has experimental support for SAM types under the -Xexperimental flag. This will be non-experimental without a flag in 2.12.

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

scala> import java.nio.file.{ Files, Path, Paths }
import java.nio.file.{Files, Path, Paths}

scala> Files.walk(Paths.get(".")).filter(p => Files.isExecutable(p))
<console>:13: error: missing parameter type
       Files.walk(Paths.get(".")).filter(p => Files.isExecutable(p))
                                         ^

scala> :set -Xexperimental

scala> Files.walk(Paths.get(".")).filter(p => Files.isExecutable(p))
res1: java.util.stream.Stream[java.nio.file.Path] = java.util.stream.ReferencePipeline$2@589838eb

scala> Files.walk(Paths.get(".")).filter(Files.isExecutable)
res2: java.util.stream.Stream[java.nio.file.Path] = java.util.stream.ReferencePipeline$2@185d8b6

Upvotes: 15

Related Questions