프로그래밍 공부/Kotlin

[매일 매일 코틀린] 수신객체 지정람다 : with

U&MeBlue 2021. 5. 7. 09:29

수신객체 지정람다 : with

수신객체 지정람다 : with

다음 링크 https://www.acmicpc.net/blog/view/71 코드예제 1000 번 문제 참고

import java.util.*

fun main(args: Array<String>) = with(Scanner(System.`in`)) {
    println(nextInt() + nextInt())
}

최상위 수준 함수

fun main() : 코틀린에서는 클래스 선언 없이 메인 함수를 정의하여 사용할 수 없다. 메인 함수 뿐만 아니라 일반적인 함수도 클래스 선언 없이 정의해서 사용할 수 있다.

코틀린 컴파일러는 이같은 동작을 위해서 내부적으로 클래스를 대신 만들어준다. 예를 들어 위 코드가 Test.kt 라는 이름의 파일에 작성되어 있다면 TestKt.class 라는 자바 클래스 파일을 만든 뒤, Test.kt 파일에 작성된 최상위 수준 함수들(클래스 밖에 정의된 함수들)을 TestKt.class 클래스의 static 메소드로 정의해준다.

식이 본문인 함수

함수의 내용이 등호와 하나의 표현식으로 나타내어진다면 이를 식이 본문인 함수라고 한다. 위 main 함수는 하나의 with 함수 호출로 표현되기 때문에 등호를 이용하여 간단히 나타낼 수 있었다.

식이 본문인 함수는 반환형을 생략할 수 있는데, 코틀린 컴파일러가 함수 본문을 분석하여 반환형을 추론할 수 있기 때문이다.

if~else, when 같은 구성요소를 이용해서 식이 본문인 함수를 좀 더 복잡하게 구성할 수도 있는데, 이 경우 각 블록의 마지막에 표현한 값이 함수의 반환값이 되며, 이 반환값의 타입이 함수의 리턴 타입이 된다.

fun foo(x: Int) = if (x > 100) { // 이 함수의 반환형은 String
    "more than 100"
} else {
    "less than or equal to 100"
}

수신 객체 지정 람다 : with

어떤 클래스 객체가 반복적으로 사용되는 경우, 이러한 작업이 번거로울 수 있다.

Person person = new Person();
person.height = 180;
person.gender = Gender.Male;
person.weight = 70;
...

코틀린에서는 with 함수를 이용해서 보다 간단한 형태로 리펙토링 할 수 있다.

val person = Person()
with(person) {
    this.height = 180    // this 키워드를 사용하여 person 객체 사용을 명확하게 표현할 수도 있고
    gender = Gender.Mail // this 를 생략하여 사용할 수도 있다.
    weight = 70
    ...
}

매번 객체 이름을 명시해야 했던 Java 와 비교했을 때 훨씬 편하게 객체를 사용할 수 있다. 이 글 초반에 나온 코드에서는 with 함수를 통해서 Scanner 객체의 nextInt 와 같은 메소드를 객체 이름 지정 없이 편하게 사용할 수 있었다.

with 함수의 내부 구현을 보면

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
} 
  • block: T.() -> R : 코틀린은 람다를 함수의 인자로 전달받을 수 있다.
  • receiver : 수신 객체라고 하며 인자로 전달받은 람다 함수가 내부적으로 사용할 객체를 의미한다. block 람다 함수 내부에서는 이 수신객체의 public 한 멤버들을 자유롭게 사용할 수 있다.