실무 프로젝트로 배우는 Kotlin & Spring/코틀린 고급

스코프 함수

webmaster 2022. 10. 22. 18:59
728x90
  • 코틀린의 표준 라이브러리에는 객체의 컨텍스트 내에서 코드 블록을 실행하기 위해서만 존재하는 몇 가지 함수가 포함되어 있고, 이를 스코프 함수라고 한다.
  • 스코프 함수를 제대로 사용하면, 변수 선언이 없어지며, 코드를 더 간결하고 읽기 쉽게 만든다.
  • 스코프 함수의 코드 블록 내부에서는 변수명을 사용하지 않고도 객체에 접근할 수 있는데, 그 이유는 수신자 객체에 접근할 수 있기 때문이다.
  • 수신자 객체는 람다식 내부에서 사용할 수 있는 객체의 참조이다.
  • 스코프 함수를 사용하면, 수신자 객체에 대한 참조로 this 또는 it를 사용한다.

스코프 함수 종류

총 5개의 유용한 스코프 함수를 제공하며, 각 스코프 함수들은 유사한 기능을 제공한다.

함수명 수신자 객체 참조 방법 반환 값 확장함수 여부
let it 함수의 결과 O
run this 함수의 결과 O
with this 함수의 결과 X
apply this 컨텍스트의 객체 O
alse it 컨텍스트의 객체 O

let


fun main() {
    val str: String? = "안녕"

    val result: Int? = str?.let {
        println(it)
        /*
        val abc: String? = "abc"
        abc?.let {
            val def: String? = "def"
            //함수 내부에 함수가 매번 쓰이니, 가독성 측면에서 그렇게 좋지는 않다.ㄴ
            def?.let {
                println("abcdef가 null 아님")
            }
        }
         */
        val abc: String? = "abc"
        val def: String? = "def"
        //많이 중첩되는 경우는 이전의 If/else 를 쓰는것이 좋다
        if (!abc.isNullOrBlank() && !def.isNullOrBlank()) {
            println("abcdef가 null 아님")
        }
        1234 //return 키워드는 없지만, 마지막 쓴 값이 리턴이 된다.
    }
    println(result)

}
  • null이 아닌 경우 사용될 로직을 작성하고, 새로운 결과를 반환하고 싶을때, 사용한다
    • null이 아닌 경우, 로직이 실행한다
  • let 함수 내부의 마지막 코드를 결과로 반환한다
  • let을 사용할 때, 호출이 중첩이된다면, 코드가 되려 복잡해지므로, if/else를 사용하는 것이 더 좋다.

run

//Run : 수신 객체의 프로퍼티를 구성하거나,새로운 결과를 반환하고 싶을 때 사용
class DatabaseClient {
    var url: String? = null
    var username: String? = null
    var password: String? = null

    //DB에 접속하고 Boolean 결과를 반환
    fun connect(): Boolean{
        println("DB 접속 중...")
        Thread.sleep(1000)
        println("DB 접속 완료")
        return true
    }
}
fun main() {
    /*
    val config = DatabaseClient()
    config.url = "localhost:3306"
    config.username = "mysql"
    config.password = "1234"
    val connected = config.connect()
    println(connected)
     */
    /*
    //let으로 변환도 가능
    val connected: Boolean = DatabaseClient().let {
        it.url = "localhost:3306"
        it.username = "mysql"
        it.password = "1234"
        it.connect()
    }

     */

    val connected: Boolean = DatabaseClient().run {
        //변수의 참조를 매번 사용할 필요 없이, 사용할 수 있다.
        url = "localhost:3306" //수신자 객체가 DatabaseClient 이기 때문에 this 생략 가능
        username = "mysql"
        password = "1234"
        connect()
    }
    println(connected)
}
  • 수신 객체의 프로퍼티를 구성하거나, 새로운 결과를 반환하고 싶을 때 사용한다.
  • run을 사용하면, 변수의 참조를 매번 사용할 필요 없이, 가독성 좋게 코드를 작성할 수 있다.
  • let을 사용하여도 같은 동작을 작성할 수 있지만, 매번 it키워드가 들어가게 되므로, run을 사용하는 편이 더 좋다.

