참고 : https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29

 

코틀린 의 apply, with, let, also, run 은 언제 사용하는가?

원문 : “Kotlin Scoping Functions apply vs. with, let, also, and run”

medium.com

 


위 다섯개의 함수는 아래의 경우에서 각기 다른 특징을 가진다.

 

1. 호출 형태 (리시버를 어떻게 전달하는지?)

 A. 확장함수 형태

 B. 일반함수 형태

2. 코드 블록 내에서 리시버 객체를 어떻게 호출하는지?

 A. 파라미터로 전달하였기 때문에 it 키워드로 접근

 B. 리시버 객체로 전달하였기에 this 키워드로 접근

3. 반환값이 무엇인지?

 A. 전달 받은 리시버 객체

 B. 코드 블록의 수행 결과

 

  let run (확장함수) also apply with
호출 형태 A A A A B
코드 블록 내에서 리시버 객체에 접근하는 방법 A B A B B
반환값 A A B B A
언제 사용하는지? 리시버 객체가 null이 아닌 경우에 코드를 실행해야 하는 경우

Nullable 객체를 다른 Nullable 객체로 변환하는 경우

단일 지역 변수의 범위를 제한하는 경우(한 번 쓰고 말 변수 선언해서 사용하는 경우 방지)
어떤 값을 계산하거나

여러 개의 지역 변수의 범위를 제한하고자 할 때 (한번 쓰고 말 변수 선언해서 사용하는 경우 방지)
리시버 객체의 속성을 변경하지 않고 함수를 실행하고

리시버 객체를 다시 반환하려고 할 때
리시버 객체의 함수를 사용하지 않고 속성을 초기화한 뒤

리시버 객체를 다시 반환하려고 할 때
리시버 객체가 null이 아닌 경우에만 사용

결과가 필요하지 않을 때
사용 예시 다른 함수를 실행하거나 연산을 수행하는 경우 위 링크 참고 객체의 사이드이펙트 확인 시
객체의 초기화
레이아웃 초기화
null이 아닌 객체의 프로퍼티를 읽어서 처리하는 경우

리시버 객체를 전달하는 방식이 두 가지가 있는데 

let, also와 같이 내부에서 it으로 접근하는 방식은 

리시버 객체를 복사하여 전달하고

run, apply, with는 객체 본인을 그대로 전달한다는 차이가 있다.

5. with

- 일반 함수로 사용 (인자가 필요함)

- 인자로 받는 객체를 블록의 리시버로 전달

- 블록의 마지막 줄을 반환

 

<예제>

var person : Person = Person("david", "male", "game")

var p : Person = with(person) {
    println("with01")
    printInfo(this);
    hobby = "walk"
    Person("hannah", "female", "swim")
}

println("with02")
printInfo(p)

<결과>

with01    
printInfo :: 
    name -> david
    hobby -> game
    sex -> male
with02
//마지막 줄을 반환하였기 때문에 david가 아닌 hannah가 나옴
printInfo :: 
    name -> hannah
    hobby -> swim
    sex -> female

 

- 세이프 콜을 지원하지 않기 때문에 NPE에 주의하여야 함 (let과 함께 사용하여 문제를 해결할 수 있음)

person?.let {
    with(it) {
        ...
    }
}

그리고 이를 run()함수를 이용하여 사용할 수 있음

person?.run() {
    ...
}

 

3. also

- 반환값은 호출한 객체 (return this)

- 블록문 안에서 호출한 객체에 접근할 때는 it 키워드를 사용

<예제>

fun main() {
    var person : Person = Person("david", "male", "game")

    var p : Person = person.also {
        println("also01")
        printInfo(it);
        it.hobby = "walk"
        Person("hannah", "female", "swim")
    }

    println("also02")
    printInfo(p)
}

<결과>

also01
printInfo :: 
    name -> david
    hobby -> game
    sex -> male
    
also02
//마지막 줄이 아닌 참조한 객체 자체를 반환
printInfo :: 
    name -> david
    hobby -> walk
    sex -> male

4. apply

- 호출하는 객체를 블록으로 전달

- 블록에서 호출한 객체에 접근하려면 this 키워드를 사용 (this 키워드는 생략 가능)

- also와 달리 인자값이 확장 함수로 처리 됨 (block: T.() -> Unit)

 

<예제>

var person : Person = Person("david", "male", "game")

var p : Person = person.apply {
    println("apply01")
    printInfo(this);
    this.hobby = "walk"	//this는 생략 가능
    Person("hannah", "female", "swim")
}

println("apply02")
printInfo(p)

<결과>

apply01
printInfo :: 
    name -> david
    hobby -> game
    sex -> male
apply02
//마지막 줄이 아닌 참조한 객체 자체를 반환
printInfo :: 
    name -> david
    hobby -> walk
    sex -> male

 

1. let

- 확장함수

- 호출한 객체를 블록의 인자로 넘기고 블록의 결과값을 반환함

- 인자로 넘긴 객체는 it 키워드를 통해 접근 가능

- 호출한 객체를 사용하여 다른 메소드를 실행하거나 연산을 수행하는 경우 사용 

 

 

<예제>

fun main() {
    var pLet : Person = Person("hannah", "female", "swim")
    
    var hobby : String = pLet.let {
        println("let01")
        printInfo(it);

        println("let02")
        it.hobby = "listen music"	//전달받은 객체의 프로퍼티 값 바꾸기
        printInfo(it)
        it.hobby	//맨 마지막 줄이 반환된다
    }

    println("let03")
    println(hobby);
}

