What Java 8 Stream.Collect Equivalents Are Available in the Standard Kotlin Library

What Java 8 Stream.collect equivalents are available in the standard Kotlin library?

There are functions in the Kotlin stdlib for average, count, distinct,filtering, finding, grouping, joining, mapping, min, max, partitioning, slicing, sorting, summing, to/from arrays, to/from lists, to/from maps, union, co-iteration, all the functional paradigms, and more. So you can use those to create little 1-liners and there is no need to use the more complicated syntax of Java 8.

I think the only thing missing from the built-in Java 8 Collectors class is summarization (but in another answer to this question is a simple solution).

One thing missing from both is batching by count, which is seen in another Stack Overflow answer and has a simple answer as well. Another interesting case is this one also from Stack Overflow: Idiomatic way to spilt sequence into three lists using Kotlin. And if you want to create something like Stream.collect for another purpose, see Custom Stream.collect in Kotlin

EDIT 11.08.2017: Chunked/windowed collection operations were added in kotlin 1.2 M2, see https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/


It is always good to explore the API Reference for kotlin.collections as a whole before creating new functions that might already exist there.

Here are some conversions from Java 8 Stream.collect examples to the equivalent in Kotlin:

Accumulate names into a List

// Java:  
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// Kotlin:
val list = people.map { it.name } // toList() not needed

Convert elements to strings and concatenate them, separated by commas

// Java:
String joined = things.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
// Kotlin:
val joined = things.joinToString(", ")

Compute sum of salaries of employee

// Java:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val total = employees.sumBy { it.salary }

Group employees by department

// Java:
Map<Department, List<Employee>> byDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin:
val byDept = employees.groupBy { it.department }

Compute sum of salaries by department

// Java:
Map<Department, Integer> totalByDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
// Kotlin:
val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

Partition students into passing and failing

// Java:
Map<Boolean, List<Student>> passingFailing =
students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
// Kotlin:
val passingFailing = students.partition { it.grade >= PASS_THRESHOLD }

Names of male members

// Java:
List<String> namesOfMaleMembers = roster
.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(p -> p.getName())
.collect(Collectors.toList());
// Kotlin:
val namesOfMaleMembers = roster.filter { it.gender == Person.Sex.MALE }.map { it.name }

Group names of members in roster by gender

// Java:
Map<Person.Sex, List<String>> namesByGender =
roster.stream().collect(
Collectors.groupingBy(
Person::getGender,
Collectors.mapping(
Person::getName,
Collectors.toList())));
// Kotlin:
val namesByGender = roster.groupBy { it.gender }.mapValues { it.value.map { it.name } }

Filter a list to another list

// Java:
List<String> filtered = items.stream()
.filter( item -> item.startsWith("o") )
.collect(Collectors.toList());
// Kotlin:
val filtered = items.filter { it.startsWith('o') }

Finding shortest string a list

// Java:
String shortest = items.stream()
.min(Comparator.comparing(item -> item.length()))
.get();
// Kotlin:
val shortest = items.minBy { it.length }

Counting items in a list after filter is applied

// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();
// Kotlin:
val count = items.filter { it.startsWith('t') }.size
// but better to not filter, but count with a predicate
val count = items.count { it.startsWith('t') }

and on it goes... In all cases, no special fold, reduce, or other functionality was required to mimic Stream.collect. If you have further use cases, add them in comments and we can see!

About laziness

If you want to lazy process a chain, you can convert to a Sequence using asSequence() before the chain. At the end of the chain of functions, you usually end up with a Sequence as well. Then you can use toList(), toSet(), toMap() or some other function to materialize the Sequence at the end.

// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()

// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()

Why are there no Types?!?

You will notice the Kotlin examples do not specify the types. This is because Kotlin has full type inference and is completely type safe at compile time. More so than Java because it also has nullable types and can help prevent the dreaded NPE. So this in Kotlin:

val someList = people.filter { it.age <= 30 }.map { it.name }

is the same as:

val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }

Because Kotlin knows what people is, and that people.age is Int therefore the filter expression only allows comparison to an Int, and that people.name is a String therefore the map step produces a List<String> (readonly List of String).

Now, if people were possibly null, as-in a List<People>? then:

val someList = people?.filter { it.age <= 30 }?.map { it.name }

Returns a List<String>? that would need to be null checked (or use one of the other Kotlin operators for nullable values, see this Kotlin idiomatic way to deal with nullable values and also Idiomatic way of handling nullable or empty list in Kotlin)

See also:

  • API Reference for extension functions for Iterable
  • API reference for extension functions for Array
  • API reference for extension functions for List
  • API reference for extension functions to Map

What is the Kotlin equivalent of Java Stream.collect?

For scenarios not covered by built in operations toList() etc, you can use the fact that collect is just a fold. So given

val list: List<Pair<String, Int>> = listOf("Ann" to 19, "John" to  23)

you can collect to a collection of your choice with a fold

val map: Map<String, Int> = list.fold(HashMap(), { accumulator, item ->
accumulator.put(item.first, item.second); accumulator})

If you then define an extension function

fun <T, R> Iterable<T>.collectTo(accumulator: R, accumulation: (R, T) -> Unit) = 
this.fold(accumulator, { accumulator, item -> accumulation(accumulator, item); accumulator } )

you can further simplify

