Jay*_*ard 171 java collections java-8 kotlin java-stream
在Java 8中,Stream.collect允许对集合进行聚合.在Kotlin中,除了作为stdlib中的扩展函数的集合之外,它不以相同的方式存在.但目前尚不清楚不同用例的等价性.
例如,在JavaDocCollectors的顶部是为Java 8编写的示例,当将它们移植到Kolin时,在不同的JDK版本上不能使用Java 8类,因此它们应该以不同的方式编写.
在网上显示Kotlin集合示例的资源方面,它们通常是微不足道的,并没有真正与相同的用例进行比较.什么是真正符合Java 8记录的案例的好例子Stream.collect?那里的清单是:
以上链接的JavaDoc中的详细信息.
注意: 这个问题是由作者故意编写和回答的(自答案问题),因此对于常见问题的Kotlin主题的惯用答案存在于SO中.还要澄清为Kotlin的alphas写的一些非常古老的答案,这些答案对于当前的Kotlin来说是不准确的.
Jay*_*ard 240
Kotlin stdlib中有函数用于平均,计数,分离,过滤,查找,分组,连接,映射,最小化,最大化,分区,切片,排序,求和,到/从数组到/从列表到/从地图,联合,共同迭代,所有功能范例等等.因此,您可以使用它们来创建小的1行,并且不需要使用更复杂的Java 8语法.
我认为内置的Java 8 Collectors类中唯一缺少的是摘要(但在这个问题的另一个答案是一个简单的解决方案).
两者中缺少的一件事是按计数进行批处理,这可以在另一个Stack Overflow答案中看到,并且也有一个简单的答案.另一个有趣的案例是Stack Overflow:使用Kotlin将序列分解为三个列表的惯用方法.如果您想创建类似于Stream.collect其他用途的内容,请参阅Kotlin中的Custom Stream.collect
编辑11.08.2017:在kotlin 1.2 M2中添加的分块/窗口收集操作,请参阅https://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/
在创建可能已经存在的新函数之前,总是很好地探索kotlin.collections的API参考.
以下是从Java 8 Stream.collect示例到Kotlin中的等效内容的一些转换:
将名称累积到列表中
// Java:
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val list = people.map { it.name } // toList() not needed
Run Code Online (Sandbox Code Playgroud)
将元素转换为字符串并将它们连接起来,用逗号分隔
// Java:
String joined = things.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val joined = things.joinToString(", ")
Run Code Online (Sandbox Code Playgroud)
计算员工工资的总和
// Java:
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val total = employees.sumBy { it.salary }
Run Code Online (Sandbox Code Playgroud)
按部门分组员工
// Java:
Map<Department, List<Employee>> byDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val byDept = employees.groupBy { it.department }
Run Code Online (Sandbox Code Playgroud)
按部门计算工资总额
// Java:
Map<Department, Integer> totalByDept
= employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}
Run Code Online (Sandbox Code Playgroud)
将学生分成传球和失败
// Java:
Map<Boolean, List<Student>> passingFailing =
students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val passingFailing = students.partition { it.grade >= PASS_THRESHOLD }
Run Code Online (Sandbox Code Playgroud)
男性成员的姓名
// Java:
List<String> namesOfMaleMembers = roster
.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(p -> p.getName())
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val namesOfMaleMembers = roster.filter { it.gender == Person.Sex.MALE }.map { it.name }
Run Code Online (Sandbox Code Playgroud)
按性别分列的名册成员组名
// Java:
Map<Person.Sex, List<String>> namesByGender =
roster.stream().collect(
Collectors.groupingBy(
Person::getGender,
Collectors.mapping(
Person::getName,
Collectors.toList())));
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val namesByGender = roster.groupBy { it.gender }.mapValues { it.value.map { it.name } }
Run Code Online (Sandbox Code Playgroud)
将列表过滤到另一个列表
// Java:
List<String> filtered = items.stream()
.filter( item -> item.startsWith("o") )
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val filtered = items.filter { it.startsWith('o') }
Run Code Online (Sandbox Code Playgroud)
找到最短的字符串列表
// Java:
String shortest = items.stream()
.min(Comparator.comparing(item -> item.length()))
.get();
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val shortest = items.minBy { it.length }
Run Code Online (Sandbox Code Playgroud)
应用过滤器后计算列表中的项目
// Java:
long count = items.stream().filter( item -> item.startsWith("t")).count();
Run Code Online (Sandbox Code Playgroud)
// 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') }
Run Code Online (Sandbox Code Playgroud)
在它上面......在所有情况下,模拟都不需要特殊的折叠,缩小或其他功能Stream.collect.如果您还有其他用例,请在评论中添加它们,我们可以看到!
如果你想延迟处理链,你可以在链之前转换为Sequence使用asSequence().在功能链的末尾,你通常Sequence也会得到一个结果.然后你可以使用toList(),toSet(),toMap()或其他一些功能兑现的Sequence结尾.
// 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()
Run Code Online (Sandbox Code Playgroud)
您会注意到Kotlin示例未指定类型.这是因为Kotlin具有完整的类型推断并且在编译时完全是类型安全的.比Java更多,因为它也有可空类型,可以帮助防止可怕的NPE.所以这在Kotlin:
val someList = people.filter { it.age <= 30 }.map { it.name }
Run Code Online (Sandbox Code Playgroud)
是相同的:
val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }
Run Code Online (Sandbox Code Playgroud)
因为科特林知道什么people是,这people.age是Int因此,过滤器表达式只允许比较的Int,那people.name是一个String因此map步骤产生List<String>(只读List的String).
现在,如果people可能的话null,List<People>?那么:
val someList = people?.filter { it.age <= 30 }?.map { it.name }
Run Code Online (Sandbox Code Playgroud)
返回List<String>?需要进行null检查的(或者使用其他Kotlin运算符之一来获取可空值,请参阅此Kotlin惯用方法来处理可空值以及处理可空或空列表的惯用方法)
Jay*_*ard 43
有关其他示例,以下是将Java 8 Stream Tutorial转换为Kotlin的所有示例.每个示例的标题都源自源文章:
流是如何工作的
// Java:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
// C1
// C2
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val list = listOf("a1", "a2", "b1", "c2", "c1")
list.filter { it.startsWith('c') }.map (String::toUpperCase).sorted()
.forEach (::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#1
// Java:
Arrays.asList("a1", "a2", "a3")
.stream()
.findFirst()
.ifPresent(System.out::println);
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)
Run Code Online (Sandbox Code Playgroud)
或者,在String上创建一个名为ifPresent的扩展函数:
// Kotlin:
inline fun String?.ifPresent(thenDo: (String)->Unit) = this?.apply { thenDo(this) }
// now use the new extension function:
listOf("a1", "a2", "a3").firstOrNull().ifPresent(::println)
Run Code Online (Sandbox Code Playgroud)
另见:apply()功能
另请参见:扩展函数
另请参阅: ?.安全调用运算符,以及一般的可空性:在Kotlin中,处理可空值,引用或转换它们的惯用方法是什么
不同种类的流#2
// Java:
Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println);
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#3
// Java:
IntStream.range(1, 4).forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)
// Kotlin: (inclusive range)
(1..3).forEach(::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#4
// Java:
Arrays.stream(new int[] {1, 2, 3})
.map(n -> 2 * n + 1)
.average()
.ifPresent(System.out::println); // 5.0
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
arrayOf(1,2,3).map { 2 * it + 1}.average().apply(::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#5
// Java:
Stream.of("a1", "a2", "a3")
.map(s -> s.substring(1))
.mapToInt(Integer::parseInt)
.max()
.ifPresent(System.out::println); // 3
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
sequenceOf("a1", "a2", "a3")
.map { it.substring(1) }
.map(String::toInt)
.max().apply(::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#6
// Java:
IntStream.range(1, 4)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3
Run Code Online (Sandbox Code Playgroud)
// Kotlin: (inclusive range)
(1..3).map { "a$it" }.forEach(::println)
Run Code Online (Sandbox Code Playgroud)
不同种类的流#7
// Java:
Stream.of(1.0, 2.0, 3.0)
.mapToInt(Double::intValue)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
sequenceOf(1.0, 2.0, 3.0).map(Double::toInt).map { "a$it" }.forEach(::println)
Run Code Online (Sandbox Code Playgroud)
Java 8 Stream Tutorial的这一部分与Kotlin和Java相同.
在Kotlin中,它取决于收集的类型是否可以不止一次消费.A Sequence每次都会生成一个新的迭代器,除非它断言"只使用一次",否则每次执行时它都会重置为开始.因此,虽然以下在Java 8流中失败,但在Kotlin中工作:
// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));
stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
stream.forEach(::println) // b1, b2
println("Any B ${stream.any { it.startsWith('b') }}") // Any B true
println("Any C ${stream.any { it.startsWith('c') }}") // Any C false
stream.forEach(::println) // b1, b2
Run Code Online (Sandbox Code Playgroud)
并在Java中获得相同的行为:
// Java:
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
Run Code Online (Sandbox Code Playgroud)
因此,在Kotlin中,数据提供者决定它是否可以重置并提供新的迭代器.但是如果你想故意约束Sequence一次迭代,你可以使用如下constrainOnce()函数Sequence:
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
.constrainOnce()
stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once.
Run Code Online (Sandbox Code Playgroud)
收集示例#5(是的,我跳过已经在另一个答案中的那些)
// Java:
String phrase = persons
.stream()
.filter(p -> p.age >= 18)
.map(p -> p.name)
.collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));
System.out.println(phrase);
// In Germany Max and Peter and Pamela are of legal age.
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val phrase = persons.filter { it.age >= 18 }.map { it.name }
.joinToString(" and ", "In Germany ", " are of legal age.")
println(phrase)
// In Germany Max and Peter and Pamela are of legal age.
Run Code Online (Sandbox Code Playgroud)
作为旁注,在Kotlin中,我们可以创建简单的数据类并实例化测试数据,如下所示:
// Kotlin:
// data class has equals, hashcode, toString, and copy methods automagically
data class Person(val name: String, val age: Int)
val persons = listOf(Person("Tod", 5), Person("Max", 33),
Person("Frank", 13), Person("Peter", 80),
Person("Pamela", 18))
Run Code Online (Sandbox Code Playgroud)
收集例子#6
// Java:
Map<Integer, String> map = persons
.stream()
.collect(Collectors.toMap(
p -> p.age,
p -> p.name,
(name1, name2) -> name1 + ";" + name2));
System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}
Run Code Online (Sandbox Code Playgroud)
好的,这是Kotlin的一个更有意义的案例.首先是错误的答案,探索Map从集合/序列创建变体的变体:
// Kotlin:
val map1 = persons.map { it.age to it.name }.toMap()
println(map1)
// output: {18=Max, 23=Pamela, 12=David}
// Result: duplicates overridden, no exception similar to Java 8
val map2 = persons.toMap({ it.age }, { it.name })
println(map2)
// output: {18=Max, 23=Pamela, 12=David}
// Result: same as above, more verbose, duplicates overridden
val map3 = persons.toMapBy { it.age }
println(map3)
// output: {18=Person(name=Max, age=18), 23=Person(name=Pamela, age=23), 12=Person(name=David, age=12)}
// Result: duplicates overridden again
val map4 = persons.groupBy { it.age }
println(map4)
// output: {18=[Person(name=Max, age=18)], 23=[Person(name=Peter, age=23), Person(name=Pamela, age=23)], 12=[Person(name=David, age=12)]}
// Result: closer, but now have a Map<Int, List<Person>> instead of Map<Int, String>
val map5 = persons.groupBy { it.age }.mapValues { it.value.map { it.name } }
println(map5)
// output: {18=[Max], 23=[Peter, Pamela], 12=[David]}
// Result: closer, but now have a Map<Int, List<String>> instead of Map<Int, String>
Run Code Online (Sandbox Code Playgroud)
现在为了正确答案:
// Kotlin:
val map6 = persons.groupBy { it.age }.mapValues { it.value.joinToString(";") { it.name } }
println(map6)
// output: {18=Max, 23=Peter;Pamela, 12=David}
// Result: YAY!!
Run Code Online (Sandbox Code Playgroud)
我们只需要加入匹配的值来折叠列表并提供一个转换器来jointToString从Person实例移动到Person.name.
收集例子#7
好的,这个可以很容易地完成,没有自定义Collector,所以让我们用Kotlin方式解决它,然后设计一个新的例子,展示如何做一个Collector.summarizingIntKotlin本身不存在的类似过程.
// Java:
Collector<Person, StringJoiner, String> personNameCollector =
Collector.of(
() -> new StringJoiner(" | "), // supplier
(j, p) -> j.add(p.name.toUpperCase()), // accumulator
(j1, j2) -> j1.merge(j2), // combiner
StringJoiner::toString); // finisher
String names = persons
.stream()
.collect(personNameCollector);
System.out.println(names); // MAX | PETER | PAMELA | DAVID
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
val names = persons.map { it.name.toUpperCase() }.joinToString(" | ")
Run Code Online (Sandbox Code Playgroud)
他们挑选了一个简单的例子,这不是我的错! 好的,这是summarizingIntKotlin和匹配样本的新方法:
SummarizingInt示例
// Java:
IntSummaryStatistics ageSummary =
persons.stream()
.collect(Collectors.summarizingInt(p -> p.age));
System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}
Run Code Online (Sandbox Code Playgroud)
// Kotlin:
// something to hold the stats...
data class SummaryStatisticsInt(var count: Int = 0,
var sum: Int = 0,
var min: Int = Int.MAX_VALUE,
var max: Int = Int.MIN_VALUE,
var avg: Double = 0.0) {
fun accumulate(newInt: Int): SummaryStatisticsInt {
count++
sum += newInt
min = min.coerceAtMost(newInt)
max = max.coerceAtLeast(newInt)
avg = sum.toDouble() / count
return this
}
}
// Now manually doing a fold, since Stream.collect is really just a fold
val stats = persons.fold(SummaryStatisticsInt()) { stats, person -> stats.accumulate(person.age) }
println(stats)
// output: SummaryStatisticsInt(count=4, sum=76, min=12, max=23, avg=19.0)
Run Code Online (Sandbox Code Playgroud)
但最好创建一个扩展函数,2实际上匹配Kotlin stdlib中的样式:
// Kotlin:
inline fun Collection<Int>.summarizingInt(): SummaryStatisticsInt
= this.fold(SummaryStatisticsInt()) { stats, num -> stats.accumulate(num) }
inline fun <T: Any> Collection<T>.summarizingInt(transform: (T)->Int): SummaryStatisticsInt =
this.fold(SummaryStatisticsInt()) { stats, item -> stats.accumulate(transform(item)) }
Run Code Online (Sandbox Code Playgroud)
现在您有两种方法可以使用新summarizingInt功能:
val stats2 = persons.map { it.age }.summarizingInt()
// or
val stats3 = persons.summarizingInt { it.age }
Run Code Online (Sandbox Code Playgroud)
所有这些都产生相同的结果.我们还可以创建此扩展以处理Sequence适当的基元类型.
为了好玩,请比较实现此摘要所需的Java JDK代码与Kotlin自定义代码.
| 归档时间: |
|
| 查看次数: |
31548 次 |
| 最近记录: |