App노자

[Kotlin] 코틀린 표준 라이브러리 본문

Android/Kotlin

[Kotlin] 코틀린 표준 라이브러리

앱의노예 2023. 8. 9. 20:47

1. let() 함수


public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

let() 함수는 Nullable 객체의 안전한 호출 및 변환을 도와주는 유용한 함수이다 

객체가 null이 아닌 경우에만 특정 동작을 수행하거나 변환을 수행한다는 특징을 갖고 있다

제네릭의 확장 함수 형태이므로 어디든 적용할 수 있으며 특히 Nullable 객체를 다룰 때 자주 사용된다

let() 함수는 함수를 호출하는 객체 T를 이어지는 block의 인자로 넘기고 block의 결괏값 R을 반환한다

매개변수로는 람다식 형태인 block가 있고 T를 매개변수로 받아 R을 반환하며 return blcok(this)의 this는 객체 T를 가리키는데

이것은 람다식 결과 부분을 그대로 반환한다는 뜻이다

이 함수를 호출한 객체를 인자로 받으므로 다른 메서드를 실행하거나 연산을 수행해야 하는 경우 사용할 수 있다

let() 예시 

fun main() {

    val score: Int? = 32
//    var score = null

    // 일반적인 null 검사
    fun checkScore() {
        if (score != null) {
            println("Score: $score")
        }
    }

    // let을 사용해 null 검사를 제거함
    fun checkScoreLet() {
        score?.let { println("Score: $it") } // (1)
        
        val str = score.let { it.toString() } // (2)
        
        val cal = score?.let { it + 10 } // (3)
        println(str)
        println("cal $cal")
    }

    checkScore()
    checkScoreLet()
    
    // 체이닝
    var a = 1
    var b = 2

    a = a.let { it + 2 }.let {
        val i = it + b
        i  // 마지막 식이 반환 된다
    }
    println(a) //5

}

위의 코드에서 score는 null 가능성이 있는 변수로 선언되었기 때문에

checkScore()에서 if문을 사용해 null검사를 하고 있다

checkScoreLet()의 (1)을 보면 score멤버 메서드를 호출하듯 let함수를 사용했는데 매개변수가 람다식 하나일 때는

let({ ... })에서 표현이 소괄호가 생략되어 let{ ... }과 같이 작성할 수 있다

세이프 콜을 사용해 작성했기 때문에 만일 score가 null일 경우 람다식 구문은 수행되지 않는다 

(2)의 코드에서는 toString()을 사용해 it을 문자열로 변환한 후 반환된 값을 str에 할당한다

세이프콜을 사용하지 않았기 때문에 score가 null이라면 str에는 null이 할당된다

(3)의 코드에서는 세이프 콜을 사용했지만 람다식을 사용하지 않게 되므로 str은 String?으로 추론되어 null이 할당된다

2. also() 함수


public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

also() 함수는 함수를 호출하는 객체 T를 이어지는 block에 전달하고 객체 T자체를 반환한다

let() 함수와 역할이 거의 동일하지만 let() 함수는 마지막으로 수행된 코드 블록의 결과를 반환하는데 반면

also() 함수는 블록 안의 코드 수행 결과와 상관없이 T인 객체 this를 반환한다

also() 예시 

fun main() {

    // also의 기본 개념
    var m = 1
    m = m.also { it + 3 }
    println(m) // 원본값 1

    // 활용
    data class Person(var name: String, var skills : String)
    var person = Person("Kildong", "Kotlin")

    val a = person.let {
        it.skills = "Android"
        "sucess" // 마지막 문장을 결과로 반환
    }
    println(person)
    println("a: $a")
    val b = person.also {
        it.skills = "Java"
        "success" // 마지막 문장은 사용되지 않음
    }
    println(person)
    println("b: $b")
}

let() 함수와 also() 함수를 비교해 보면 let() 함수는 person객체에서 skills를 변경하고 마지막 표현식인 "success"를 반환해 a를 할당한다 반면에 also() 함수는 람다식이 본문을 처리하지만 마지막 표현식이 b에 할당되는 것이 아닌 person객체 자신에 할당되어 b는 Person의 객체 person을 반환하고 새로운 객체 b가 할당되어 만들어진다

3. apply() 함수


public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

apply() 함수는 also() 함수와 마찬가지로 호출하는 객체 T를 이어지는 block으로 전달하고 객체 자체인 this를 반환한다

apply() 함수는 특정 객체를 생성하면서 함께 호출해야 하는 초기화 코드가 있는 경우 사용할 수 있다

also() 함수와의 다른 점은 T.()와 같은 표현에서 람다식으로 확장 함수로 처리된다는 것이다

apply() 예시 

fun main() {
    data class Person(var name: String, var skills : String)
    var person = Person("Kildong", "Kotlin")

    // 여기서 this는 person 객체를 가리키게 된다.
    person.apply { this.skills = "Swift" }
    println(person)

    val retrunObj = person.apply {
        name = "Sean" // this는 생략할 수 있다.
        skills = "Java" // this없이 객체의 멤버를 여러번 접근한다.
    }
    println(person)
    println(retrunObj)

}

