java에는 없고 kotlin에만 있는 함수로 let, also, apply, run 이 있다. 하나씩 살펴보자.
let
fun main() {
// let
val now : LocalDateTime?= null
// 타입에 대한 제네릭을 받아서 리턴해줌 -> map이랑 비슷
/**
* @kotlin.internal.InlineOnly
* public inline fun <T, R> T.let(block: (T) -> R): R {
* contract {
* callsInPlace(block, InvocationKind.EXACTLY_ONCE)
* }
* return block(this)
* }
*/
val kst = now?.let{
// 엘비스 연산자를 통해 있을 때만 처리하도록 함
println(it)
}
// 안에서 사용되는 지역 변수를 설정함
// 타입도 지정이 가능
val kst2 = now?.let{localDateTime ->
println(localDateTime)
}
val kst3 = now?.let{localDateTime:LocalDateTime ->
println(localDateTime)
}
// let의 특징 - > 마지막에 있는 것이 리턴됨
// 스코프 안에서
// return을 적지 않음
val kst4 = now?.let {localDateTime ->
println("")
"let scope"
} ?: LocalDateTime.now()
// let -> 특정한 객체를 받아서 마지막 라인을 리턴, default는 it으로 쓰지만 변수명을 쓸 수 있음
}
fun logic(userDto: UserDto?): UserResponse? {
return userDto?.let {
println(it)
UserEntity(
name = it.name
)
}?.let { userEntity ->
println(userEntity)
UserResponse(
name = userEntity.name
)
}
}
data class UserDto(
var name:String?=null,
)
data class UserEntity(
var name:String?=null
)
data class UserResponse(
var name:String?=null
)
// map 이랑 let 차이 ?
// map -> collection, let -> 모든 객체
let 함수란?
let 함수는 Kotlin의 고차 함수 중 하나로, 호출한 객체를 인자로 받아 원하는 작업을 수행하고 마지막 표현식의 결과를 반환한다. 주로 null이 아닐 경우에만 특정 로직을 실행하거나, 람다 표현식 내부에서 사용되는 지역 변수를 명시적으로 지정할 때 사용된다.
기본 사용법
기본적으로 let 함수는 객체가 null이 아닐 때 람다 표현식을 실행한다.
val now: LocalDateTime? = null
val kst = now?.let {
println(it)
}
지역 변수 설정
let 함수 내부에서 사용하는 변수의 이름을 직접 지정할 수 있다.
val kst2 = now?.let { localDateTime ->
println(localDateTime)
}
타입 지정
또한 변수의 타입을 명시적으로 지정할 수도 있다.
val kst3 = now?.let { localDateTime: LocalDateTime ->
println(localDateTime)
}
반환값
let 함수는 마지막 표현식의 결과를 반환한다. 따라서 원하는 값을 반환하려면 마지막 줄에 그 값을 적어주면 된다.
val kst4 = now?.let { localDateTime ->
println("")
"let scope"
} ?: LocalDateTime.now()
let과 map의 차이
let은 모든 객체에 사용할 수 있는 반면, map 함수는 주로 컬렉션에서만 사용된다.
fun logic(userDto: UserDto?): UserResponse? {
return userDto?.let {
UserEntity(
name = it.name
)
}?.let { userEntity ->
UserResponse(
name = userEntity.name
)
}
}
also
Kotlin의 also 함수는 let과 유사하게 객체를 인자로 받아 원하는 작업을 수행할 수 있다. 그러나 also의 주된 특징은 작업이 끝나면 그대로 원래의 객체를 반환한다는 것이다. also 함수의 동작 방식과 사용 케이스에 대해 살펴보자.
fun main() {
// also -> 또한
// 넘어온 객체를 그대로 리턴 -> map이랑 동일한 부분은 키워드를 it으로 받되 변경 가능
// 타입도 지정 가능
// let이랑 차이 ? -? let은 마지막 라인을 리턴
// also는 받은 파라미터를 그대로 리턴한다.
/**
* @kotlin.internal.InlineOnly
* @SinceKotlin("1.1")
* public inline fun <T> T.also(block: (T) -> Unit): T {
* contract {
* callsInPlace(block, InvocationKind.EXACTLY_ONCE)
* }
* block(this)
* return this
* }
*/
UserDto(
name = "홍길동"
).also {
println(it)
// let 이랑 차이는 받은 것을 그대로 리턴하므로 아래 코드를 리턴하지 않음
UserDto(name = "유관순")
}
// 그대로 넘기므로 로깅 등에 사용
}
also 함수의 정의와 특징
also 함수는 람다 표현식을 인자로 받아 이 블록 안에서 원하는 로직을 수행한다. 그 후, 받은 객체를 그대로 반환한다. 이를 통해 메서드 체이닝(method chaining)이나 로깅 같은 작업이 용이해진다.
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
기본 사용법
also 함수를 이용하여 객체를 생성과 동시에 그 객체에 대한 추가 작업을 할 수 있다.
UserDto(
name = "홍길동"
).also {
println(it)
}
반환값
also 함수는 작업이 끝나면 원래의 객체를 반환한다. 이 특징은 let과는 다르다. let은 마지막 표현식의 결과를 반환하기 때문에, 두 함수의 반환값에 차이가 생긴다.
// also는 원래 객체를 그대로 반환
val user = UserDto(name = "홍길동").also {
UserDto(name = "유관순")
}
println(user.name) // 출력: "홍길동"
로깅 및 디버깅
also 함수는 로깅 또는 디버깅에 자주 사용된다. 이는 작업 중간에 상태를 확인하거나 로그를 출력하고 싶을 때 유용하다.
val list = mutableListOf(1, 2, 3).also {
println("리스트 초기 상태: $it")
}.apply {
add(4)
}.also {
println("리스트 최종 상태: $it")
}
apply
Kotlin에서는 객체의 초기화와 동시에 다양한 설정을 쉽게 할 수 있도록 apply 함수를 제공한다.
fun main() {
// apply, 생성자 패턴
// t 라는 제네릭에 함수가 있음 -> t 제네릭 확장 함수
// block이라는 람다식이 넘어가게 되어 있음
// 반환형이 없는 Unit이 매개변수로 넘어감
// also 랑 비교 ? -> also의 경우 타입을 넣어줌 -> it.name으로 접근이 가능
// apply는 특정 클래스에 function을 추가 -> 확장 함수 람다
// 람다식이 넘어가는데 그것이 확장함수
// apply는 초기화를 시킬 때 많이 사용하는데 키워드가 this로 넘어옴 -> 수신 받은 객체 본인
/**
* @kotlin.internal.InlineOnly
* public inline fun <T> T.apply(block: T.() -> Unit): T {
* contract {
* callsInPlace(block, InvocationKind.EXACTLY_ONCE)
* }
* block()
* return this
* }
*/
var userDto = UserDto().apply {
this.name="홍길동"
name = "홍길동"
""
}
// 수신 받은 객체를 리턴하는데, this를 변경할 수는 없음
// 초기화 블록
// 마지막 라인을 리턴하는 게 아니고 수신 받은 객체를 그대로 리턴하므로 변경 x
println(userDto)
// also 와 유사 -> 수신 받은 객체를 그대로 전달
// 생성자때 많이 사용한다.
}
fun String.myStr(){
// 본인에게 접근할 때 this로 접근
}
fun <T> T.myStr():Unit{
// 즉, T에다가 본인 자신 접근 -> this.
// 모든 제네릭에다가 apply 적용 모든 제네릭 클래스들이 가지고 있는 기본 메서드 -> 넘겨주는 확장 함수는 이 안에서만 쓸 수 있다.
// 본인을 던져주기 때문에
// 즉 apply에 넘어가는 것은 확장 함수가 넘어가면 됨
}
fun UserDto.myUserDto(){
println(this.name)
}
apply 함수의 정의와 특징
apply 함수는 수신 객체(this)의 람다에서 호출되는 확장 함수이다. 이 함수는 초기화 작업이나 값을 변경할 때 주로 사용된다.
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
기본 사용법
apply 함수는 객체 생성과 동시에 해당 객체의 필드를 쉽게 초기화할 수 있다.
val userDto = UserDto().apply {
name = "홍길동"
}
이 예시에서는 UserDto 객체를 생성하고, apply 함수를 이용해 이름을 초기화한다. 이렇게 하면, 생성자에서 이런 작업을 별도로 해주지 않아도 된다.
apply와 also의 차이
apply함수는 수신 객체를this로 참조. 따라서, 수신 객체의 멤버에 바로 접근할 수 있다.also함수는 수신 객체를it로 참조.
// apply 사용 예
val user1 = UserDto().apply {
name = "홍길동"
}
// also 사용 예
val user2 = UserDto().also {
it.name = "유관순"
}
확장 함수의 사용
apply는 수신 객체의 멤버 뿐만 아니라 수신 객체를 이용한 확장 함수도 호출할 수 있다.
fun UserDto.init() {
name = "초기 이름"
}
val userDto = UserDto().apply {
init()
}
수신 객체를 this로 참조하여 람다 블록 내에서 자유롭게 멤버나 함수를 호출할 수 있다. 이 함수는 객체 생성과 초기화를 한 번에 수행하므로 코드를 간결하게 만들어 준다.
run
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/**
* @author : Rene
* @since : 2023/10/22
*/
fun main(){
// run , 지역 scope
/**
* @kotlin.internal.InlineOnly
* public inline fun <T, R> T.run(block: T.() -> R): R {
* contract {
* callsInPlace(block, InvocationKind.EXACTLY_ONCE)
* }
* return block()
* }
*/
// let 함수와 동일하게 마지막에 있는 게 리턴된다.
// 확장 함수를 전달 받아 안에 있는 것을 바로 접근 가능하다.
val userDto = UserDto("").run {
name = "홍길동"
"empty"
}
println("result $userDto")
val x = 10
// 주로 지역 스코프를 만들 때 많이 사용된다.
// 안에 있는 x를 통해서 연산이 됨
val sum = run {
val x = 2
val y = 3
x + y
}
println(sum)
val now: LocalDateTime? = null
val n = now?.let { it }?: LocalDateTime.now()
val d = now?.let {
val minusTime = it.minusDays(1)
minusTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
// 특정 스코프를 만들어 주어야 할 때
// 위에서는 로직이 필요 없음 -> 로직을 만들어야 한다면 스코프를 만들어야 하기 때문에 run을 만들고 사용하고자 하는 로직을 넣는다.
}?: run {
val now = LocalDateTime.now()
val minusTime = now.minusDays(1)
minusTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"))
}
println("result $d")
}
run 함수의 정의와 특징
run 함수는 let과 유사하게 마지막 라인을 리턴하는 특징이 있다. 다만, run은 수신 객체를 this로 참조하기 때문에, 람다 블록 내에서 수신 객체의 멤버에 바로 접근할 수 있다.
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
기본 사용법
val userDto = UserDto("").run {
name = "홍길동"
"empty"
}
println("result $userDto") // 출력: "result empty"
지역 스코프의 생성
run 함수는 주로 지역 스코프를 생성할 때 사용된다.
val sum = run {
val x = 2
val y = 3
x + y
}
println(sum) // 출력: 5
여기에서 run 내부의 x와 y는 run 블록 외부에서 접근할 수 없다. 이렇게 지역 스코프를 생성하여 변수의 범위를 제한할 수 있다.
조건부 로직에 활용
run은 특정 조건에 따라 다른 로직을 실행할 때 유용하게 사용될 수 있다.
val d = now?.let {
it.minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
} ?: run {
LocalDateTime.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"))
}
println("result $d")
이 예시에서는 now 값이 null인지 아닌지에 따라 다른 로직을 실행한다. null이 아니면 let을, null이면 run을 사용하여 로직을 처리한다.
이처럼 Kotlin의 run 함수는 주로 지역 스코프를 만들거나, 조건부로 다른 로직을 실행할 때 유용하게 사용된다. 수신 객체를 this로 참조하여 람다 블록 내에서 자유롭게 멤버나 함수를 호출할 수 있어 매우 유용한 함수이며, let과 함께 많이 사용된다.
reference
- fastcampus 시그니처 백엔드 path 초격차 패키지 course 5
- https://kotlinlang.org/spec/introduction.html#reference
'Programming > Kotlin' 카테고리의 다른 글
| Java와 Kotlin 비교: default value, when, 확장함수 (1) | 2023.10.22 |
|---|---|
| Java와 Kotlin 비교 : map, 고차함수, 클래스 - 상속 인터페이스, 코틀린의 property 개념 (0) | 2023.10.22 |
| Java와 Kotlin 비교 : 변수, null 안정성, 엘비스 연산자, 가변/불변 컬렉션 (1) | 2023.10.22 |