-
[Kotlin] Collection, 함수, 람다, 기타 ..Backend/코틀린 2023. 3. 26. 22:15반응형
출처 - 인프런 자바개발자를 위한 코틀린 입문 강의
https://www.inflearn.com/course/java-to-kotlin
CH15. 배열과 컬렉션
배열
val array = arrayOf(100, 200) for (i in array.indices) { println("${i} ${array[i]}") } for ((idx, value) in array.withIndex()) { println("${idx} ${value}") } array.plus(300)
- 인덱스 가져오기 : array.indices
- 인덱스랑 값 같이 가져오기 : array. withIndex()
- 배열에 값 추가하기 : array.plus
Collection
- 코틀린은 불변, 가변을 지정해주어야한다. (기본은 불변)
- 가변컬렉션 : 컬렉션에 element 추가, 삭제 가능
- listOf, setOf, mapOf
- 불변컬렉션 : 컬렉션에 element 추가, 삭제 불가
- mutableListOf, mutableSetOf ,mutableMapOf
- 가변컬렉션 : 컬렉션에 element 추가, 삭제 가능
- 불변이라고 하더라도 reference타입 안에 있는 필드는 바꿀 수 있다.
// 선언 // 기본적으로는 불변 val numbers = listOf(100, 200) val emptyList = emptyList<Int>() // 비어있고 타입 추론이 불가능하다면 <>에 타입을 적어주어야함 // 가변리스트 val numbers = mutableListOf(100, 200) numbers.add(300) // 가져오기 numbers.get(0) numbers[0] // for문 for (number in numbers) { println(number) } for ((idx, value) in numbers.withIndex()) { }
map
val oldMap = mutableMapOf<Int, String>() // 타입 지정 oldMap[1] = "MONDAY" oldMap[2] = "TUSEDAY" mapOf(1 to "MONDAY", 2 to "TUESTDAY") // to라는 중위호출 사용 for (key in oldMap.keys) { println(key) println(oldMap[key]) } for ((key,value)) in oldMap.entries) { println(key) println(value) }
컬렉션의 null가능성
- List<Int?>
- 리스트에 null이 들어갈 수 있지만 리스트는 not null
- List<Int>?
- 리스트에 null이 들어갈 수 없지만 리스트는 nullable
- List<Int?>?
- 둘다 nullable
Java와 같이 사용하는 Collection
- 자바에서 코틀린의 불변 컬렉션을 가져와서 구분없이 리스트에 값을 추가하고 다시 코틀린에 돌려줄 수 있음.
- 아니면 코틀린 쪽에서 자바의 Collection.unmodifiableXX이런거로 사용해서 방어를 할 수도 있을것
CH16. 함수
확장함수
//EX. String클래스 안에 있는 것 같은 확장함수 fun main() { val str = "ABC" println(str.lastChar()) } fun String.lastChar(): Char { return this[this.length-1] }
- String클래스를 확장하므로 String. 으로 쓰고 (수신객체 타입) this (수신 객체) 를 통해 실제 클래스 안의 값에 접근함
- 확장함수는 원본 클래스의 private, protected에 접근하지 못함
- 원래 있는 클래스와 같은 시그니처 메서드를 호출하면 멤버함수(원본)가 호출된다.
- 확장함수를 만들고 나중에 멤버함수에 같은 메서드가 생긴다면 오류가 생길 수 있음 (주의)
- 오버라이드 된 확장함수
-
val tran: Tran = Train() train.isExpensive() // Train의 확장함수 val srt1: Train = Srt() srt1.isExpensive() // Train의 확장함수 val srt2: Srt = Srt() srt2.isExpensive() // Srt의 확장함수
- 해당 변수의 현재 타입(즉, 정적인 타입)에 의해 어떤 확장함수를 호출할지 결정됨
-
- 확장프로퍼티
- 확장 함수와 비슷하게 프로퍼티도 만들 수 있음(ex. custom getter)
infix함수(중위함수) : 변수 {함수이름} argument
- 함수를 호출하는 새로운 방법
fun Int.add(other: Int): Int { return this + other } infix fun Int.add2(other: Int):Int { return this + other } // 3.add(4) // 3.add2(4) 3 add2 4
inline함수
- 함수가 호출되는 대신 함수가 호출하는 지점에 본문을 그대로 복사하는것
- 바이트코드로 봤을 때 알 수 있음
- 함수를 파라미터로 전달할 때 오버헤드를 줄일 수 있다.
- 성능측정과 함께 신중하게 사용되어야 한다.
fun main() { 3.add(4) } inline fun Int.add(other: Int): Int { return this + other }
지역함수
- 함수 안에 함수를 선언할 수 있다.
- 함수로 추출하면 좋을 것 같은데 이 함수를 지금 함수에서만 사용하고 싶을 때
- 단 depth가 깊어지기도 하고 그렇게 깔끔하진 않을 수도 있음...
fun createPerson(~~): Person { fun validateName(name: String, fieldName: String) { if (name.isEmpty) { throw IllegalArgumentException() } } validateName(firstName, "firstName") validateName(lastName, "lastName") return Person(firstName, lastName, 1) }
CH17. 람다
코틀린에서의 람다
- 코틀린에서는 함수가 그 자체로 값이 될 수 있다. (1급시민)
- 변수에 할당할 수도 있고, 파라미터로 넘길 수 있음
val fruits = listOf(..) // 람다 생성 방법 val isApple: (Fruit) -> Boolean = fun(fruit: Fruit): Boolean { return fruit.name == "사과" } val isApple2: (Fruit) -> Boolean = { fruit: Fruit -> fruit.name == "사과"} // 람다 호출 방법 isApple(fruits[0]) isApple.invoke(fruits[0])
private fun filterFruits( fruits: List<Fruit>, filter: (Fruit) -> Boolean ): List<Fruit> { val results = mutableListOf<Fruit>() for (fruit in fruits) { if (filter(fruit)){ results.add(fruit) } } return results }
// 다 가능 filterFruits(fruits, isApple) filterFruits(fruits, isApple2) // 코틀린 스럽게 바꿔보기 filterFruits(fruits, { fruit: Fruit -> fruit.name == "사과"}) filterFruits(fruits) { fruit: Fruit -> fruit.name == "사과"} // 1 filterFruits(fruits) { fruit -> fruit.name == "사과"} // 2 filterFruits(fruits) { it.name == "사과" } //3
1. 소괄호 밖으로 중괄호 람다 빼기
함수(파라미터1, 파라미터2, ..., {중괄호파라미터})
- 함수(파라미터1, 파라미터2, ...) {중괄호파라미터} 이렇게 쓸 수 있다.
- 마지막 파라미터가 중괄호로 되어있으면 소괄호 밖으로 중괄호를 빼면 함수 호출할 때 중괄호가 가장 마지막 파라미터로 들어감
- 람다는 여러줄 작성할 수 있고 마지막 줄의 결과가 람다의 반환값임
2. 타입추론 가능한거 타입 빼기
3. 파라미터가 한개인 경우 익명함수에서 it 이라고 쓸 수 있음
- 그래도 it 말고 이름 써주는걸 추천한다고 함
Closure
- 자바
-
String targetFruitName = "바나나"; targetFruitName = "수박"; filterFruits(fruits, (fruit) -> targetFruitName.equals(fruit.getname())); // 자바에서는 final 아닌 변수를 람다에서 쓰는것에서 오류 발생시킴
-
- 코틀린
-
var targetFruitName = "바나나" targetFruitName = "수박" filterFruits(fruits) { it.name == targetFruitName} // 됨
- 코틀린에서는 람다가 시작하는 지점에 참조하는 모든 변수들을 모두 포획하여 가지고 있음. 이렇게 해야만 람다를 진정한 일급시민으로 간주할 수 있기 때문
- 이 데이터 구조를 Closure라고 부른다.
-
try with resources
public inline fun <T : Closeable?, R> T.use(block: (T) -> R):R { } // Cloesable 구현체 T에 대한 확장함수 // inline함수 // 람다를 받게 만들어진 함수
CH18. 컬렉션을 함수형으로 다루는 방법
필터와 맵
filter
val apples = fruits.filter { fruit -> fruit.name == "사과"}
filterIndexed
val apples = fruits.filterIndexed { idx, fruit -> println(idx) fruit.name == "사과" }
map
val applePrices = fruits.filter { fruit -> fruit.name == "사과"} .map { fruit -> fruit.currentPrice }
mapIndexed
val applePrices = fruits.filter { fruit -> fruit.name == "사과"} .mapIndexed { idx, fruit -> println(idx) fruit.currentPrice }
mapNotNull
val values = fruits.filter { fruit -> fruit.name == "사과"} .mapNotNull { fruit -> fruit.nullOrValue() }
다양한 컬렉션 처리 기능
- all : 조건을 모두 만족하면 true 그렇지 않으면 false
-
val isAllApple = fruits.all { fruit -> fruit.name == "사과" }
-
- none : 조건을 모두 불만족하면 true 그렇지않으면 false
- any : 조건을 하나라도 만족하면 true 그렇지 않으면 false
- count : 개수를 센다
- sortedBy : (오름차순) 정렬을 한다.
- sortedByDescending : 내림차순
- distincyBy : 변형된 값을 기준으로 중복을 제거한다.
- first : 첫번째 값을 가져온다. (null이 무조건 아니어야함)
- firstOrNull : 첫번째 값 또는 null을 가져온다.
- last , lastOrNull
List를 Map으로
- groupBy
- 과일이름 (Key) -> List<과일> (value) Map
-
val map: Map<String, List<Fruit>> = fruits.groupBy {fruit -> fruit.name}
-
- 과일이름 (key) -> List<출고가> (value) Map
-
val map: Map<String, List<Long>> = fruits .groupBy( {fruit -> fruit.name}, fruit -> fruit.factoryPrice})
-
- 과일이름 (Key) -> List<과일> (value) Map
- associateBy : id를 통해서 중복되지 않는 키를 가지고 뭔가 매핑해야할 때
- id (key) -> 과일(Value)
-
val map: Map<Long, Fruit> = fruits.associateBy { fruit -> fruit.id }
-
- id -> 출고가
-
val map: Map<Long, Long> = fruits .associateBy({ fruit -> fruit.id}, {fruit -> fruit.factoryPrice})
-
- id (key) -> 과일(Value)
중첩된 컬렉션 처리
val fruitsInList: List<List<Fruit>> = listOf( listOf(), listOf(), ... )
- flatMap
- 출고가와 현재가가 동일한 과일을 고를 때
-
val samePriceFruits = fruitsInList.flatMap { list -> list.filter {fruit->fruit.factoryPrice == fruit.currentPrice} }
- 리팩토링
-
val samePriceFruits = fruitsInList.flatMap {list -> list.samePriceFilter} // List에 대한 확장함수 만들어서 호출 val List<Fruit>.samePriceFilter: List<Fruit> get() = this.filter(Fruit::isSamePrice)
-
- 출고가와 현재가가 동일한 과일을 고를 때
- flatten
- 중첩된 컬렉션 해제
CH19. 코틀린 이모저모
Type Alias 긴 이름의 클래스 혹은 함수타입 네이밍
typealias FruitFilter = (Fruit) -> Boolean fun filterFruits(fruits: List<Fruit>, filter: FruitFilter) {}
as import : 클래스, 함수 import할 때 이름을 바꾸는 기능
import ~~~.printHelloWorld as printHelloWorldA import ~~~.printHelloWorld as printHelloWolrdB fun main() { printHelloWorldA() printHelloWorldB() }
구조분해 : 복합적인 값을 분해하여 여러 변수를 한 번에 초기화하는것
data class Person( val name: String, val age: Int ) val person = Person("강지수", 27) val (name, age) = person println("이름 : ${name}, 나이 : ${age}")
- Data Class는 component{N}이란 함수를 자동으로 만들어준다.
-
// 구조분해를 풀어보면 이렇게 됨. name, age를 인식하는 것은 아님 val name = person.compoent1() val age = person.component2()
-
- Data Class가 아닌 경우 componentN함수를 구현하면 된다. operator 로 연산자 오버로딩을 해줘야한다.
-
class Person( val name: String ) { operator fun component1(): String { return this.name } }
-
- for ( (key, value) in map.entries) 도 구조분해 문법의 일종이다.
jump와 Label
- jump
- return : 기본적으로 가장 가까운 enclosing function 또는 익명함수로 값이 반환
- break : 가장 가까운 루프가 제거
- continue : 가장 가까운 루프를 다음 step으로 보냄
- forEach에서는 break, continue를 쓸 수 없음! (run + return or 일반 for, while문)
- label
- 라벨 명시하면 jump@label 가능...
- 사용하지 않는 것을 강력추천!!!
TakeIf와 TakeUnless
- takeIf : 주어진 조건을 만족하면 그 값이, 그렇지 않으면 null이 반환된다.
- takeUnless : 주어진 조건을 만족하지 않으면 그 값이, 그렇지 않으면 null이 반환
CH20. scope function
- scope function : 일시적인 영역을 형성하는 함수
// 기본 코드 fun printPerson(person: Person?) { if (person != null) { println(person.name) println(person.age) } }
- ex) let (람다를 받아 람다 결과를 반환하는 확장함수) 사용
-
// 기본 코드 fun printPerson(person: Person?) { person?.let { println(it.name) println(it.age) } }
-
inline fun <T, R> T.let(block: (T) -> R): R { return block(this) }
-
- 종류 , usage
- let
- 하나이상의 함수를 call chain 결과로 호출할 때
- non-null 값에 대해서만 code block을 실행시킬 때
- 일회성으로 제한된 영역에 지역변수를 만들 때 (는 메서드분리가 나을지도)
- run
- 객체 초기화와 반환 값의 계산을 동시에 해야할 때
- also
- 객체를 수정하는 로직이 call chain 중간에 필요할 때
- apply
- 객체 설정을 할 때 객체 수정하는 로직이 call chain 중간에 필요할 때(ex. Test Fixture 만들 때)
- with (확장함수 X 람다 )
- this 사용해 접근하고 this는 생략 가능
- 특정 객체를 다른 객체로 변환해야 하는데, 모듈 간의 의존성에 의해 정적 팩토리 혹은 toClass함수를 만들기 어려울 때
- let
it 사용 it: 생략 불가, 다른 이름 붙일 수 있음 this 사용 this : 생략 가능, 다른 이름 붙일 수 없음 람다의 결과 let run 객체 그 자체 (람다 결와 무관) also apply val value1 = person.let { //value1 = age it.age } val value2 = person.run { //value2 = age this.age } val value3 = person.also { //value3 = person it.age } val value4 = person.apply { //value4 = person this.age }
// let은 일반함수를 받음. 파라미터를 받고 파라미터에 대한 이름을 직접 넣어줄 수 있음 inline fun <T, R> T.let(block: (T) -> R): R { return block(this) } // run은 확장함수를 받음. 본인 자신을 this로 호출하고 생략할 수 있음 inline fun <T, R> T.run(block: T.() -> R):R { return block() }
'Backend > 코틀린' 카테고리의 다른 글
[Kotlin] 클래스, 상속, 접근제어, object (0) 2023.03.20