apply()는 확장 함수로써 person을 this로 받아오는데 클로저를 사용하는 방식과 같다

객체의 프로퍼티를 변경하면 원본 객체에 반영되고 객체는 this로 반환된다

this. 가 생략이 가능하기 때문에 위의 코드와 같이 작성이 가능하며 이때 this로부터 반환된 객체를

 

 

 

4. run() 함수


public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

run() 함수는 인자가 없는 익명 함수처럼 동작하는 형태와 객체에서 호출하는 형태 2가지로 사용할 수 있다

객체 없이 run() 함수를 사용하면 인자 없는 익명 함수처럼 사용할 수 있다

run() 함수에서는 block이 독립적으로 사용된다 이어지는 block 내에서 처리할 작업을 넣어 줄 수 있으며

일반 함수와 마찬가지로 값을 반환하지 않거나 특정 값을 반환할 수도 있다

fun main() {

    // 기본 사용법
    var skills = "Kotlin"
    println(skills) // Kotlin

    val a = 10
    skills = run {
        val level = "Kotlin Level:" + a
        level
    }
    println(skills) // Kotlin Level:10

    // let과 함께 사용
    var p : String? = null
    p?.let { println("p is $p") } ?: run {
        println("p was null. Setting default value to: ")
        p = "Kotlin"
    }

    println(p)
    
    // let과 비교
    StringBuilder().let {
        it.append("content: ")
        it.append(it.javaClass.canonicalName)
    }.print()

    StringBuilder().run {
        append("content: ")
        append(javaClass.canonicalName)
    }.print()

    // apply와 run 비교
    data class Person(var name: String, var skills : String)
    var person = Person("Kildong", "Kotlin")

    val retrunObj = person.apply {
        this.name = "Sean"
        this.skills = "Java"
        "success"
    }
    println(person)
    println("retrunObj: $retrunObj")

    val retrunObj2 = person.run {
        this.name = "Dooly"
        this.skills = "C#"
        "success"
    }
    println(person)
    println("retrunObj2: $retrunObj2")

}

fun Any.print() = println(this)

 

 

 

 

6. with() 함수


public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with() 함수는 인자로 받는 객체를 이어지는 block의 receiver로 전달하며 결괏값을 반환한다

with() 함수는 run() 함수와 기능이 거의 동일한데, run() 함수의 경우 receiver가 없지만

with() 함수에서는 receiver로 전달할 객체를 처리하므로 객체의 위치가 달라진다

매개변수가 2개이므로 with() {}와 같은 형태로 넣어준다 with() 함수는 확장 함수 형태가 아닌

단독으로 사용되는 함수이며 세이프콜을 지원하지 않기 때문에 let() 함수와 같이 사용되기도 한다

fun main() {
    data class User(val name: String, var skills: String, var email: String? = null)

    val user = User("Kildong", "default")

    val result = with (user) {
        skills = "Kotlin"
        email = "kildong@example.com"
        "Success"
    }
    println(user)
    println("result: $result")

}

객체 이름을 with() 함수에 인자로 넣고 본문에서 멤버 이름만 사용해 접근할 수 있다

기본적으로 Unit이 반환되지만, 필요한 경우 마지막 표현식을 반환할 수 있다

7. use() 함수


public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

use() 함수는 객체가 사용된 후 닫아야 하는 경우에 사용한다 

객체를 사용한 후 close() 함수를 자동적으로 호출해 닫아 줄 수 있으며

T의 제한된 자료형을 보면 Closeable?로 block은 닫힐 수 있는 객체를 지정해야 한다

fun main() {

    PrintWriter(FileOutputStream("d:\\test\\output.txt")).use {
        it.println("hello")
    }

    val file = File("d:\\test\\contents.txt")
    file.bufferedReader().use {
        println(it.readText())
    }

}

위의 첫 번째 코드에서 PrintWriter()는 파일 등에 내용을 출력한다

이때 인자로 FileOutputStream()을 사용해 파일 output.txt를 지정하고 hello를 출력한 후

use()에 의해 내부적으로 파일을 닫게 된다

두 번째 코드에서는 output.txt 파일에 "hello"라는 문자열을 저장하는 소스 코드이다

일반적으로 파일 작업을 하고 나면 close()를 명시적으로 호출해야 하는데 use 블록 안에서는 그럴 필요가 없다

파일에서 읽어 들일 때도 사용하는데 생성한 파일을 use() 함수를 사용해 읽을 수 있다 

'Android > Kotlin' 카테고리의 다른 글

[Kotlin] object 키워드  (0) 2023.08.15
[Kotlin] Closure (클로저)  (0) 2023.08.10
[Kotlin] by 키워드  (0) 2023.08.06
[Kotlin] 지연 초기화  (0) 2023.08.05
[Kotlin] 컬렉션의 확장 함수  (0) 2023.08.04