private fun printInfo(p: Person) {
    println("let :: " +
            "\n    name -> ${p.name}" +
            "\n    hobby -> ${p.hobby}" +
            "\n    sex -> ${p.sex}"
            )
    }
}

<출력 결과>

let01
let :: 
    name -> hannah
    hobby -> swim
    sex -> female
let02
let :: 
    name -> hannah
    hobby -> listen music
    sex -> female
let03
listen music

 

- it을 통해 객체의 내용을 읽을 수 있고 수정도 가능함.

- 블록의 마지막 줄을 반환

- ?.와 함께 사용하면 if(pLet != null) 같은 검사용 코드를 대체 가능함

<예제>

fun main() {
    var pLet : Person? = null

    var hobby : String?

    hobby = pLet?.let {		//pLet이 null인 경우 실행되지 않는다
        println("let01")
        printInfo(it);
        it.hobby
    }

    println("let02")
    println(hobby);
}

<결과>

let02
null

 

- ?:와 사용 시 if(pLet != null) { ... } else { ... } 같은 검사용 코드를 대체 가능

<예제>

fun main() {
    var pLet : Person? = null

    var hobby : String?

    hobby = pLet?.let {
        println("let01")	//pLet이 null이라서 실행되지 않음
        printInfo(it);
        it.hobby
    } ?: "defaultValue"		//pLet이 null이므로 hobby에는 defaultValue가 할당

    println("let02")
    println(hobby);
}

<결과>

let02
defaultValue

2. run

- 확장함수 형태와 익명함수처럼 동작하는 형태 두가지가 있음

- 블록의 마지막 줄을 반환함

- 확장함수 형태로 사용 시 자신을 호출한 객체를 블록문 안에서 사용할 수 있으며, 이 때 this 키워드를 사용

<예제> - 일반함수 형태로 사용하는 경우

fun main() {
    var pRun01 : Person = Person("younghei", "female", "drawing")
    var runObject = run {
        println("run01-1")
        printInfo(pRun01)	//일반 함수로 구현했기 때문에 pRun01에 접근할 때 this 사용 불가

        println("run01-2")
        pRun01.hobby = "swim"
        printInfo(pRun01)
        pRun01	//블록의 마지막 줄 리턴
    }

    println("run01-3")
    println(runObject.hobby)
}

<결과>

run01-1
printInfo :: 
    name -> younghei
    hobby -> drawing
    sex -> female
run01-2
printInfo :: 
    name -> younghei
    hobby -> swim
    sex -> female
run01-3
swim

 

<예제> - 확장함수 형태로 사용하는 경우

var runObject = pRun01.run {
    println("run01-1")
    printInfo(this)		//확장함수로 사용하였기 때문에 this 키워드로 해당 객체 접근 가능

    println("run01-2")
    this.hobby = "swim"	//이 경우 this 키워드 생략 가능
    printInfo(this)
    this
}

println("run01-3")
println(runObject.hobby)

결과는 위와 동일!


공부하다보니 문득

'그렇다면 let과 run의 차이는 무엇일까?' 라는 생각이 들었다.

일단 보여지기로는 내부에서 객체를 참조할 때 it 키워드를 사용하느냐 this 키워드를 사용하느냐 정도인데..

 

일단 여기저기 물어본 결과..

let = "날 호출한 객체를 이용해서 뭘 할래!"

run = "날 호출한 객체에 뭘 할래!"

로 사용한다는 얘기를 들었다.

 

예시를 들어보면..

let = "사이다를 이용해서 에이드를 만들겠다!"

cider.let {
    makeAde(it)
}

run = "사이다에 레몬맛을 첨가하겠다!"

cider.run {
    flavor = lemon
}

이런 식인 걸까?

 

사용하는 곳이 명확하게 다르다고 들었으니

두 개를 나눈 이유가 있을 것 같은데

어째서일까....?

'개발 > Kotlin' 카테고리의 다른 글

코틀린 공부 - 함수 (4) with  (0) 2021.08.03
코틀린 공부 - 함수 (4) also, apply  (0) 2021.08.03
코틀린 공부 - 함수(3)  (0) 2021.08.02
코틀린 공부 - 함수(2)  (0) 2021.08.02
코틀린 공부 - 함수(1)  (0) 2021.08.02
fun main() {
    val result = callByValue(lambda())	//값에 의한 호출
    println(result)

    val result2 = callByName(lambda)	//이름에 의한 호출
    println(result2)
}

fun callByValue(b: Boolean): Boolean {
    println("callByValue function")
    return b
}

fun callByName(b : ()-> Boolean) : Boolean {
    println("callByName function")
    return b();
}

val lambda: () -> Boolean = {
    println("lambda function")
    true
}

위의 코드를 실행하면 아래와 같은 결과가 나온다.

 

lambda function
callByValue function
true

callByName function
lambda function
true

 

순서가 바뀌는 이유는 무엇일까?

 

값에 의한 호출은 메소드에서 람다식을 호출하면서 람다식을 먼저 수행한 뒤 해당 값을 사용하고

이름에 의한 호출은 메소드에서 람다식 자체를 가져가서 사용하므로 

실제로 메소드 내에서 람다식을 실행할 때 수행되기 때문이다.

'개발 > Kotlin' 카테고리의 다른 글

코틀린 공부 - 함수 (4) also, apply  (0) 2021.08.03
코틀린 공부 - 함수(4) - let / run  (0) 2021.08.03
코틀린 공부 - 함수(2)  (0) 2021.08.02
코틀린 공부 - 함수(1)  (0) 2021.08.02
코틀린 기초 5 - object  (0) 2020.06.25

+ Recent posts