ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kotlin] Scope functions(let, also, apply, with, run, use)
    Kotlin 2024. 12. 3. 23:29
    728x90
    반응형
    반응형

    코틀린 scope functions를 사용하면 좀 더 간단하게 코드를 작성할 수 있다

    헷갈리는 것들이 있어 정리해보려고 한다

     


     

    1. let

    let을 호출하는 객체 T를 매개변수로 받아서 block으로 넘기고 block안의 결과값(value)을 리턴함

    사실 이렇게 말하면 어렵고 코드로 보면 쉽다

     

    package org.example
    
    fun main() {
        checkName("hans")
        checkNameLet(null)
    }
    
    fun checkName(name: String?) {
        when(name) {
            null -> println("이름을 다시 입력해주세요")
            else -> println("name is $name")
        }
    }
    
    fun checkNameLet(name: String?) {
        name?.let { println("name is $it") } ?: println("이름을 다시 입력해주세요")
    }

    let과 세이프콜(.?)을 사용해 위와 같이 작성해보았다

    let을 호출한 name이라는 것이 null이 아니면 람다식 블록에서 this(여기선 name임)을 받아서 람다식 안의 결과값(println("name is $it"))을 리턴한다. 만약 null인 경우에는 람다식 구문이 동작하지 않는다

    하지만 나의 경우에는 엘비스 연산자를 사용해 null일 때도 위와 같이 처리를 해주었다

     

    2. also

    T라는 객체를 람다식 안에서 매개변수로 받아서 also를 호출한 객체(T) 자체를 리턴한다

     

    package org.example
    
    fun main() {
    
        data class Person(var name: String, var age: Int)
        val person = Person("james", 100)
    
        val one = person.let {
            it.name = "산타"
            it.age = 2000
            "변경 완료1"
        }
    
        println(person) // Person(name=산타, age=2000)
        println(one) // "변경 완료1" 
        println()
    
        val two = person.also {
            it.name = "루돌프" 
            it.age = 1000
            "변경 완료2"
        }
        println(person) // Person(name=루돌프, age=1000)
        println(two) // Person(name=루돌프, age=1000)
    }

     

    let으로 했을 때는 람다식 블록 안에 있는 마지막 결과값이 리턴되는데 반해서

    also는 호출한 객체 자체(person)를 리턴하고 있다

     

    3. apply

    also와 마찬가지로 객체 T를 받아서 람다식 안에서 also를 호출한 객체 T를 리턴하고 있다

     

    package org.example
    
    fun main() {
    
        data class Person(var name: String, var age: Int)
        val person = Person("james", 100)
    
        val one = person.apply {
            this.age = 2000
            "변경 완료1"
        }
    
        println(person) // Person(name=james, age=2000)
        println(one) // Person(name=james, age=2000)
        println()
    
        val two = person.apply {
            this.name = "루돌프"
            this.age = 1000
            "변경 완료2"
        }
        println(person)// Person(name=루돌프, age=1000)
        println(two) // Person(name=루돌프, age=1000)
    }

     

    그럼 also랑 차이점이 뭘까?

    apply에서는 T.() (여기서 T는 this)와 같이 확장함수처럼 동작한다는 것이다 

    그러니까 람다식 구문 안에서 this.name, this.age 이런 식으로 접근할 수 있고, this를 생략해도 된다

    하지만 also에서는 일반 람다식((T) -> Unit)(여기서 T는 it)를 사용해야해서 it(this(also를 호출한 객체))를 생략할 수 없다

    찾아보니 주로 객체초기화 할 때 apply를 많이 사용한다고 한다

     

    4. run

     

    run은 위의 scope function들 같이 객체에서 호출하는 방법과 객체 없이 run()을 사용해 익명 함수처럼 동작하는 형태 총 두 가지가 있다

     

    package org.example
    
    fun main() {
    
        data class Person(var name: String, var age: Int)
        val person = Person("james", 100)
    
        val one = person.apply {
            name = "산타"
            age = 2000
            "변경 완료1"
        }
    
        println(person) // Person(name=산타, age=2000)
        println(one) // Person(name=산타, age=2000)
        println()
    
        val two = person.run {
            this.name = "루돌프"
            this.age = 1000
            "변경 완료2"
        }
        println(person) // Person(name=루돌프, age=1000)
        println(two) // 변경 완료2
    
        run {
            person.name = "트리"
            person.age = 1234
        }
    
        println(person) // Person(name=트리, age=1234)
    
    }

     

    run을 사용하니까 람다식 블록 안에 있는 마지막 결과값이 리턴되었다

    또한 익명함수같이 객체에서 run을 호출하지 않고 인자 없이 익명함수처럼 사용할 수도 있다

     

        val two = person.run {
            this.name = "루돌프"
            this.age = 1000
            // "변경 완료2"
        }
        println(person) // Person(name=루돌프, age=1000)
        println(two) // kotlin.Unit

    또한 람다블록 안에서 표현식을 작성하지 않으면 Unit을 리턴하는 것을 볼 수 있다

     

    5. with

    run과 비슷하지만 with는 인자로 받은 객체를 람다식 블록 안에 receiver로 전달함

    그래서 with(객체){ ...} <- 이런 형식으로 작성해야하고 세이프콜(.?)을 지원하지 않는다

     

    package org.example
    
    fun main() {
    
        data class Person(var name: String, var age: Int)
        val person = Person("james", 100)
    
        val one = with(person) {
            name = "산타"
            age = 2000
            "변경 완료1"
        }
    
        println(person) // Person(name=산타, age=2000)
        println(one) // 변경 완료1
        println()
    
    }

    만약 마지막 표현식이 없다면 Unit을 리턴한다

     

    6. use

     

    객체를 사용하고 닫아야하는 경우에 use를 사용하면 자동으로 close()를 호출해 객체를 닫아줄 수 있다

    exception이 발생하든 안 하든 close()는 실행된다

    use는 파일 객체 같이 사용하고 나서 닫아야하는 Closeable한 객체에서 사용한다

     val filePath = File("/Users/junghana/Desktop/output.txt")
        PrintWriter(FileOutputStream(filePath)).use { writer ->
            writer.println("1234")
        }

     

    output.txt라는 파일에 hello world라는 문자열을 출력하는 코드임

    PrintWriter는 closeable을 구현한 객체이므로 use()를 사용해서 output.txt에 1234를 출력하고 파일을 닫음

     

     


    이렇게 정리했지만 역시 사용하면서 계속 익혀야겠다...!

    반응형

    'Kotlin' 카테고리의 다른 글

    [Kotlin] 지연초기화(lateinit, by lazy), 위임(by)  (0) 2024.12.20
    [Kotlin] Custom Getter, Setter  (1) 2024.12.19
    [kotlin] String.format  (3) 2024.11.07
    [Kotlin] 기본타입(Primitive Types)  (3) 2024.09.28
Designed by Tistory.