Kotlin

[Kotlin] Scope functions(let, also, apply, with, run, use)

하나쓰 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를 출력하고 파일을 닫음

 

 


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

반응형