with

fun main() {
    val str = "안녕하세요"
    val length = with(str) {
        //println("length= $length")
        length //함수의 마지막에 쓴 결과를 반환 한다
    }
    println(length)

    //with는 확장함수가 아니다 -> 파라미터에 참조를 넣는다
    val result:Boolean = with(DatabaseClient()) {
        url = "localhost:3306"
        username = "mysql"
        password = "1234"
        connect()
    }
    println(result)
}
  • 결과 반환없이 내부에서 수신 객체를 이용해 다른 함수를 호출하고 싶을 때 사용한다.
    • 확장 함수가 아닌, 파라미터에 수신 객체를 참조로 넣어 전달한다.
  • with, run으로도 같은 동작을 하는 함수를 작성할 수 있다.

apply

fun main() {
    //apply 같은 경우 마지막에 호출한 함수가 아닌 본인을 반환한다
    /*
    val client = DatabaseClient().apply {
        //초기화나 같은 동작에서 많이 사용한다
        url = "localhost:3306"
        username = "mysql"
        password = "1234"
        connect()
    }
    //println(client)
    client.connect().run {
        println(this)
    }
     */
    DatabaseClient().apply {
        //초기화나 같은 동작에서 많이 사용한다
        url = "localhost:3306"
        username = "mysql"
        password = "1234"
        connect()
    }.connect()
        .run { println(this) }
}
  • 수신 객체의 프로퍼티를 구성하고, 수신 객체를 결과 그대로 반환하고 싶을 때 사용한다.
    • let, run with 함수의 결과가 반환되는 것에 반해 apply는 수신 객체 그대로 반환한다.
  • 메서드 체이닝을 통해 한번에 작성할 수 있지만, 가독성 측면에서 좋지 않다.

also

//alse : 부수 작업을 수행하거나, 전달받은 객체를 그대로 반환할 때 사용한다
class User(val name: String, val password: String) {
    fun validate(){
        if(name.isNotEmpty() && password.isNotEmpty()){
            println("검증 성공")
        }else{
            println("검증 실패!")
        }
    }
    fun printName() = println(name)
}

fun main() {
	//apply 사용 x
    val user: User = User(name = "tony", password = "1234")
    user.validate()
    //apply 사용
    User(name = "tony", password = "1234").also {
        it.validate()
        it.printName()
    }
}
  • 부수 작업을 수행하고 전달받은 수신 객체를 결과 그대로 반환하고 싶을 때 사용한다.

스코프 함수 사용 시 주의할 점


fun main() {

    /*
    val this:String? = null //this는 키워드라 다른 곳에서 사용 안된다.
    val it: String? =null //it은 사용가능 //소프트 예약어
     */
    val hello = "hello"
    val hi = "hi"
    /*
    hello.let {
        println(it.length)
        hi.let {
            println(it.length)//해당 it는 해당 컨텍스트 it인지, 외부의 it인지 알기 힘드므로, 중첩된 코드는 사용하지 말자
        }
    }
     */
    hello.let { a: String ->
        println(a.length)
        hi.let {b: String -> //변수를 사용하는 방식으로 하여, 혼동이 없도록 하자
            println(b.length)
        }
    }
}
  • 스코프 함수는 모두 기능이 유사해, 실무에선 섞어 쓰는 경우가 많다.
  • this는 키워드(사전에 정의된 예약어) 임으로 다른 의미로 사용할 수 없지만, it는 특정 용도에서만 작동하는 소프트 키워드이기 때문에 다른 용도로 사용할 수 있다.
  • 중첩으로 사용된 경우 this, it에 대해 혼동되므로, 변수를 사용하는 방식으로 혼동되지 않게 사용하도록 하자
    • 중첩 함수 내에서 외부 함수에 대해 it로 접근이 불가능하다
728x90

'실무 프로젝트로 배우는 Kotlin & Spring > 코틀린 고급' 카테고리의 다른 글

람다로 프로그래밍하기  (0) 2022.10.22
고급 예외처리  (0) 2022.10.22
페어와 구조분해할당  (0) 2022.10.16
지연 초기화  (0) 2022.10.16
제네릭  (0) 2022.10.16