ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 5. 추가적으로 알아두어야 할 코틀린 특성
    Kotlin/자바 개발자를 위한 코틀린 입문 2023. 7. 29. 10:31

    19. 코틀린의 이모저모

    1. Type Alias 와 as import

    - 긴 이름의 클래스 혹은 함수 타입이 있을 때 축약하거나 더 좋은 이름을 쓰고 싶다!

    typealias FruitFilter = (Fruit) -> Boolean
    fun filterFruits2(fruit: List<Fruit>, filter: FruitFilter) {
        
    }
    
    // 이름 긴 클래스를 컬렉션에 사용할 때도 간단히 줄일 수 있다.
    data class UltraSuperGuardianTribe(
        val name: String
    )
    typealias USGTMap = Map<String, UltraSuperGuardianTribe>

     

    - 다른 패키지의 같은 이름 함수를 동시에 가져오고 싶다면?!
    - as import : 어떤 클래스나 함수를 임포트할 때 이름을 바꾸는 기능

    import com.lannstark.lec19.a.printHelloWorld as printHelloWorldA
    import com.lannstark.lec19.b.printHelloWorld as printHelloWorldB
    
    fun main() {
        printHelloWorldA()
        printHelloWorldB()
    }

     

    2. 구조분해와 componentN 함수

    - 구조분해 : 복합적인 값을 분해하여 여러 변수를 한 번에 초기화하는 것

    val person = Person("박현민", 100)
    val (name, age) = person

        ㆍ순서가 정해져있다. 첫번째  프로퍼티를 첫번째 변수, 두번째 프로퍼티를 두번째 변수에 넣어준다.

    - Data Class는 componentN이란 함수도 자동으로 만들어준다!

    val name = person.component1()
    val age = person.component2()

     

    - Data Class가 아닌데 구조분해를 사용하고 싶다면, componentN 함수를 직접 구현해줄 수도 있다.

    class Person2(
        val name: String,
        val age: Int
    ) {
        operator fun component1(): String {
            return this.name
        }
        
        operator fun component2(): Int {
            return this.age
        }
    }

     

    val map = mapOf(1 to "A", 2 to "B")
    for ((key, value) in map.entries) {
    }

    - 이 문법 역시 구조분해 이다.

     

    3. Jump와 Label 

    - return / break / continue

        ㆍreturn : 기본적으로 가장 가까운 enclosing function 또는 익명함수로 값이 반환된다.

        ㆍbreak : 가장 가까운 루프가 제거된다

        ㆍcontinue : 가장 가까운 루프를 다음 step으로 보낸다.

    - for 문 및 while 문에서 break, continue 기능은 동일하다.

        ㆍ단, forEach 문과 함께 break 또는 continue를 사용할 수 없다.

    - forEach문과 함께 break 또는 continue를 꼭 쓰고 싶다면?!

    run {
        numbers.map { number -> number + 1 }
            .forEach { number ->
                if (number == 3) {
                    return@run	// break 역할
                }
                println(number)
            }
    }
    numbers.map { number -> number + 1 }
        .forEach { number ->
            if (number == 3) {
                return@forEach	// continue 역할
            }
            println(number)
        }

    - break, continue를 사용할 때엔 가급적 익숙한 for문 사용을 추천한다.

     

    - 코틀린에는 라벨이라는 기능이 있다.

    - 특정 expression에 라벨이름@ 을 붙여 하나의 라벨로 간주하고 break, continue, return 등을 사용하는 기능

    loop@ for(i in 1..100) {
        for(j in 1..100) {
            if(j == 2) {
                break@loop
            }
            println("$i $j")
        }
    }

    - 라벨을 사용한 Jump는 사용하지 않는 것을 강력 추천한다.

        ㆍ복잡도가 증가하고 유지보수가 어려워진다.

     

    4. TakeIf와 TakeUnless

    fun getNumberOrNull(): Int? {
        return if (number <= 0) {
            null
        }else {
            number
        }
    }

    - Kotlin에서는 method chaning을 위한 특이한 함수를 제공한다.

    fun getNumberOrNullV2(): Int? {
        return number.takeIf { it > 0 }
    }

    - takeIf : 주어진 조건을 만족하면 그 값이, 그렇지 않으면 null이 반환된다.

     

    fun getNumberOrNullV3(): Int? {
        return number.takeUnless { it <= 0 }
    }

    - takeUnless : 주어진 조건을 만족하지 않으면 그 값이, 그렇지 않으면 null이 반환된다.

     

    정리

    - 타입에 대한 별칭을 줄 수 있ㄷ는 typealias 라는 키워드가 존재한다.

    - Import 당시 이름을 바꿀 수 있는 as import 기능이 존재한다.

    - 변수를 한 번에 선언할 수 있는 구조분해 기능이 있으며 componentN 함수를 사용한다.

    - for문, while문과 달리 forEach에는 break와 continue를 사용할 수 없다.

    - takeIf와 takeUnless를 활용해 코드양을 줄이고 method chaning 을 활용할 수 있다.

     

    20. 코틀린의 scope function

    1. scope function이란 무엇인가?

    - scope : 영역 / fucntion : 함수

    - scope function : 일시적인 영역을 형성하는 함수

    fun printPerson(person: Person?) {
    //    if(person != null) {
    //        println(person.name)
    //        println(person.age)
    //    }
        person?.let {
            println(it.name)
            println(it.age)
        }
    }

    - Safe Call (?.) 을 사용 : person이 null이 아닐때에 let을 호출

    - let : scope function의 한 종류

        ㆍ확장함수. 람다를 받아, 람다 결과를 반환한다.

    public inline fun <T, R> T.let(blcok: (T) -> R): R {
        return block(this)
    }

     

    - 람다를 사용해 일시적인 영역을 만들고, 코드를 더 간결하게 만들거나, method chaning에 활용하는 함수를 scope function이라고 한다.

     

     

    2. scope function의 분류

    - 절대 외우지마라!

      it 사용 this 사용
    람다의 결과 리턴 let run
    객체 그자체 리턴 also apply
      with ( 확장함수 X )  

        ㆍthis : 생략이 가능한 대신, 다른 이름을 붙일 수 없다.

        ㆍit : 생략이 불가능한 대신, 다른 이름을 붙일 수 있다.

    val value1 = person.let { p ->
        p.age
    }
    
    val value2 = person.run {
        age
    }

        ㆍ let 은 일반함수를 받는다.

        ㆍrun 은 확장함수를 받는다.

    * 확장함수에서는 본인 자신을 this로 호출하고, 생략할 수 있었다.

     

    val person = Person("박현민", 100)
    with(person) {
        println(name)
        println(this.age)
    }

    - with(파라미터, 람다) : this를 사용해 접근하고, this는 생략 가능하다.

     

    3. 언제 어떤 scope function을 사용해야 할까?

    1. let

    - 하나 이상의 함수를 call chain 결과 호출할 때

    val strings = listOf("APPLE", "CAR")
    strings.map { it.length }
        .filter { it > 3 }
    //        .let(::println)
        .let { lengths -> println(lengths) }

     

    - non-null 값에 대해서만 code block을 실행시킬 때

    val length = str?.let {
        println(it.uppercase())
        it.length
    }

        ㆍ제일 많이 사용

     

    - 일회성으로 제한된 영역에 지역 변수를 만들 때

    val numbers = listOf("one", "two", "three", "four")
    val modifiedFirstItem = numbers.first()
        .let {firstItem ->
            if (firstItem.length >= 5) firstItem else "!$firstItem!"
        }.uppercase()
    println(modifiedFirstItem)

        ㆍ주로 쓰지 않음

     

    2. run

    - 객체 초기화와 반환 값의 계산을 동시에 해야 할 때

    val person = Person("박현민", 100).run(personRepository::save)

        ㆍ객체를 만들어 DB에 바로 저장하고, 그 인스턴스를 활용할 때

    val person = Person("박현민",100).run {
        hobby = "독서"
        personRepository.save(this)
    }

        ㆍ개인적으로는 잘 사용하지 않는다.

    - 반복되는 생성 후처리는 생성자, 프로퍼티, init blolck으로 넣는 것이 좋다.

    val person = personRepository.save(Person("박현민", 100))

     

    3. apply

    - apply 특징 : 객체 그 자체가 반환된다.

    - 객체 설정을 할 때에 객체를 수정하는 로직이 call chain 중간에 필요할 때

     

    - Test Fixture를 만들 때

     

    fun createPerson(
        name: String,
        age: Int,
        hobby: String
    ): Person {
        return Person(
            name = name,
            age = age,
        ).apply {
            this.hobby = hobby
        }
    }
    val person = Person("박현민", 100)
    person.apply { this.growOld() }
        .let{ println(it) }

     

    4. also

    - also 특징 : 객체 그 자체가 반환된다.

    - 객체를 수정하는 로직이 call chain 중간에 필요할 때

    mutableListOf("one", "two", "three")
        .also { println("four 추가 이전 지금 값: $it") }
        .add("four")

     

    5. with

    - 특정 객체를 다른 객체로 변환해야 하는데, 모듈 간의 의존성에 의해 정적 팩토리 혹은 toClass 함수를 만들기 어려울 때

    return with(person) {
        PersonDto(
            name = name,
            age = age,
        )
    }

        ㆍthis를 생략할 수 있어 필드가 많아도 코드가 간결해진다.

     

    4. scope function과 가독성

    - scope function을 사용한 코드가 그렇지 않은 코드보다 가독성 좋은 코드일까?!

    // 1번 코드
    if(person != null && person.isAdult) {
        view.showPerson(person)
    } else {
        view.showError()
    }
    
    // 2번 코드
    person?.takeIf { it.isAdult }
        ?.let(view::showPerson)
        ?: view.showError()

        ㆍ1번 코드 : 전통적인 if와 else를 활용

        ㆍ2번 코드 : scope function을 활용한 코틀린스러운 코드

            ▷ view.showPerson() 이 null을 반환한다면?! - elivs 연산자에서 view.showError 가 출력

                showPerson도 불리고 showError도 불리는 버그가 생길 수 있다.

     

    - 개인적(강사)으로 1번 코드가 훨씬 좋은 코드라고 생각한다.

        ㆍ1. 구현 2는 숙련된 코틀린 개발자만 더 알아보기 쉽다.

               어쩌면 숙련된 코틀린 개발자도 잘 이해하지 못할 수 도 있다.

        ㆍ2. 구현 1의 디버깅이 쉽다.

        ㆍ3. 구현이 1이 수정도 더 쉽다.

    - 사용 빈도가 적은 관용구는 코드를 더 복잡하게 만들고 이런 관용구들을 한 문장 내에서 조합해 사용하면 복잡성이 훨씬 증가한다.

    - 하지만 scope function을 사용하면 안되는 것도 아니다! 적절한 convention을 적용하면 유용하게 활용할 수 있다.


    정리

    - 코틀린의 scope function은 일시적인 영역을 만들어 코드를 더 간결하게 하거나, method chain에 활용된다.

    - scope function의 종류에는 let / run / also / apply / with 가 있었다.

    - scope function을 사용한 코드는 사람에 따라 가독성을 다르게 느낄 수 있기 때문에, 함께 프로덕트를 만들어 가는 팀끼리 convention을 잘 정해야 한다.

     


    강의에서 다루지 못한 내용

    - 제네릭

    - 리플렉션

    - 복잡한 함수형 프로그래밍

    - DSL

    - 동시성 프로그래밍 (코루틴)

    댓글

Designed by Tistory.