💡 Kotlin

[Kotlin] apply, let, with, also, run 비교 (Scope Function)

콩드로이드 2022. 7. 14. 16:04

안녕하세요 :) 

오늘은 코틀린의 다섯 가지 함수를 비교해보고자 합니다.

[2022.07.14 업데이트]

 

SCOPE 함수란?

객체의 컨텍스트 내에서 코드 블록을 실행하는 것이 유일한 목적인 함수입니다.

람다식으로 SCOPE 함수를 호출할 때 임시적으로 범위를 생성하고,  해당 범위 내에선 객체의 이름 없이 객체에 접근할 수 있습니다(it, this 등)

본적으로 5가지 함수는 같은 기능을 하지만 표현식이 어떻게 되는지, 블록 내에서 객체 사용법에 따라 구분합니다

 

📍 let

함수원형

public inline fun <T,R> T.let(block : (T) -> R): R 

객체를 블록의 인자(T)로 넘기고, 람다의 결과값(R)을 반환합니다.

 

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
    println(it)
} 

 

또 let은 safe call operator (?.) 를 사용해서 null체크를 할 수 있습니다. 

 

그러므로, 아래와 같은 경우에 쓰면 좋습니다 

 

📌 non-null인 코드 실행하는 경우

📌 단일 지역변수 범위를 제한하는 경우 

val phone = Phone("", "")
val result = phone?.let { it ->
    it.number="999"
    it.company="samsung"
    it
}

 

 


📍 also

함수원형

public inline fun <T> T.also(block: (T) -> Unit): T

객체를 블록의 인자(T)로 넘기고 받아온 객체 그대로(T)를 return 합니다.

객체말고 다른 값을 반환해야 하는 경우엔  also를 사용하는 것이 적합하지 않습니다 

그러므로, 아래와 같은 경우에 쓰면 좋습니다 

 

📌 람다가 객체를 사용하지 않거나 속성을 변경하지 않는 경우

📌 디버그 , Log, 데이터 유효성 확인에 적합

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")

 

 


📍 apply

함수원형

public inline fun <T> T.apply(block: T.() -> Unit): T

block에 람다 리시버(T.())를 전달하고 자기자신(T)을 반환합니다 

람다 리시버로 전달받았기 때문에 객체의 property에 접근할 때 it, this를 사용하지 않아도 됩니다

주로 객체 초기화에 많이 쓰입니다.

 

그러므로, 아래와 같은 경우에 쓰면 좋습니다  

 

📌 객체 함수를 사용하지 않고 객체를 다시 반환하는 경우

📌 객체의 속성을 구성할 때

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

 


 

📍 With

함수원형

public inline fun <T, R> with(receiver: T, block: T.() -> R): R

객체를 인수로 받고(T), block에 람다 리시버(T.())를 전달하고 람다결과(R)를 반환합니다

null에 대해 별도로 처리를 해야 하기때문에 null이 아닌 경우에 사용하는 것이 좋습니다

 

📌 non-null 객체를 사용하고 block의 return 값이 필요하지 않을 때 사용

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

 

📌 객체에서 여러 개의 메소드를 호출할 때 

class Turtle {
    fun penDown()
    fun penUp()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}

val myTurtle = Turtle()
with(myTurtle) { 
    penDown()
    for (i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
}

 

 


📍 run

run은 특이하게 원형이 2개가 있습니다 

 

함수원형 ① : 확장함수도 아니고 block에 입력값도 없는 경우 

public inline fun <R> run(block: () -> R): R

 

📌 객체를 생성하기 위해서 block안에 묶어서 가독성을 높이는 경우

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

 

함수원형 ② :  람다 리시버를 인자로 받고 람다 결과를 반환

public inline fun <T, R> T.run(block: T.() -> R): R

 

📌 값을 계산하거나, 여러 변수의 범위 제한 시 사용

val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

 

 

코틀린을 사용하면서 자주 사용하지만 헷갈리는 Scope 함수에 대해 정리해보았습니다

헷갈리는 개념이지만 Scope 함수들을 용도에 따라 사용하면 소스 가독성이 높아집니다

 

포스팅 속 예제는 코틀린 공식 문서에 있는 소스입니다

 

궁금하신 점이나 의견이 있으시면 댓글 부탁드립니다 감사합니다 😊

 

 

'💡 Kotlin' 카테고리의 다른 글

[Kotlin] Collection (List, Set, Map)  (0) 2023.01.03
[kotlin] Pair  (0) 2023.01.02
[Kotlin] 배열  (0) 2022.07.02
[Kotlin/Android] Room 사용하기  (0) 2022.06.30
[Kotlin] 동적 View 생성  (0) 2021.06.13