Kotlin takeIf 알아보기

2024. 5. 10. 20:34·Kotlin

Kotlin에서 제공하는 다양한 표준 라이브러리 함수들은 보다 간결하고 안전한 코드를 작성하는데 도움이 된다. 오늘은 그중에서 takeIf를 기본 사용법과 여러 가지 예제를 통해 이해하기 쉽게 설명해 본다.

 

takeIf란?

공식 문서를 참고해 보면

Returns this value if it satisfies the given predicate or null, if it doesn't.

주어진 Predicate를 만족하면 그 값을 반환하고 만족하지 않다면 null을 반환하는 함수이다. 자바의 if와 비교하자면, if은 조건에서 사용한 뒤 본문에서 필요하다면 다시 호출해 사용해야 하지만, takeIf문은 takeIf를 사용한 객체나 조건을 호출 없이 람다에서 다시 사용할 수 있어 특정 상황에서 유용하게 사용할 수 있다.

 

takeIf 구조

inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?

함수 시그니처를 보면, 제네릭 타입 T를 받는 takeIf는 Predicate를 만족하면 T? 타입의 값을 반환한다. 즉, Predicate가 true면 원래 값 T를 그대로 반환하고, false면 null을 반환한다. T는 제네릭 타입이므로 어떠한 타입의 객체에도 takeIf를 사용할 수 있고, 람다 파라미터로 T를 가지므로 내부에서 추가적인 조건을 만들 수도 있다.

 

값에 takeIf 사용하기

- 조건과 값

예를 들어 조건 A와 값 a가 있다고 하자. 

a.takeIf { A }

- 만약 조건 A가 true라면 위 코드는 a를 반환하고 조건 A가 false라면 null을 반환한다.

 

- 간단한 예시로 a를 3, A를 2보다 크다라는 조건이라고 한다면 위 코드는 3을 반환하게 된다.

 

- 상태와 값

이번에는 상태 A, B, C와 값 a가 있다.

1) a.takeIf { it == A }
1-1) a.takeIf { value -> value == A }

2) a.takeIf { it == B || it == C }

- 람다 내부에서 파라미터로써 takeIf를 사용한 값(a)을 호출할 수 있다. 암시적 파라미터 it으로도 사용 가능하며, it shadow 경고를 피하기 위해 (1-1)처럼 직접 이름을 설정해 사용할 수도 있다.

 

- (1)은 a가 A라면 a를 반환하고 동일하지 않다면 null을 반환한다.

 

- (2)는 a가 B 또는 C라면 값 a를 반환, 두 상태 모두 아니라면 null을 반환한다. 

 

예를 들자면 X, Y, Z를 가진 enum class State가 있고, A, B, C는 각각 State.X, State.Y, State.Z이며 a는 A이다.

enum class State {
    X, Y, Z
}

val a: State = State.X

- (1)은 a가 A(State.X)이므로 a를 반환한다.

 

-(2)는 a가 B(State.Y)나 C(State.Z)가 아니므로 null을 반환한다.

 

- non-null

사용 시 주의할 점이 있다. null 가능성이 있는 필드에서는 문제없이 사용할 수 있지만, null을 허용하지 않는 필드에서 사용하려면 null일 때의 값을 지정해 주어야 문제가 생기지 않는다.

a.takeIf { A }?: b

val count: Int = a.takeIf { it > 3 }
val count: Int = a.takeIf { it > 3 } ?: 0

- ?: 를 사용해서 null일 때의 값을 지정해 주면 조건 A가 true라면 a를, false라면 b를 사용하여 null이 반환될 가능성이 사라져 문제가 해결된다.

 

- count에서 a가 2라면 위의 코드에서 Int 타입이지만 null이 반환되어 오류가 발생하기 때문에, 아래 코드처럼 예외 처리를 해주어야 한다.

 

- 추가 작업 실행

단순히 값을 반환하는 것뿐만 아니라 조건에 따라 작업을 실행할 수도 있다.

1) a.takeIf { A }?.run { a.value = b } ?: run { a.value = c }

2) a.takeIf { A }?.let { b.value = it } ?: run { b.value = c }

- let과 run의 차이점은 let은 람다 파라미터가 암시적 파라미터인 it이거나 명시적으로 지정할 수 있는 반면, run은 람다 파라미터가 없고 this를 수신 객체로 사용해 생략 가능하다.

 

- (1)은 조건 A가 만족한다면 run을 실행해 a의 값을 b로 설정하고, 만족하지 않는다면 ?: run을 실행해 a의 값을 c로 설정한다.

 

- (2)는 조건 A가 만족한다면 b의 값을 it(a)로 설정하며, 만족하지 않는다면 b의 값을 a로 설정한다. a가 3, b가 7, c가 5라 하고 조건 A가 b가 c보다 크다라고 한다면, true이므로 let이 실행되어 b를 a의 값인 3으로 설정하게 된다.

 

