Java 8 Stream.collect ใดที่มีอยู่ในไลบรารี Kotlin มาตรฐาน


181

ใน Java 8 มีการStream.collectอนุญาตให้รวมตัวกันบนคอลเลกชัน ใน Kotlin สิ่งนี้ไม่ได้อยู่ในลักษณะเดียวกันนอกเหนือไปจากบางทีอาจเป็นชุดของฟังก์ชันส่วนขยายใน stdlib แต่มันไม่ชัดเจนว่าการเทียบเคียงสำหรับกรณีการใช้งานที่แตกต่างกันอย่างไร

ตัวอย่างเช่นที่ด้านบนของ JavaDoc สำหรับCollectorsเป็นตัวอย่างที่เขียนขึ้นสำหรับ Java 8 และเมื่อย้ายไปยัง Kolin คุณจะไม่สามารถใช้คลาส Java 8 เมื่ออยู่ในรุ่น JDK ที่แตกต่างกันดังนั้นจึงเป็นไปได้ว่าพวกเขาควรจะเขียนต่างกัน

ในแง่ของทรัพยากรออนไลน์แสดงตัวอย่างของคอลเลกชัน Kotlin พวกเขามักจะเล็กน้อยและไม่ได้เปรียบเทียบกับกรณีการใช้งานที่เหมือนกัน มีตัวอย่างที่ดีอะไรบ้างที่ตรงกับเคสเช่นเอกสารสำหรับ Java 8 Stream.collect? รายการมี:

  • สะสมชื่อลงในรายการ
  • สะสมชื่อลงใน TreeSet
  • แปลงองค์ประกอบเป็นสตริงและเชื่อมต่อพวกเขาคั่นด้วยเครื่องหมายจุลภาค
  • คำนวณผลรวมของเงินเดือนของพนักงาน
  • จัดกลุ่มพนักงานตามแผนก
  • คำนวณยอดรวมเงินเดือนตามแผนก
  • แบ่งนักเรียนให้ผ่านและไม่ผ่าน

ด้วยรายละเอียดใน JavaDoc ที่ลิงค์ด้านบน

หมายเหตุ: คำถามนี้เขียนโดยเจตนาและตอบโดยผู้เขียน ( คำถามที่ตอบด้วยตนเอง ) ดังนั้นคำตอบที่เป็นไปตามหัวข้อ Kotlin ที่ถูกถามบ่อยนั้นมีอยู่ใน SO นอกจากนี้เพื่อชี้แจงคำตอบเก่า ๆ ที่เขียนขึ้นสำหรับอัลฟาของ Kotlin ที่ไม่ถูกต้องสำหรับ Kotlin วันปัจจุบัน


ในกรณีที่คุณไม่มีทางเลือกนอกจากใช้งานcollect(Collectors.toList())หรือคล้ายกันคุณอาจพบปัญหานี้: stackoverflow.com/a/35722167/3679676 (ปัญหาพร้อมวิธีแก้ไขปัญหา)
Jayson Minard

คำตอบ:


257

มีฟังก์ชั่นใน Kotlin stdlib สำหรับค่าเฉลี่ย, จำนวน, ชัดเจน, การกรอง, การค้นหา, การจัดกลุ่ม, การเข้าร่วม, การทำแผนที่, ขั้นต่ำ, สูงสุด, การแบ่งพาร์ติชัน, การแบ่ง, การเรียงลำดับ, การสรุป, ถึง / จากอาร์เรย์ สหภาพการรวมซ้ำทุกกระบวนทัศน์การทำงานและอื่น ๆ ดังนั้นคุณสามารถใช้สิ่งเหล่านี้เพื่อสร้าง 1-liners เล็กน้อยและไม่จำเป็นต้องใช้ไวยากรณ์ที่ซับซ้อนยิ่งขึ้นของ Java 8

ผมคิดว่าสิ่งเดียวที่ขาดหายไปจากในตัว Java 8 Collectorsระดับคือการสรุป ( แต่ในคำตอบของคำถามนี้ก็เป็นวิธีง่ายๆ)

สิ่งหนึ่งที่ขาดหายไปจากทั้งสองคือการแบตช์ตามจำนวนซึ่งเห็นได้ในคำตอบสแต็คโอเวอร์โฟลว์อื่นและมีคำตอบง่ายๆเช่นกัน อีกกรณีที่น่าสนใจคือคนนี้ยังมาจากกองมากเกิน: วิธีสำนวนลำดับที่หกรั่วไหลลงไปในสามรายการโดยใช้ Kotlin และถ้าคุณต้องการสร้างสิ่งที่คล้ายStream.collectกันเพื่อจุดประสงค์อื่นให้ดูที่การสตรีมแบบกำหนดเองโดยเลือกใน Kotlin

