ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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
    • 불변이라고 하더라도 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})
    • 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})

    중첩된 컬렉션 처리

    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함수를 만들기 어려울 때

     

      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

    댓글

Designed by Tistory.