하지만 (1)은 결국 아래와 같은 동작을 하므로, (2)처럼 takeIf 이후 추가 작업에서 최초 a를 사용할 것이 아니라면 굳이 takeIf를 사용할 필요가 없다.

if (A) { a.value = b } else { a.value = c }

 

조건에 takeIf 사용하기

앞서 설명한 값에 takeIf를 사용하는 방법 외에 조건문 자체에도 takeIf를 사용할 수 있다. 하지만 takeIf는 값 기반의 체이닝 흐름을 만드는데 의미가 있는 함수라서 굳이 조건에 사용할 이유가 없다.

A.takeIf { it }

A.takeIf { contidion -> }

조건 A에 takeIf를 사용하고 람다 블록에서 해당 조건인 it을 호출하면 해당 코드는 조건 A 또는 null을 반환하며, 명시적 파라미터로 설정해 사용할 수도 있다.

 

- it, !it

A.takeIf { it }
A.takeIf { !it }

위와 같이 람다 본문에서 it이나 !it을 사용하면 조건의 만족 여부를 판단해 Boolean 값이나 null을 반환한다.

- it을 사용할 때 A가 true라면 it이 true가 되므로 true를 반환하고, false라면 it이 false이므로 null을 반환한다.

- !it을 사용한다면 반대로 A가 false일 때 !it이 true가 되므로 false를 반환하고, A가 true라면 !it이 false가 되므로 null을 반환한다.

 

예외 처리를 하면서 값 또는 작업을 반환해 주는 식으로 사용할 수도 있다. 하지만 결국 if (A) a else b이므로 굳이 if문을 대체할 필요가 없다.

1) A.takeIf { it }?.let { a } ?: b 

2) A.takeIf { it }?.let { Job a } ?: run { Job b }

- (1)은 조건 A가 만족한다면 a를 반환하고 만족하지 않는다면 b를 반환한다. 

 

- (2)는 조건 A가 만족한다면 작업 a를 실행하고 만족하지 않는다면 작업 b를 반환한다. 

 

두 가지 방법의 차이점

그렇다면 값에 takeIf를 사용하는 방법과 조건에 takeIf를 사용하는 방법의 차이점은 무엇이 있을까. 사실 큰 차이는 없지만 특정 상황에서는 차이가 발생할 수 있다. takeIf는 if문과는 달리 takeIf 앞의 값이 항상 먼저 실행되기 때문이다.

isLinkValid : Boolean
linkURL : String
URL : Function

1) isLinkValid.value.takeIf { it }?.let { URL(linkURL.value) }

2) URL(linkURL.value).takeIf { isLinkValid.value }

linkURL의 값이 유효한 URL형식인지에 대한 변수 isLinkValid와 linkURL을 URL형식으로 만들어주는 함수 URL이 있다. 이때 isLinkValid의 값은 linkURL이 변할 때마다 자동으로 계산된다고 하자.

 

- (1)은 isLinkValid가 true라면 let을 실행해서 URL(linkURL.value)를 반환하고 false라면 null을 반환한다. 참고로 앞서 말했듯이 if (isLinkValid.value)와 동일하기 때문에 굳이 이 구문을 쓸 필요가 없다.

 

- (2)은 isLinkValid가 true라면 URL(linkURL.value)를 반환하고, false라면 null을 반환한다.

 

여기서 URL()는 허용하지 않는 URL형식을 입력받는다면 오류가 발생한다. 만약 오류가 발생한다면 1번과 2번 중 어느 곳에서 생기게 될까?

 

- 오류는 (2)에서 발생할 수 있다. URL(linkURL.value)를 실행한 후 takeIf를 실행하기 때문이다. 1번은 만약 linkURL이 유효한 형식이 아니라면 isLinkValid가 false이므로 let에 도달할 수 없어 오류가 발생하지 않지만, 2번은 linkURL가 유효한 형식이 아니라면 즉시 에러가 발생하게 된다. 

 

하지만 결국 (2)를 아래와 같은 if문 대신 사용할 이유가 없으므로, takeIf는 조건에 사용하기보단 완전한 값에 주로 사용하게 된다.

if (isLinkURLValid.value) URL(linkURL.value) else null

 

'Kotlin' 카테고리의 다른 글
  • Ktor로 HTTP Client 만들기
  • Kotlin LocalDateTime 날짜/시간 형식 지정하기
  • Kotlin Long to LocalDateTime, LocalDateTime to Long 변환하기
브애애앳
브애애앳
  • 브애애앳
    디벨로퍼즐
    브애애앳
  • 전체
    오늘
    어제
    • 분류 전체보기 (28)
      • Android (4)
      • Android App (1)
      • Figma (1)
      • Jetpack (2)
      • Jetpack Compose (15)
      • Kotlin (4)
      • Tistory (1)
  • 링크

    • 카카오톡 문의
  • 인기 글

  • hELLO· Designed By정상우.v4.10.0
브애애앳
Kotlin takeIf 알아보기
상단으로

티스토리툴바