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 byvalueTransform
and indexed bykeySelector
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
Adding 3Rd Party Jars to Web-Inf/Lib Automatically Using Eclipse/Tomcat
How to Load/Reference a File as a File Instance from the Classpath
Spark Strutured Streaming Automatically Converts Timestamp to Local Time
Sessions in Struts2 Application
What Determines Kafka Consumer Offset
Deciphering Variable Information While Debugging Java
Calling Win32 API Method from Java
How to Use Conditional Breakpoint in Eclipse
Java Convert Gmt/Utc to Local Time Doesn't Work as Expected
Java - Why No Return Type Based Method Overloading
How to Prevent Java.Lang.String.Split() from Creating a Leading Empty String
Converting Stream of Int's to Char's in Java
How to Create a Folder in Java
Moving Items Around in an Arraylist
What Are the Main Uses of Yield(), and How Does It Differ from Join() and Interrupt()