7장에서는 영역함수 let, run, apply, also에 대해 설명한다.

1. apply로 객체 생성 후에 초기화

객체를 사용하기 전에 생성자 인자만으로는 할 수 없는 초기화 작업이 피요할 땐 apply 함수를 사용한다.

// apply 함수는 this를 인자로 전달하고 this를 리턴하는 확장 함수이다. 
// 명시된 블록을 수신자인 this와 함께 호출하고 해당 블록이 완료되면 this를 리턴한다.  
inline fun <T> T.apply(block: T.() -> Unit): T

OFFICERS라는 데이터베이스 테이블에 매핑되는 Officer Entity가 있다고 가정하자.
이 때, SQL Insert 작성에서 문제가 하나 있다.

엔티티를 저장하는 동안 데이터베이스가 기본키를 생성한다면 제공된 객체가 새로운 키로 갱신되어야 한다는 점

위의 문제를 해결하기 위한 방법 = SimpleJdbcInsert.executeAndReturnKey()
// 칼럼 이름과 값으로 이루어진 맵을 인자로 받아 데이터베이스에서 생성된 기본 키 값을 리턴한다.

@Repository
class JdbcOfficerDAO(private val jdbcTemplate: JdbcTemplate) {
    private val insertOfficer = SimpleJdbcInsert(jdbcTemplate)
        .withTableName("OFFICERS")
        .usingGeneratedKeyColumns("id")
    
    fun save(officer: Officer) =
        officer.apply {
                id = insertOfficer.executeAndReturnKey(
                mapOf("rank" to rank,
                    "first_name" to first,
                    "last_name" to last))

        }
// ... 
}

2. 부수 효과를 위해 also 사용하기

코드 흐름을 방해하지 않고 메시지를 출력하거나 다른 부수 효과를 생성하고 싶다면 also 함수를 사용하라.

also 함수는 코틀린 표준 라이브러리에 있는 확장 함수이다. 이를 사용해 부수 효과를 생성하는 동작을 수행한다.

// also는 모든 제네릭 타입 T에 추가되고 block인자를 실행시킨 후에 자신을 리턴한다.
public inline fun <T> T.also(
    block: (T) -> Unit
): T

also로 콘솔 출력과 로그 기록하기.

val book = createBook()
    .also { println(it) } // 블록 안에서의 객체를 it 라고 한다. 
    .also { Logger.getAnonymousLogger().info(it.toString()) }
    .run {
        assertThat(latitude, `is`(closeTo(42.36, 0.01)))
        assertThat(longitude, `is`(closeTo(-71.06, 0.01)))
    } // run 함수는 블록 내에서 여러 작업을 수행할 때 사용 

also는 컨텍스트 객체를 리턴하기 때문에 추가호출을 함께 연쇄시키기에 용이함. 따라서 로그 기록, 유효성 검사 등 부가적 기능에 활용하면 좋을 것 같음.

  • run 함수는 람다의 값을 리턴
  • also 함수는 컨텍스트 객체를 리턴

3. let 함수와 엘비스 연산자 사용하기

코드 블록 실행 중 널이라면 기본값을 리턴하고 싶을 때 엘비스 연산자를 결합한 안전호출 연산자와 함께 let 영역 함수를 사용하라.

// let 함수는 컨텍스트 객체가 아닌 블록의 결과를 리턴한다. 
public inline fun<T,R> T.let(
    block: (T) -> R
): R

문자열 대문자 변경과 특수한 입력 처리

// let 함수는 내부 블록에서 when 조건을 감싸 필요한 모든 경우를 처리하고 변환된 문자열을 리턴한다. 
fun processString(str: String?) = 
    str.let {
        when {
            it.isEmpty() -> "Empty"
            it.isBlank() -> "Blank"
            else -> it.capitalize()
        }
    } ?: "Null" 
// null인 경우 문자열 "Null" return -> 널이 될 수 있는 경우와 널이 될 수 없는 경우 모두 쉽게 처리 가능

4. 임시 변수로 let 사용하기

연산 겨로가를 임시 변수에 할당하지 않고 처리하고 싶을 땐 연산에 let 호출을 연쇄하고 let에 제공한 람다 또는 함수 레퍼런스 안에서 그 결과를 처리하자.

// let 예제 (리팩토링 이전)
val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3}
println(resultList)
// let 예제 (리팩토링 이후)
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter {it > 3}.let {
    println(it)
    // 추가 작업 수행 가능 
}
import com.google.gson.Gson
import java.net.URL

data class AstroResult(
    val message: String,
    val number: Number,
    val people: List<Assignment>
)

data class Assignment(
    val craft: String,
    val name: String
)

fun main() {
    Gson().fromJson(
        URL("http://api.open-notify.org/astros.json").readText(),
        AstroResult::class.java
    ).people.map { it.name }.let(::println) // 이 때, let -> also 로 바꿔쓸 수 있음. (여기선 결과가 같음)
}
[Sergey Ryzhikov, Kate Rubins, Sergey Kud-Sverchkov, Mike Hopkins, Victor Glover, Shannon Walker, Soichi Noguchi]
  • let vs also
    • let 함수는 블록의 결과를 리턴
    • also 함수는 컨텍스트 객체를 리턴

# 참고 apply, let, also 차이

data class Customer (
    var name: String? = null,
    var age: Int? = null,
    var phone: String? = null,
    var address: String? = null
) 

apply

프로퍼티에 값을 할당할때 유용

// apply 적용 
val jungwoon = Customer().apply {
    name="Jungwoon"
    age=31
    phone="01012341234"
    address="Seoul"
}
// apply 적용 X 
val jungwoon = Customer()
jungwoon.name = "Jungwoon"
jungwoon.age = 31
jungwoon.phone = "01012341234"
jungwoon.address = "Seoul"

let

null 검사를 하고 null 이 아닐때만 코드를 실행할때 유용

// let 적용 
val jungwoon = Customer(
    name = "Jungwoon",
    age = 31,
    phone = "01012341234",
    address = "Seoul"
)

jungwoon?.let {
    print(it.name)
    print(it.age)
    print(it.phone)
    print(it.address)
}
// let 적용 X 
val jungwoon = Customer(
    name = "Jungwoon",
    age = 31,
    phone = "01012341234",
    address = "Seoul"
)


if (jungwoon != null) {
    print(jungwoon.name)
    print(jungwoon.age)
    print(jungwoon.phone)
    print(jungwoon.address)
}

also

데이터를 할당하기 전에 유효성 검사등을 할 때 유용

// also 적용 
val validData = jungwoon.also {
    requireNotNull(it.name)
    requireNotNull(it.age)
    requireNotNull(it.phone)
    requireNotNull(it.address)
}
// also 적용 X
var validData: Customer? = 
    if (jungwoon.name != null &&
        jungwoon.age != null && 
        jungwoon.phone != null && 
        jungwoon.address != null) {
        jungwoon
    } else {
        null
    }

Exercism Raindrops

기본코드

object Raindrops {

    fun convert(n: Int): String {
        TODO("Implement this function to complete the task")
    }
}
// 풀이  
object Raindrops {

    fun convert(n: Int): String = buildString {
        if(n % 3 == 0) append("Pling")
        if(n % 5 == 0) append("Plang")
        if(n % 7 == 0) append("Plong")
        if(isEmpty()) append(n)
    }
}

태그:

카테고리:

업데이트:

댓글남기기