แก้ไข 11.08.2017:การดำเนินการเก็บรวบรวมแบบแยก / เรียงใน kotlin 1.2 M2 ดูhttps://blog.jetbrains.com/kotlin/2017/08/kotlin-1-2-m2-is-out/


เป็นการดีเสมอที่จะสำรวจการอ้างอิง API สำหรับ kotlin.collectionsโดยรวมก่อนสร้างฟังก์ชันใหม่ที่อาจมีอยู่แล้ว

ต่อไปนี้เป็นตัวอย่าง Conversion จาก Java 8 Stream.collectเป็นค่าที่เทียบเท่าใน Kotlin:

สะสมชื่อลงในรายการ

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

แปลงองค์ประกอบเป็นสตริงและเชื่อมต่อพวกเขาคั่นด้วยเครื่องหมายจุลภาค

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

คำนวณผลรวมของเงินเดือนของพนักงาน

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

จัดกลุ่มพนักงานตามแผนก

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

แบ่งนักเรียนให้ผ่านและไม่ผ่าน

// 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 }

ชื่อสมาชิกชาย

// 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 }

ชื่อกลุ่มของสมาชิกในรายชื่อตามเพศ

// 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 } }   

กรองรายการไปยังรายการอื่น

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

การค้นหารายการสตริงที่สั้นที่สุด

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

การนับรายการในรายการหลังจากใช้ตัวกรอง

// 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') }

และมันจะไป ... ในทุกกรณีไม่มีพับพิเศษลดหรือการทำงานอื่น ๆ 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()

ทำไมไม่มีประเภท?!

คุณจะสังเกตเห็นตัวอย่าง Kotlin ไม่ได้ระบุประเภท นี่เป็นเพราะ Kotlin มีการอนุมานแบบเต็มรูปแบบและพิมพ์ปลอดภัยอย่างสมบูรณ์ในเวลารวบรวม มากกว่า Java เพราะมันมีประเภท nullable และสามารถช่วยป้องกัน NPE ที่หวั่น ดังนั้นนี่ใน Kotlin:

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

เหมือนกับ:

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

เพราะ Kotlin รู้ว่าสิ่งที่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 }

ส่งคืน a List<String>?ที่จะต้องมีการตรวจสอบเป็นโมฆะ ( หรือใช้หนึ่งในผู้ประกอบการ Kotlin อื่น ๆ สำหรับค่า nullable ดูวิธีนี้Kotlin สำนวนที่จะจัดการกับค่า nullableและยังเป็นวิธีการจัดการรายการว่างเปล่าหรือว่างเปล่าใน Kotlin )

ดูสิ่งนี้ด้วย:


มีเทียบเท่ากับ parallelStream ของ Java8 ใน Kotlin?
arnab

คำตอบเกี่ยวกับคอลเลกชันที่ไม่เปลี่ยนรูปแบบและ Kotlin เป็นคำตอบเดียวกันสำหรับ @arnab ที่นี่สำหรับขนานมีห้องสมุดอื่น ๆ ใช้: stackoverflow.com/a/34476880/3679676
Jayson Minard

2
@arnab คุณอาจต้องการดูการสนับสนุน Kotlin สำหรับคุณสมบัติ Java 7/8 (โดยเฉพาะ kotlinx-support-jdk8) ที่จัดทำขึ้นเมื่อต้นปีนี้: discuss.kotlinlang.org/t/jdk7-8-features-in -kotlin-1-0 / 1625
roborative

มันเป็นเรื่องจริงไหมที่ใช้การอ้างอิงแบบ "มัน" 3 แบบในหนึ่งข้อความ
เฮอ

2
มันเป็นการตั้งค่าในตัวอย่างข้างต้นฉันทำให้พวกเขาสั้นและให้ชื่อท้องถิ่นสำหรับพารามิเตอร์หากจำเป็น
Jayson Minard

47

สำหรับตัวอย่างเพิ่มเติมต่อไปนี้เป็นตัวอย่างทั้งหมดจาก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
// Kotlin:
val list = listOf("a1", "a2", "b1", "c2", "c1")
list.filter { it.startsWith('c') }.map (String::toUpperCase).sorted()
        .forEach (::println)

ความแตกต่างของสายน้ำ # 1

// Java:
Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
listOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

หรือสร้างฟังก์ชั่นส่วนขยายบน 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)

