- Kotlin Эквиваленты потока Java 8
- замечания
- О лени
- Почему нет типов.
- Повторное использование потоков
- Смотрите также:
- Накопить имена в списке
- Преобразуйте элементы в строки и объедините их, разделяя их запятыми
- Вычислить сумму заработной платы работника
- Сотрудники группы по подразделениям
- Вычисление суммы заработной платы отделом
- Разделение студентов на прохождение и провал
- Имена мужчин-членов
- Имена групп членов в реестре по полу
- Отфильтровать список в другом списке
- Поиск кратчайшей строки списка
- Различные типы потоков №2 - лениво используя первый элемент, если существует
- Различные типы потоков №3 - повторять ряд целых чисел
- Различные типы потоков # 4 - итерация массива, отображение значений, вычисление среднего значения
- Различные типы потоков №5 - лениво перебирать список строк, отображать значения, преобразовывать в Int, находить max
- Различные типы потоков №6 - лениво перебирать поток интс, отображать значения, печатать результаты
- Различные типы потоков # 7 - лениво повторять парные числа, сопоставлять с Int, отображать на String, печатать каждый
- Подсчет элементов в списке после применения фильтра
- Как работают потоки - фильтр, верхний регистр, затем сортировка списка
- Различные типы потоков №1 - стремятся использовать первый элемент, если он существует
- Соберите пример № 5 - найдите людей, достигших совершеннолетия, выведите форматированную строку
- Соберите пример # 6 - люди группы по возрасту, возраст печати и имена вместе
- Соберите пример # 7a - Названия карт, объединяйтесь вместе с разделителем
- Соберите пример # 7b - Соберите с SummarizingInt
Kotlin
Эквиваленты потока Java 8
Kotlin предоставляет множество методов расширения для коллекций и итераций для применения функциональных операций. Специальный тип Sequence позволяет использовать ленивый состав нескольких таких операций.
замечания
О лени
Если вы хотите лениво обрабатывать цепочку, вы можете преобразовать ее в 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, потому что он также имеет типы с нулевым значением и может помочь предотвратить опасный NPE. Так это в Котлине:
val someList = people.filter < it.age .map
val someList: List = people.filter < it.age .map
Поскольку Котлин знает, что такое people , и что people.age является Int поэтому выражение фильтра допускает сравнение с Int и что people.name является String поэтому на этапе map создается List (только для чтения List String ).
Теперь, если people могут быть null , как в List? затем:
val someList = people?.filter < it.age ?.map
Возвращает List? который должен быть нулевым ( или использовать один из других операторов Котлина для значений с нулевым значением, см. этот идиоматический способ Kotlin для обработки значений с нулевым значением, а также идиоматический способ обработки нулевого или пустого списка в Котлине )
Повторное использование потоков
В Котлине зависит от типа сбора, может ли он потребляться более одного раза. Sequence генерирует новый итератор каждый раз, и если он не утверждает, что «использует только один раз», он может каждый раз перезапускаться до начала каждого действия. Поэтому, если в потоке Java 8 не работает, но работает в Котлине:
// Java: Stream 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 $>") // Any B true println("Any C $>") // Any C false stream.forEach(::println) // b1, b2
И в Java, чтобы получить такое же поведение:
// Java: Supplier> 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
Поэтому в Котлине поставщик данных решает, может ли он вернуться и предоставить новый итератор или нет. Но если вы хотите преднамеренно ограничить 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.
Смотрите также:
- Справочник API для функций расширения для Iterable
- Ссылка API для функций расширения для массива
- Ссылка API для функций расширения для списка
- Ссылка API для функций расширения на карту
Накопить имена в списке
// Java: List 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() // ", " is used as separator, by default
Вычислить сумму заработной платы работника
// Java: int total = employees.stream() .collect(Collectors.summingInt(Employee::getSalary)));
// Kotlin: val total = employees.sumBy
Сотрудники группы по подразделениям
// Java: Map> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment));
// Kotlin: val byDept = employees.groupBy
Вычисление суммы заработной платы отделом
// Java: Map 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> passingFailing = students.stream() .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
// Kotlin: val passingFailing = students.partition < it.grade >= PASS_THRESHOLD >
Имена мужчин-членов
// Java: List namesOfMaleMembersCollect = 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
Имена групп членов в реестре по полу
// Java: Map> 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 filtered = items.stream() .filter( item -> item.startsWith("o") ) .collect(Collectors.toList());
// Kotlin: val filtered = items.filter
Поиск кратчайшей строки списка
// Java: String shortest = items.stream() .min(Comparator.comparing(item -> item.length())) .get();
// Kotlin: val shortest = items.minBy
Различные типы потоков №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[] ) .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 - лениво перебирать список строк, отображать значения, преобразовывать в Int, находить max
// 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 - лениво повторять парные числа, сопоставлять с Int, отображать на String, печатать каждый
// 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: 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
Как работают потоки - фильтр, верхний регистр, затем сортировка списка
// Java: List 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)
или, создайте функцию расширения для строки, называемой 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)
Соберите пример № 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 map = persons .stream() .collect(Collectors.toMap( p -> p.age, p -> p.name, (name1, name2) -> name1 + ";" + name2)); System.out.println(map); //
Хорошо, здесь больше интересного для Котлина. Сначала неправильные ответы, чтобы изучить варианты создания Map из коллекции / последовательности:
// Kotlin: val map1 = persons.map < it.age to it.name >.toMap() println(map1) // output: // Result: duplicates overridden, no exception similar to Java 8 val map2 = persons.toMap(< it.age >, < it.name >) println(map2) // output: // 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> instead of Map val map5 = persons.groupBy < it.age >.mapValues < it.value.map < it.name >> println(map5) // output: // Result: closer, but now have a Map> instead of Map
И теперь для правильного ответа:
// Kotlin: val map6 = persons.groupBy < it.age >.mapValues < it.value.joinToString(";") < it.name >> println(map6) // output: // Result: YAY!!
Нам просто нужно было объединить соответствующие значения, чтобы свернуть списки и предоставить трансформатору joinToString для перехода от экземпляра Person к Person.name .
Соберите пример # 7a - Названия карт, объединяйтесь вместе с разделителем
// Java (verbose): Collector 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 // Java (concise) String names = persons.stream().map(p -> p.name.toUpperCase()).collect(Collectors.joining(" | "));
// Kotlin: val names = persons.map < it.name.toUpperCase() >.joinToString(" | ")
Соберите пример # 7b - Соберите с SummarizingInt
// Java: IntSummaryStatistics ageSummary = persons.stream() .collect(Collectors.summarizingInt(p -> p.age)); System.out.println(ageSummary); // IntSummaryStatistics
// 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.summarizingInt(): SummaryStatisticsInt = this.fold(SummaryStatisticsInt()) < stats, num ->stats.accumulate(num) > inline fun Collection.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
И все они дают одинаковые результаты. Мы также можем создать это расширение для работы над Sequence и для соответствующих примитивных типов.