수신 객체 지정 람다: with, apply, also

유우선·2026년 2월 3일

Kotlin Study📚

목록 보기
12/32

개요

  • with, apply, also 함수에 대해 알아본다
  • 수신 객체 지정 람다 → 수신 객체를 명시하지 않고 람다 본문 안에서 다른 객체의 메서드를 호출

with 함수

어떤 객체의 이름을 반복하지 않고 그 객체에 대해 다양한 연산을 수행할 수 있게 해줌

with를 사용한 리펙토링

  1. 일반 코드
fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z')
            result.append(letter)

    result.append("\n Now I know the alphabet!")
    return result.toString()
}

fun main(){
    println(alphabet())
    // ABCDEFGHIJKLMNOPQRSTUVWXYZ
    //  Now I know the alphabet!
}
  • result에 대해 여러 메서드를 호출하면서 객체의 이름을 반복 사용함
  1. with를 사용한 리펙터링
fun alphabet(): String {
    val stringBuilder = StringBuilder()
    return with(stringBuilder) { // 메서드를 호출하려는 수신 객체를 지정
        for (letter in 'A'..'Z')
            this.append(letter)
        this.append("\n Now I know the alphabet!")
        this.toString() // with의 결과 반환
    }
}
  • 반복적으로 사용하던 result라는 객체의 이름을 this로 대체
  1. 불필요한 변수 제거
fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z')
        append(letter)
    append("\n Now I know the alphabet!")
    toString()
}
  • 식을 바로 반환 → 식을 본문으로 하는 함수로 표현할 수 있음

with는 파라미터가 2개 있는 함수임

  • 위의 예제의 경우 stringBuilder, 람다가 파라미터임
  • 첫 인자로 받은 객체를 두 번째로 받은 람다의 수신 객체로 만듬
  • 람다 안에선 this를 통해 수신 객체에 접근할 수 있음
  • this를 생략하고 메서드나 프로퍼티 이름만 사용해 접근할 수도 있음

메서드 이름 충돌

with에 인자로 넘긴 객체의 클래스와 with를 사용하는 함수가 선언된 클래스 안에 이름이 같은 메서드가 있는 경우

  • this 참조 앞에 레이블을 붙이면 됨
  • this@OuterClass.toString()

with가 반환하는 값 → 람다를 실행한 결과


apply 함수

apply 함수는 with와 동일하게 작동하지만 apply는 항상 자신에게 전달된 객체를 반환한다는 차이가 있음

alphabet 함수 리펙터링

fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z')
        append(letter)
    append("\n Now I know the alphabet!")
}.toString()
  • apply를 임의의 타입의 확장 함수로 호출할 수 있음
  • apply를 호출한 객체 → apply에 전달된 람다의 수신 객체가 됨
    • apply를 호출한 결과는 StringBuilder기 때문에 toString()을 호출할 수 있음

인스턴스를 만들면서 즉시 프로퍼티 중 일부를 초기화해야 하는 경우 apply가 유용함

fun createViewWithCustomAttribute(context: Context) =
    TextView(context).apply {
        text = "Sample Text"
        textSize = 20.0
        setPadding(10, 0, 0, 0)
    }
  • 새로운 TextView 인스턴스를 만들고 즉시 apply에 넘김
  • apply의 람다 안에서는 TextView가 수신 객체가 됨
    • TextView의 함수, 프로퍼티 참조 가능
  • 람다 실행 후 apply는 초기화된 TextView 인스턴스를 반환
  • 이 인스턴스는 createViewWithCustomAttribute 함수의 결과가 됨

buildString 함수를 사용한 alphabet 함수 리펙터링

fun alphabet() = buildString {
    for (letter in 'A'..'Z')
        append(letter)
    append("\n Now I know the alphabet!")
}
  • buildString → StringBuilder 객체 생성과 toString() 호출을 알아서 해줌
  • buildString의 인자는 수신 객체 지정 람다임
  • 수신 객체는 항상 StringBuilder임

읽기 전용 컬렉션을 가변 컬렉션 처럼 생성하기

val fibonacci = buildList {
    addAll(listOf(1,1,2))
    add(3)
    add(index = 0, element = 3)
}

val shouldAdd = true

val fruits = buildSet {
    add("Apple")
    if(shouldAdd) {
        addAll(listOf("Apple", "Banana", "Cherry"))
    }
}

val medals = buildMap<String, Int> {
    put("Gold", 1)
    putAll(listOf("silver" to 2, "Bronze" to 3))
}

객체에 추가 작업 수행: also

수신 객체를 받고 그 수신 객체에 대해 어떤 동작을 수행한 후 수신 객체를 돌려줌

  • also는 람다 안에서 수신 객체를 인자로 참조함
    • 파라미터 이름을 부여하거나 it을 사용해야 함
  • also 코드는 “그리고 다음을 객체에게 수행한다.” 라고 해석할 수 있음

also를 사용해 기능을 추가하기

fun main() {
    val fruits = listOf("Apple", "Banana", "Cherry")
    val uppercaseFruits = mutableListOf<String>()
    val reversedLongFruits = fruits
        .map{ it.uppercase() }
        .also{ uppercaseFruits.addAll(it) }
        .filter { it.length > 5 }
        .also { println(it) }
        .reversed()
		// [BANANA, CHERRY]
    println(uppercaseFruits)
		// [APPLE, BANANA, CHERRY]
    println(reversedLongFruits)
		// [CHERRY, BANANA]
}

0개의 댓글