1. 필터와 맵
자바에서 Stream과 함께 사용되는 filter, map 함수를
코틀린에서는 아래와 같이 사용한다.
// 익명함수를 전달해서 필터링
val apples = fruits.filter { fruit -> fruit.name == "사과" }
// 인덱스 또한 받아서 처리 가능
val apples2 = fruits.filterIndexed { index, fruit ->
println(index)
fruit.name == "사과"
}
// 자바에서 map의 반환은 Stream 이지만 코틀린에서는 List를 반환한다.
// 그래서 map 이후 .collect(Collectors.toList)를 안해도 된다.
val applePrices = fruits.filter{fruit -> fruit.name=="사과"}
.map { fruit -> fruit.price }
코틀린에서는 filter와 map 같은 고차 함수들이 Stream을 사용하지 않고도 컬렉션에서 직접 사용할 수 있습니다.
코틀린은 컬렉션 API에 내장된 고차 함수들을 제공하여,
List, Set, Map 등과 같은 컬렉션에서 매우 쉽게 데이터를 처리할 수 있도록 합니다.
변경전 예시)
private fun filterFruits(
fruits: List<Fruit>,
funcName: (Fruit) -> Boolean
): List<Fruit> {
val results = mutableListOf<Fruit>()
for (fruit in fruits) {
if (funcName(fruit)) {
results.add(fruit)
}
}
return results
}
변경후)
private fun filterFruits(
fruits: List<Fruit>,
funcName: (Fruit) -> Boolean
): List<Fruit> {
return fruits
.filter(funcName) // 바로 익명함수를 전달해줄수 있다.
// .filter { fruit -> funcName(fruit) } 이처럼 작성할수도있지만 위가 더 깔끔하다.
}
2. 다양한 컬렉션 처리 기능
all -> 조건을 모두 만족하면 true 그렇지 않으면 false
val isAllApple = fruits.all {fruit -> fruit.name == "사과"}
none -> 조건을 모두 불만족하면 true 그렇지 않으면 false
val isAllApple = fruits.none {fruit -> fruit.name == "사과"}
any -> 조건을 하나라도 만족하면 true 그렇지 않으면 false
val isAllApple = fruits.any {fruit -> fruit.name == "사과"}
val fruitCount = fruits.count();
//val fruitCount = fruits.size; count()와 동일
// (오름차순) 정렬을 한다.
val sorted = fruits.sortedBy { fruit -> fruit.price }
// (내림차순) 정렬을 한다.
val sortedDesc = fruits.sortedByDescending { fruit -> fruit.price }
// 변형된 값을 기준으로 중복을 제거한다. 이 예시는 이름을 기준으로 중복제거
val distinct = fruits.distinctBy { fruit -> fruit.name }
// 첫번째 값 가져오기 -> 무조건 null이 아니어야한다. null이라면 exception 발생
val first = fruits.first()
// 첫번째 값 또는 null을 가져온다.
val firstNullable = fruits.firstOrNull()
// 마지막 값 가져오기 -> 무조건 null이 아니어야한다. null이라면 exception 발생
val last = fruits.last()
// 마지막 값 또는 null을 가져온다.
val lastNullable = fruits.lastOrNull()
3. List를 Map으로
// List -> Map , 반환되는 값을 키값으로 하는 Map 반환 , 즉 과일이름으로 Map 만들기
val fruitMap = fruits.groupBy { fruit -> fruit.name }
fun main() {
for (entry in fruitMap.entries) {
println("key : ${entry.key} , value : ${entry.value}")
}
}
/*
groupBy같이 map을 반환해준다.
groupBy는 value에 List가 들어갈 수 있지만 associateBy는 동일키를 가진 value를 만났을때
가장 마지막에 만난 요소로 대체해준다. 즉 value는 단일객체가 들어간다.
*/
val fruitMap2 = fruits.associateBy { fruit -> fruit.name }
fun main() {
for (entry in fruitMap2.entries) {
println("key : ${entry.key} , value : ${entry.value}")
}
}
사과라는 동일한 키를 가지고 있는 value를 만나면 가장 마지막에 만나는 요소로 넣어준다.
// map을 만들때 키에 대한 설정말고 value에 대한 설정도 할 수 있다.
val fruitMap3 = fruits.groupBy ({fruit -> fruit.name},{fruit -> fruit.price})
fun main() {
for (entry in fruitMap3.entries) {
println("key : ${entry.key} , value : ${entry.value}")
}
}
// map 또한 위에서 사용한 filter 같은 기능을 사용할 수 있다.
val fruitMap3 = fruits.groupBy ({fruit -> fruit.name},{fruit -> fruit.price})
.filter { (key,value) -> key == "사과" }
fun main() {
for (entry in fruitMap3.entries) {
println("key : ${entry.key} , value : ${entry.value}")
}
}
구조 분해 선언
val fruitMap3 = fruits.groupBy ({fruit -> fruit.name},{fruit -> fruit.price})
.filter { (key,value) -> key == "사과" }
위의 코드는 잘 동작하지만 [ 구조 분해 선언 덕분에 ]
val fruitMap3 = fruits.groupBy ({fruit -> fruit.name},{fruit -> fruit.price})
.filter { key -> key == "사과" }
위의 코드는 동작하지않는다. [ 에러메시지 -> Operator '==' cannot be applied to 'Map.Entry<String!, List<Int>>' and 'String' ]
key에 Map의 Entry가 들어가기 때문이다.
리스트였으면 요소의 순서대로 하나씩 요소가 key에 들어갔을텐데
Map이니까 하나의 entry가 순서대로 들어가게 된다.
val fruitMap3 = fruits.groupBy ({fruit -> fruit.name},{fruit -> fruit.price})
.filter { entry -> entry.key == "사과" }
이렇게 코드를 작성하면 잘 들어간다.
4. 중첩된 컬렉션 처리
val fruitsInList: List<List<Fruit>> = listOf(
listOf(
Fruit("사과", 1000),
Fruit("사과", 1200),
Fruit("사과", 1200),
Fruit("사과", 1500),
Fruit("바나나", 2500),
Fruit("수박", 10000)
),
listOf(
Fruit("사과", 1000),
Fruit("사과", 1200),
Fruit("사과", 1200),
Fruit("바나나", 3000)
),
listOf(
Fruit("사과", 1000),
Fruit("사과", 1200),
Fruit("수박", 10000)
)
)
중첩 컬렉션을 처리할때 java 에서는 flatmap을 이용해서 처리했다.
코틀린에서 가능하다.
val samePriceFruits = fruitsInList.flatMap { list->
list.filter { fruit -> fruit.name =="사과" }
}
람다안에 람다가 들어가는 형식인데
위의 코드는 리팩토링이 가능하다.
public class Fruit {
private final String name;
private final int price;
public Fruit(String name, int price) {
this.name = name;
this.price = price;
}
// 중략
public boolean isApple() {
return this.name.equals("사과");
}
//중략
}
Fruit 클래스에 isApple()라는 이름이 사과임을 체크하는 메서드를 만들고
val List<Fruit>.appleFilter:List<Fruit>
get() = this.filter({ fruit -> fruit.isApple })
val applesList = fruitsInList.flatMap { list->
list.appleFilter
}
List<Fruit>에 확장함수인 appleFilter를 생성해준다.
그리고 flatMap 내부에서 해당 확장함수를 쓰는형식으로 리팩토링 가능하다.
위의 Fruit는 자바클래스로 진행했지만 코틀린 Fruit 클래스라면 isApple을 프로퍼티로 만들어서 사용하면된다.
val isApple:Boolean
get() = this.name == "사과"
중첩 컬렉션을 1차원 컬렉션으로 바꿀때
List<List<Fruit>>를 List<Fruit>바꾸려고할때
flatten()을 사용한다.
val resultList = fruitsInList.flatten()
for (fruit in resultList) {
println(fruit.toString())
}
모든 리스트를 펼쳐서 하나의 리스트로 만들어준다.
'인프런 > 자바 개발자를 위한 코틀린 입문' 카테고리의 다른 글
13) 코틀린의 scope function (0) | 2025.01.12 |
---|---|
12) 코틀린의 이모저모 (0) | 2025.01.11 |
10) 코틀린에서 람다를 다루는 방법 (0) | 2025.01.10 |
9) 코틀린에서 다양한 함수를 다루는 방법 (0) | 2025.01.10 |
8) 코틀린에서 배열과 컬렉션을 다루는 방법 (0) | 2025.01.09 |
댓글