val map2: Map<String, Int> = list.collectTo(HashMap(), { accumulator, item -> 
accumulator.put(item.first, item.second) })

Although in this case of course you could just use the .toMap extension function.

How can I call collect(Collectors.toList()) on a Java 8 Stream in Kotlin?

UPDATE: This issue is now fixed in Kotlin 1.0.1 (previously was KT-5190). No work around is needed.


Workarounds

Workaround #1:

Create this extension function, then use it simply as .toList() on the Stream:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())

usage:

Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()

This adds a more explicit generic parameter to the Collectors.toList() call, preventing the bug which occurs during inference of the generics (which are somewhat convoluted for that method return type Collector<T, ?, List<T>>, eeeks!?!).

Workaround #2:

Add the correct type parameter to your call as Collectors.toList<Path>() to avoid type inference of that parameter:

Files.list(Paths.get(file)).filter { /* filter clause */ }.collect(Collectors.toList<Path>())

But the extension function in workaround #1 is more re-usable and more concise.


Staying Lazy

Another way around the bug is to not collect the Stream. You can stay lazy, and convert the Stream to a Kotlin Sequence or Iterator, here is an extension function for making a Sequence:

fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()

Now you have forEach and many other functions available to you while still consuming the Stream lazily and only once. Using myStream.iterator() is another way but may not have as much functionality as a Sequence.

And of course at the end of some processing on the Sequence you can toList() or toSet() or use any other of the Kotlin extensions for changing collection types.

And with this, I would create an extensions for listing files to avoid the bad API design of Paths, Path, Files, File:

fun Path.list(): Sequence<Path> = Files.list(this).iterator().asSequence()

which would at least flow nicely from left to right:

File(someDir).toPath().list().forEach { println(it) }
Paths.get(dirname).list().forEach { println(it) }

Alternatives to using Java 8 Streams:

We can change your code slightly to get the file list from File instead, and you would just use toList() at the end:

file.listFiles().filter { /* filter clause */ }.toList()

or

file.listFiles { file, name ->  /* filter clause */ }.toList()

Unfortunately the Files.list(...) that you originally used returns a Stream and doesn't give you the opportunity to use a traditional collection. This change avoids that by starting with a function that returns an Array or collection.

In General:

In most cases you can avoid Java 8 streams, and use native Kotlin stdlib functions and extensions to Java collections. Kotlin does indeed use Java collections, via compile-time readonly and mutable interfaces. But then it adds extension functions to provide more functionality. Therefore you have the same performance but with many more capabilities.

See Also:

  • What Java 8 Stream.collect equivalents are available in the standard Kotlin library? - You will see it is more concise to use Kotlin stdlib functions and extensions.
  • Kotlin Collections, and Extension Functions API docs
  • Kotlin Sequences API docs
  • Kotlin idioms

You should review the API reference for knowing what is available in the stdlib.

Java 8 stream.collect(Collectors.toMap()) analog in kotlin

Assuming that you have

val list: List<Person> = listOf(Person("Ann", 19), Person("John", 23))

the associateBy function would probably satisfy you:

val map = list.associateBy({ it.name }, { it.age })
/* Contains:
* "Ann" -> 19
* "John" -> 23
*/

As said in KDoc, associateBy:

Returns a Map containing the values provided by valueTransform and indexed by keySelector functions applied to elements of the given array.

If any two elements would have the same key returned by keySelector the last one gets added to the map.

The returned map preserves the entry iteration order of the original array.

It's applicable to any Iterable.

Java 8 lambda to kotlin lambda

Kotlin collections don't have a stream() method.

As mentioned in https://youtrack.jetbrains.com/issue/KT-5175,
you can use

(data1 as java.util.Collection<Account>).stream()...

or you can use one of the native Kotlin alternatives that don't use streams, listed in the answers to this question:

val list = data1.map { it.name }

Collect conditional items while streaming in kotlin

You can use .mapNotNull

val exceptionResults = listOf<String>("Name1", "Name2", "Name3")
.filter {
it.length > 2
}
.mapNotNull { name ->
try {
if (name == "Name2") {
throw Exception(name)
}
null
} catch (exception: Exception) {
name
}
}
println(exceptionRequests) // This prints `Name2`.

If exception isn't thrown, try catch expression will result in null.
If exception is thrown, try catch expression will result in name.
mapNotNull will filter out nulls (cases where exception wasn't thrown).

what is the difference in using stream.max() on the stream directly vs using stream.collect(...)

I would say readability, mostly if you use this grammar:

menu.stream().max(Comparator::comparing(Dish::getCalories));

I can find also this question pretty similar

Kotlin How to use java streams .map() in kotlin to map a different object response

some quick prototyping

    details  = acctBal
.filter{ f -> f.getBalType() != null }
.map { it -> mapToProductDetail (it) }

you can have a look here

Getting the elements from a list of lists with the Java Stream API in Kotlin

In Kotlin it's super easy without any excessive boilerplate:

val listOfLists: List<List<String>> = listOf()

val flattened: List<String> = listOfLists.flatten()

flatten() is the same as doing flatMap { it }


In Java, you need to utilize Stream API:

List<String> flattened = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());

You can also use Stream API in Kotlin:

val flattened: List<String> = listOfLists.stream()
.flatMap { it.stream() }
.collect(Collectors.toList())


Related Topics



Leave a reply



Submit