고딩왕 코범석
FP 본문
Index
코틀린에서 배열과 컬렉션을 다루는 방법
배열
잘 사용하지 않는다.
val arr = arrayOf(100, 200)
val plusArr = arr.plus(300)
for ((i, value) in plusArr.withIndex()) {
println("${i}, ${value}")
}
코틀린에서의 Collection - List, Set, Map
컬렉션을 만들어줄 때 불변인지, 가변인지를 설정해야한다.
코틀린에서는 Mutable 키워드가 붙은 인터페이스가 있다. 그럼 불변 컬렉션과 가변 컬렉션의 차이는 무엇일까?
- 가변 컬렉션 : 컬렉션에 element를 추가, 삭제할 수 있다.
- 불변 컬렉션 : 컬렉션에 element를 추가, 삭제할 수 없다.
유의 사항
- 불변 컬렉션이라 하더라도 Reference Type인 Element의 필드는 바꿀 수 있다.
- 즉, 컬렉션 자체가 변경되는게 불가능하고 컬렉션 내 객체의 조작은 가능하다.
fun main() {
val numbers = listOf(100, 200)
// 타입을 명시해줘야 함
val emptyList = emptyList<Int>()
// 만약 추론할 수 있다면 타입 생략 가능
printNumbers(emptyList())
}
fun printNumbers(emptyList: List<Int>) {
// logic
}
조작은 다음과 같이 한다.
fun main() {
val numbers = listOf(100, 200)
val i = numbers[0] // 배열처럼 접근
println(i)
for (number in numbers) {
println(number)
}
for ((idx, number) in numbers.withIndex()) {
println("$idx, $number")
}
}
가변 리스트를 만들고 싶다면? mutableListOf()
를 사용하며 ArrayList
와 동일하다.
fun main() {
val numbers = mutableListOf(100, 200)
numbers.add(300)
}
우선, 불변으로 생성한 다음 필요에 따라 가변으로 바꾸는 것을 권장한다.
Set
의 경우 자료구조의 차이점 외에는 List
와 비슷하다. 가변 집합을 만들고 싶다면 mutableSetOf()
을 사용하고 타입은
LinkedHashSet
이다.
Map
은 다음과 같이 작성한다. 차이점이 있다면 map변수명[key] = value
로 put()
을 호출할 수 있다.
to
는 Pair
를 반환하는 중위호출 함수이다.
fun main() {
val mutableMap = mutableMapOf<Int, String>()
mutableMap[1] = "one"
mutableMap[2] = "two"
for (key in mutableMap.keys) {
println(key)
println(mutableMap[key])
}
for ((key, value) in mutableMap.entries) {
println(key)
println(value)
}
val map = mapOf(1 to "one", 2 to "two")
}
컬렉션의 null 가능성, 자바와 함께 사용하기
컬렉션을 다룰 때는 null 가능성에 대해 알아야한다.
List<Int?>
- element가
Int
형이나null
이 올 수 있다.
- element가
List<Int>?
- element는
Int
만 올 수 있고List
자체가null
일 수 있다.
- element는
List<Int?>?
- element가
Int
형이나null
이 올 수 있고,List
자체가 null일 수 있다.
- element가
자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않는다. 코틀린에서 만든 불변 컬렉션을 자바 코드에서 가져온다면 자바 코드에서는 element를 삽입, 삭제할 수 있다.
또 자바는 null 가능성에 대해 판단하지 않는다. 코틀린에서 List
이를 감안하여 방어 로직을 작성하거나 코틀린에서 Collections.unmodifiableXXX()
를 활용해야한다.
코틀린에서 자바 코드를 가져올 때는 플랫폼 타입에도 신경써야한다. 예를 들어 자바의 ListList<Int?>
인지
List<Int>?
인지 List<Int?>?
인지 알 수 없다. 이 때는 자바 코드를 보며 맥락을 파악하고 자바 코드를 가져오는
지점에
대해 Wrapping하는 것이 좋다.
코틀린에서 다양한 함수를 다루는 방법
확장함수
코틀린은 자바와 100%호환되는 것을 목표로 하고 있다. 이에 대한 고민 중 하나가 자바 코드위에 코틀린 코드를 덧붙이는 것인데, 이를 해결하기 위해 ‘클래스안에 메서드처럼 호출할 수 있지만 함수는 밖에서 만들 수 있게 하자’ 라는 개념이 나온다.
// 함수 앞에 타입은 수신객체 타입
fun String.lastChar(): Char {
// this는 수신객체라고 한다.
return this[this.length - 1]
}
그렇다면 ‘확장함수가 public
이고 확장함수의 수신객체 클래스 내부의 private
함수를 가져오면 캡슐화가 깨지지 않나?’
라는
생각이 들 수 있다.
- 확장함수는 애초에
private
,protected
멤버를 가져올 수 없도록 했다.
멤버함수와 확장함수의 시그니처가 같다면 누구를 호출할까?
- 멤버함수를 우선적으로 호출한다.
- 확장함수를 만들었지만 다른 기능의 똑같은 시그니처를 가진 멤버함수가 생긴다면 오류가 발생할 수 있으니 유의해야한다.
확장함수가 오버라이드된다면?
- 해당 변수의 현재 타입에 의해 어떤 확장함수가 호출할지 결정된다.
자바에서는 코틀린의 확장함수를 어떻게 호출?
- 정적 메서드를 호출하는 것 처럼 사용 가능하다.
확장 프로퍼티
확장 프로퍼티의 원리는 확장함수 개념과 custom getter 개념을 합친 것과 같다.
val String.lastChar: Char
get() = this[this.length - 1]
infix 함수
중위함수는 함수를 호출하는 새로운 방법이다. ex) downTo
, step
변수랑 매개변수가 각각 하나씩만 있을 경우 ‘변수 함수명 argument’
방식으로 정의할 수 있다.
확장함수, 멤버함수에도 infix
키워드를 붙일 수 있다.
fun main() {
val a = 19
println(a.add(2)) // 21
println(a add 2) // 21
}
infix fun Int.add(other: Int) = this + other
inline 함수
함수가 호출되는 대신 함수를 호출한 지점에 함수 본문을 그대로 복사하고 싶은 경우 사용한다.
함수를 파라미터로 전달할 때 오버헤드를 줄일 수 있어 사용한다. 하지만 성능 측정과 함께 신중히 사용해야한다.
inline fun Int.add(other: Int) = this + other
지역함수
함수안의 함수를 의미한다. 아래는 기존의 자바 코드를 코틀린의 지역함수로 변경한 코드이다.
함수 내에서 중복 로직을 함수로 추출하면 좋을 것 같은데 추출한 함수를 함수 내에서만 사용 가능하도록 하고싶을 때 사용하면 좋다.
// firstName, lastName 검증 로직 중복
fun createUser(firstName: String, lastName: String): User {
if (firstName.isEmpty()) {
throw Exception()
}
if (lastName.isEmpty()) {
throw Exception()
}
return Person(firstName, lastName)
}
// 지역함수를 통해 중복 제거
fun createUser(firstName: String, lastName: String): User {
fun validate(name: String) {
if(name.isEmpty()) {
throw Exception()
}
}
validate(firstName)
validate(lastName)
return Person(firstName, lastName)
}
코틀린에서 람다를 다루는 방법
코틀린에서의 람다
코틀린에서는 함수가 그 자체로 값이 될 수 있다. 즉, 변수로 할당될 수 있고 파라미터로 넘길 수도 있다.
// 이름 없는 함수를 작성해 변수에 할당
val isApple = fun(fruit: Fruit) = fruit.name == "사과"
// 중괄호와 화살표를 통해 간결하게 선언
val isApple2 = {
fruit: Fruit -> fruit.name == "사과"
}
isApple.invoke(fruits[0])
isApple2(fruits[0])
// 함수의 타입 표기 가능 (파라미터타입) -> 리턴타입
val isApple2: (Fruit) -> Boolean = {
fruit: Fruit -> fruit.name == "사과"
}
함수의 마지막 파라미터가 함수이며 함수 파라미터가 한개일 경우 아래와 같이 간단하게 작성 가능하다.
fun main() {
val fruits = listOf(
Fruit("사과", 1_000L),
Fruit("사과", 2_000L),
Fruit("바나나", 1_000L),
Fruit("바나나", 2_000L),
Fruit("바나나", 3_000L),
Fruit("파인애플", 3_000L),
Fruit("파인애플", 2_000L),
)
// 컴파일을 통해 추론이 가능하여 it로 간단하게 작성 가능
val filterFruits = filterFruits(fruits) { it.name == "사과" }
for (fruit in filterFruits) {
println(fruit)
}
}
private fun filterFruits(
fruits: List<Fruit>,
nameFilter: (Fruit) -> Boolean,
): List<Fruit> {
val filteredFruits = mutableListOf<Fruit>()
for (fruit in fruits) {
if (nameFilter(fruit)) {
filteredFruits.add(fruit)
}
}
return filteredFruits
}
Closure
자바에서는 다음 코드는 컴파일 에러가 발생한다.
String targetFruitName = "바나나";
targetFruitName = "수박";
filterFruits(fruits, (fruit) -> targetFruitName.equals(fruit.getName()));
Variable used in lambda expression should be final or effectively final
자바에서는 람다를 쓸 때 사용할 수 있는 변수에 제약이 있다. 하지만 코틀린에서는 아무 문제 없이 동작한다. 왜 가능할까?
- 코틀린에서는 람다가 시작하는 지점에 참조하고 있는 변수들을 모두 포획하여 그 정보를 가지고 있다.
- 이렇게 해야 람다를 일급 시민으로 간주할 수 있는데, 이러한 데이터 구조를
**Closure**
라 부른다.
try with resource (use 확장함수 다시보기)
코틀린의 use 확장함수를 살펴보자.
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {...}
먼저 Closeable
구현체에 대한 확장함수이며 인라인 함수임을 알 수 있다. 매개변수에는 block이라는 이름을 가진 함수(람다)를 받고 있다.
코틀린에서 컬렉션을 함수형으로 다루는 방법
다양한 컬렉션 처리 기능
all
: 조건을 모두 만족하면true
, 그렇지 않으면false
none
: 조건을 모두 만족하지 않으면true
, 하나라도 만족하면false
any
: 조건을 하나라도 만족하면true
, 그렇지 않으면false
count
:size
와 같다.sortedBy
,sortedByDescending
: 정렬과 역순 정렬distinctBy
: 람다의 리턴 값first
,last
: 처음 혹은 마지막 값을 가져온다. (null이 아니어야하고 비어있는 컬렉션이 비어있을 경우 예외 발생)firstOrNull
,lastOrNull
: 컬렉션이 비어도 가져올 수 있음
List를 Map으로
groupBy
: 해당 함수의 람다로 넘긴 필드가key
, 만들어진key
에 해당하는 객체가 value 리스트에 속하게 된다. (그룹핑)associateBy
:groupBy
와 달리 단일 객체만 들어가게 된다. 만약 같은key
를 가진 객체들이 여러개 있을 경우 가장 나중의 객체가 들어간다.
중첩된 컬렉션 처리
flatMap
: 중첩된 리스트를 단일 리스트로 변경해준다. 람다가 중첩된다.flatten
:flatMap
과 동일하나 단일 리스트로만 변경이 가능하다.
'Language & Framework > Kotlin' 카테고리의 다른 글
특성 (0) | 2022.08.05 |
---|---|
OOP (0) | 2022.08.02 |
코드 제어 (0) | 2022.07.30 |
변수, 타입, 연산자 (0) | 2022.07.30 |