@@ -117,6 +117,134 @@ class Test {
117117```
118118
119119
120- ## Future work
121- - Converters for ` java.util.stream `
122- - [ ` Spliterator ` ] ( https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html ) s for Scala collections
120+ ## Converters from Scala collections to Java 8 Streams
121+
122+ Scala collections gain ` seqStream ` and ` parStream ` as extension methods that produce a Java 8 Stream
123+ running sequentially or in parallel, respectively. These are automatically specialized to a primitive
124+ type if possible. For instance, ` List(1,2).seqStream ` produces an ` IntStream ` . Maps additionally have
125+ ` seqKeyStream ` , ` seqValueStream ` , ` parKeyStream ` , and ` parValueStream ` methods.
126+
127+ Scala collections also gain ` accumulate ` and ` stepper ` methods that produce utility collections that
128+ can be useful when working with Java 8 Streams. ` accumulate ` produces an ` Accumulator ` or its primitive
129+ counterpart (` DoubleAccumulator ` , etc.), which is a low-level collection designed for efficient collection
130+ and dispatching of results to and from Streams. Unlike most collections, it can contain more than
131+ ` Int.MaxValue ` elements. ` stepper ` produces a ` Stepper ` which is a fusion of ` Spliterator ` and ` Iterator ` .
132+ ` Stepper ` s underlie the Scala collections' instances of Java 8 Streams.
133+
134+ Java 8 Streams gain ` toScala[Coll] ` and ` accumulate ` methods, to make it easy to produce Scala collections
135+ or Accumulators, respectively, from Java 8 Streams. For instance, ` myStream.to[Vector] ` will collect the
136+ contents of a Stream into a ` scala.collection.immutable.Vector ` . Note that standard sequential builders
137+ are used for collections, so this is best done to gather the results of an expensive computation.
138+
139+ Finally, there is a Java class, ` ScalaStreamer ` , that has a series of ` from ` methods that can be used to
140+ obtain Java 8 Streams from Scala collections from within Java.
141+
142+ #### Performance Considerations
143+
144+ For sequential operations, Scala's ` iterator ` almost always equals or exceeds the performance of a Java 8 stream. Thus,
145+ one should favor ` iterator ` (and its richer set of operations) over ` seqStream ` for general use. However, long
146+ chains of processing of primitive types can sometimes benefit from the manually specialized methods in ` DoubleStream ` ,
147+ ` IntStream ` , and ` LongStream ` .
148+
149+ Note that although ` iterator ` typically has superior performance in a sequential context, the advantage is modest
150+ (usually less than 50% higher throughput for ` iterator ` ).
151+
152+ For parallel operations, ` parStream ` and even ` seqStream.parallel ` meets or exceeds the performance of Scala parallel
153+ collections methods (invoked with ` .par ` ). Especially for small collections, the difference can be substantial. In
154+ some cases, when a Scala (parallel) collection is the ultimate result, Scala parallel collections can have an advantage
155+ as the collection can (in some cases) be built in parallel.
156+
157+ Because the wrappers are invoked based on the static type of the collection, there are also cases where parallelization
158+ is inefficient when interfacing with Java 8 Streams (e.g. when a collection is typed as ` Seq[String] ` so might have linear
159+ access like ` List ` , but actually is a ` WrappedArray[String] ` that can be efficiently parallelized) but can be efficient
160+ with Scala parallel collections. The ` parStream ` method is only available when the static type is known to be compatible
161+ with rapid parallel operation; ` seqStream ` can be parallelized by using ` .parallel ` , but may or may not be efficient.
162+
163+ If the operations available on Java 8 Streams are sufficient, the collection type is known statically with enough precision
164+ to enable parStream, and an ` Accumulator ` or non-collection type is an acceptable result, Java 8 Streams will essentially
165+ always outperform the Scala parallel collections.
166+
167+ #### Scala Usage Example
168+
169+ ``` scala
170+ import scala .compat .java8 .StreamConverters ._
171+
172+ object Test {
173+ val m = collection.immutable.HashMap (" fish" -> 2 , " bird" -> 4 )
174+ val s = m.parValueStream.sum // 6, potientially computed in parallel
175+ val t = m.seqKeyStream.toScala[List ] // List("fish", "bird")
176+ val a = m.accumulate // Accumulator[(String, Int)]
177+
178+ val n = a.stepper.fold(0 )(_ + _._1.length) +
179+ a.parStream.count // 8 + 2 = 10
180+
181+ val b = java.util.Arrays .stream(Array (2L , 3L , 4L )).
182+ accumulate // LongAccumulator
183+ val l = b.to[List ] // List(2L, 3L, 4L)
184+ }
185+ ```
186+
187+ #### Using Java 8 Streams with Scala Function Converters
188+
189+ Scala can emit Java SAMs for lambda expressions that are arguments to methods that take a Java SAM rather than
190+ a Scala Function. However, it can be convenient to restrict the SAM interface to interactions with Java code
191+ (including Java 8 Streams) rather than having it propagate throughout Scala code.
192+
193+ Using Java 8 Stream converters together with function converters allows one to accomplish this with only a modest
194+ amount of fuss.
195+
196+ Example:
197+
198+ ``` scala
199+ import scala .compat .java8 .FunctionConverters ._
200+ import scala .compat .java8 .StreamConverters ._
201+
202+ def mapToSortedString [A ](xs : Vector [A ], f : A => String , sep : String ) =
203+ xs.parStream. // Creates java.util.stream.Stream[String]
204+ map[String ](f.asJava).sorted. // Maps A to String and sorts (in parallel)
205+ toArray.mkString(sep) // Back to an Array to use Scala's mkString
206+ ```
207+
208+ Note that explicit creation of a new lambda will tend to lead to improved type inference and at least equal
209+ performance:
210+
211+ ``` scala
212+ def mapToSortedString [A ](xs : Vector [A ], f : A => String , sep : String ) =
213+ xs.parStream.
214+ map[String ](a => f(a)).sorted. // Explicit lambda creates a SAM wrapper for f
215+ toArray.mkString(sep)
216+ ```
217+
218+ #### Java Usage Example
219+
220+ To convert a Scala collection to a Java 8 Stream from within Java, it usually
221+ suffices to call ` ScalaStreaming.from(xs) ` on your collection ` xs ` . If ` xs ` is
222+ a map, you may wish to get the keys or values alone by using ` fromKeys ` or
223+ ` fromValues ` . If the collection has an underlying representation that is not
224+ efficiently parallelized (e.g. ` scala.collection.immutable.List ` ), then
225+ ` fromAccumulated ` (and ` fromAccumulatedKeys ` and ` fromAccumulatedValues ` ) will
226+ first gather the collection into an ` Accumulator ` and then return a stream over
227+ that accumulator. If not running in parallel, ` from ` is preferable (faster and
228+ less memory usage).
229+
230+ Note that a Scala ` Iterator ` cannot fulfill the contract of a Java 8 Stream
231+ (because it cannot support ` trySplit ` if it is called). Presently, one must
232+ call ` fromAccumulated ` on the ` Iterator ` to cache it, even if the Stream will
233+ be evaluated sequentially, or wrap it as a Java Iterator and use static
234+ methods in ` Spliterator ` to wrap that as a ` Spliterator ` and then a ` Stream ` .
235+
236+ Here is an example of conversion of a Scala collection within Java 8:
237+
238+ ``` java
239+ import scala.collection.mutable.ArrayBuffer ;
240+ import scala.compat.java8.ScalaStreaming ;
241+
242+ public class StreamConvertersExample {
243+ public int MakeAndUseArrayBuffer () {
244+ ArrayBuffer<String > ab = new ArrayBuffer<String > ();
245+ ab. $plus$eq(" salmon" );
246+ ab. $plus$eq(" herring" );
247+ return ScalaStreaming . from(ab). mapToInt(x - > x. length()). sum(); // 6+7 = 13
248+ }
249+ }
250+ ```
0 commit comments