Kotlin
Java 8 Stream Equivalents
수색…
소개
Kotlin은 기능 스타일 작업을 적용하기위한 컬렉션 및 iterable에 대한 많은 확장 메서드를 제공합니다. 전용 Sequence
유형을 사용하면 이러한 여러 작업을 지연 적으로 구성 할 수 있습니다.
비고
게으름에 대해서
체인을 지연 처리하려는 경우 chain 이전에 asSequence()
를 사용하여 Sequence
로 변환 할 수 있습니다. 함수 체인의 끝에서 보통 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이 전체 유형 유추를 가지며 컴파일 타임에 완전히 유형 안전하기 때문입니다. Null을 허용하는 NPE를 방지 할 수 있기 때문에 Java보다 더 많이 사용합니다. Kotlin에서 이렇게 :
val someList = people.filter { it.age <= 30 }.map { it.name }
와 같다:
val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }
코 틀린는 것을 알고 있기 때문에 people
, 그리고 그 people.age
있다 Int
때문에 필터 식에만 비교 할 수 있습니다 Int
, 그 people.name
A는 String
때문에 map
단계는 생산 List<String>
(읽기 전용 List
의 String
).
이제 List<People>?
과 같이 people
이 null
일 가능성이 있다면 List<People>?
그때:
val someList = people?.filter { it.age <= 30 }?.map { it.name }
List<String>?
반환 List<String>?
null 체크가 필요합니다 ( 또는 null 허용 값에 대해 다른 Kotlin 연산자 중 하나를 사용 합니다. null 허용 가능한 값 을 처리하는 이 Kotlin 관용적 방법을 참조하십시오. 또한 Kotlin에서 nullable 또는 빈 목록 을 처리하는 관용적 방법 )
스트림 재사용
Kotlin에서는 컬렉션 유형에 따라 두 번 이상 소모 될 수 있는지 여부에 따라 다릅니다. Sequence
는 매번 새로운 iterator를 생성하고 "once use"를 지정하지 않는 한, 동작 할 때마다 시작으로 재설정 할 수 있습니다. 따라서 다음은 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:
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
를 한 번 반복하도록 constrainOnce()
하려면 Sequence
constrainOnce()
함수를 다음과 같이 사용할 수 있습니다.
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.
참조 :
- Iterable의 확장 함수에 대한 API 참조
- 배열의 확장 함수에 대한 API 참조
- List의 확장 함수에 대한 API 참조
- Map에 대한 확장 기능을 위한 API 참조
목록에 누적 된 이름
// 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() // ", " is used as separator, by default
직원의 급여 총액 계산
// 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> 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 { 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 { item.startsWith('o') }
가장 짧은 문자열 찾기
// Java:
String shortest = items.stream()
.min(Comparator.comparing(item -> item.length()))
.get();
// Kotlin:
val shortest = items.minBy { it.length }
다른 종류의 스트림 # 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 - 지연된 문자열 목록을 반복하고, 값을 매핑하고, Int로 변환하고, 최대 값을 찾습니다.
// 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 - 게으른 Ints 스트림 반복, 값 매핑, 결과 인쇄
// 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 { it.startsWith('t') }
스트림의 작동 원리 - 필터, 대문자,리스트 정렬
// 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)
또는 ifPresent라는 String에 확장 함수를 만듭니다.
// 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()
: apply()
함수
참고 항목 : 확장 함수
또한보십시오 : ?.
안전한 호출 연산자 및 일반적으로 null 가능성 : http://stackoverflow.com/questions/34498562/in-kotlin-what-is-the-idiomatic-way-to-deal-with-nullable-values-referencing-o/34498563 # 34498563
예제 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}
좋아, 여기 코 틀린에 대한 더 많은 관심이있다. 먼저 컬렉션 / 시퀀스에서 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!!
일치하는 값을 조인하여 목록을 축소하고 Person
인스턴스에서 joinToString
으로 이동하기 위해 joinToString
에 대한 변환기를 제공해야 Person.name
.
모범 사례 # 7a 수집 - 이름 매핑, 구분 기호와 결합
// Java (verbose):
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
// 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{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)
그러나 실제로 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
및 적절한 기본 유형에 대해 작업 할 수 있도록이 확장을 만들 수도 있습니다.