ดูเพิ่มเติมที่: apply()ฟังก์ชั่น

ดูเพิ่มเติมที่: ฟังก์ชั่นการขยาย

ดูเพิ่มเติมที่: ?.ตัวดำเนินการโทรปลอดภัยและความเป็นโมฆะทั่วไป: ใน Kotlin เป็นวิธีการจัดการกับค่า nullable อ้างอิงหรือแปลงพวกเขาคืออะไร

ความแตกต่างของสายน้ำ # 2

// Java:
Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);    
// Kotlin:
sequenceOf("a1", "a2", "a3").firstOrNull()?.apply(::println)

ความแตกต่างของสายน้ำ # 3

// Java:
IntStream.range(1, 4).forEach(System.out::println);
// Kotlin:  (inclusive range)
(1..3).forEach(::println)

ความแตกต่างของสายน้ำ # 4

// Java:
Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println); // 5.0    
// Kotlin:
arrayOf(1,2,3).map { 2 * it + 1}.average().apply(::println)

ความแตกต่างของสายน้ำ # 5

// Java:
Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3
// Kotlin:
sequenceOf("a1", "a2", "a3")
    .map { it.substring(1) }
    .map(String::toInt)
    .max().apply(::println)

ความแตกต่างของสายน้ำ # 6

// Java:
IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3    
// Kotlin:  (inclusive range)
(1..3).map { "a$it" }.forEach(::println)

ประเภทต่างๆของสตรีม # 7

// Java:
Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3
// Kotlin:
sequenceOf(1.0, 2.0, 3.0).map(Double::toInt).map { "a$it" }.forEach(::println)

ทำไมเรื่องการสั่งซื้อ

ส่วนนี้ของการสอน Java 8 Stream นั้นเหมือนกันสำหรับ Kotlin และ Java

การใช้ซ้ำสตรีม

ใน Kotlin นั้นขึ้นอยู่กับประเภทของการสะสมว่าสามารถบริโภคได้มากกว่าหนึ่งครั้งหรือไม่ Sequenceสร้าง iterator ใหม่ทุกครั้งและถ้ามันอ้าง "การใช้เพียงครั้งเดียว" ก็สามารถตั้งค่าที่จะเริ่มต้นในแต่ละครั้งที่มีการดำเนินการใด ๆ ดังนั้นในขณะที่ข้อมูลต่อไปนี้ล้มเหลวในสตรีม 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
// 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

และใน 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

ดังนั้นใน Kotlin ผู้ให้บริการของข้อมูลจะตัดสินใจว่าจะรีเซ็ตและให้ตัววนซ้ำใหม่หรือไม่ แต่ถ้าคุณต้องการ จำกัด การSequenceทำซ้ำa ถึงหนึ่งครั้งโดยเจตนาคุณสามารถใช้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. 

การดำเนินงานขั้นสูง

รวบรวมตัวอย่าง # 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.    
// 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.

และในฐานะที่เป็นหมายเหตุข้างต้นใน 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))

รวบรวมตัวอย่าง # 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}    

ตกลงเป็นกรณีที่น่าสนใจมากขึ้นสำหรับ 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>

และตอนนี้สำหรับคำตอบที่ถูกต้อง:

// 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!!

เราเพียงแค่ต้องการที่จะเข้าร่วมการจับคู่ค่าเพื่อยุบรายการและให้หม้อแปลงไปยังjointToStringที่จะย้ายจากอินสแตนซ์ไปPersonPerson.name

รวบรวมตัวอย่าง # 7

ตกลงสามารถทำได้อย่างง่ายดายโดยไม่ต้องกำหนดเองCollectorดังนั้นให้แก้ทาง Kotlin แล้วประดิษฐ์ตัวอย่างใหม่ที่แสดงวิธีการทำกระบวนการที่คล้ายกันCollector.summarizingIntซึ่งไม่มีอยู่ใน Kotlin

// 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    
// Kotlin:
val names = persons.map { it.name.toUpperCase() }.joinToString(" | ")

ไม่ใช่ความผิดของฉันที่พวกเขาเลือกตัวอย่างเล็กน้อย !!! ตกลงนี่เป็นsummarizingIntวิธีการใหม่สำหรับ Kotlin และตัวอย่างที่ตรงกัน:

สรุปตัวอย่าง

// 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}    
// 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)

แต่จะเป็นการดีกว่าถ้าสร้างฟังก์ชั่นเสริม 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)) }

ตอนนี้คุณมีสองวิธีในการใช้summarizingIntฟังก์ชั่นใหม่:

val stats2 = persons.map { it.age }.summarizingInt()

// or

val stats3 = persons.summarizingInt { it.age }

และสิ่งเหล่านี้ให้ผลลัพธ์ที่เหมือนกัน เรายังสามารถสร้างส่วนขยายนี้เพื่อทำงานSequenceและสำหรับประเภทดั้งเดิมที่เหมาะสม

เพื่อความสนุกสนานให้เปรียบเทียบโค้ด Java JDK กับโค้ดที่กำหนดเองของ Kotlinเพื่อใช้ในการสรุปนี้


ในสตรีม 5 ไม่มีสิ่งใดที่มีประโยชน์ในการใช้แผนที่สองแบบแทนที่จะเป็นแผนที่เดียว.map { it.substring(1).toInt() }เพราะคุณรู้ดีว่าประเภทที่อนุมานได้เป็นหนึ่งในพลังงานของค็อตลิน
Michele d'Amico

จริง แต่ไม่มีข้อเสียอย่างใดอย่างหนึ่ง (สำหรับการเปรียบเทียบผมเก็บไว้ให้พวกเขาแยก)
Jayson Minard

แต่รหัส Java สามารถทำขนานได้อย่างง่ายดายดังนั้นในหลายกรณีคุณควรโทร Java stream code จาก Kotlin ดีกว่า
Howard Lovatt

@HowardLovatt มีหลายกรณีที่ขนานไม่ได้เป็นวิธีที่จะไปโดยเฉพาะอย่างยิ่งในสภาพแวดล้อมที่เกิดขึ้นพร้อมกันหนักที่คุณอยู่แล้วในสระว่ายน้ำด้าย ฉันเดิมพันกรณีการใช้งานเฉลี่ยไม่ขนานกันและเป็นกรณีที่หายาก แต่แน่นอนว่าคุณมีตัวเลือกที่จะใช้คลาส Java ตามที่เห็นสมควรและไม่มีสิ่งนี้เป็นจุดประสงค์ของคำถามและคำตอบนี้จริง ๆ
Jayson Minard

3

มีบางกรณีที่มันยากที่จะหลีกเลี่ยงการโทรcollect(Collectors.toList())หรือคล้ายกัน ในกรณีเหล่านี้คุณสามารถเปลี่ยนเป็น Kotlin ที่เทียบเท่าได้อย่างรวดเร็วยิ่งขึ้นโดยใช้ฟังก์ชันส่วนขยายเช่น:

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

จากนั้นคุณสามารถstream.toList()หรือstream.asSequence()ย้อนกลับไปยัง Kotlin API ได้ กรณีเช่นFiles.list(path)บังคับให้คุณเข้าสู่Streamเมื่อคุณไม่ต้องการและส่วนขยายเหล่านี้สามารถช่วยให้คุณเปลี่ยนกลับไปเป็นคอลเลกชันมาตรฐานและ Kotlin API


2

เพิ่มเติมเกี่ยวกับความเกียจคร้าน

ลองใช้ตัวอย่างโซลูชันสำหรับ "คำนวณผลรวมของเงินเดือนตามแผนก" โดย Jayson:

val totalByDept = employees.groupBy { it.dept }.mapValues { it.value.sumBy { it.salary }}

เพื่อที่จะทำให้เรื่องนี้ขี้เกียจ (เช่นหลีกเลี่ยงการสร้างแผนที่กลางในgroupByขั้นตอน) asSequence()มันเป็นไปไม่ได้ที่จะใช้งาน เราต้องใช้groupingByและfoldดำเนินการแทน:

val totalByDept = employees.groupingBy { it.dept }.fold(0) { acc, e -> acc + e.salary }

สำหรับบางคนสิ่งนี้อาจจะอ่านง่ายขึ้นเนื่องจากคุณไม่ได้จัดการกับรายการแผนที่: it.valueส่วนหนึ่งในการแก้ปัญหาก็สร้างความสับสนให้กับฉันในตอนแรก

เนื่องจากนี่เป็นกรณีทั่วไปและเราไม่ต้องการที่จะเขียนออกมาในfoldแต่ละครั้งมันอาจจะดีกว่าที่จะให้sumByฟังก์ชั่นทั่วไปในGrouping:

public inline fun <T, K> Grouping<T, K>.sumBy(
        selector: (T) -> Int
): Map<K, Int> = 
        fold(0) { acc, element -> acc + selector(element) }

เพื่อให้เราสามารถเขียน:

val totalByDept = employees.groupingBy { it.dept }.sumBy { it.salary }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.