[Kotlin] 알아두면 편한 Kotlin 기능 3개 소개
Android Developer Newsletter를 보다가 나도 잘 몰랐던 기능인데 알아두면 좋을 거 같아서 올만에 블로그를 펴본다!
<목차>
- sealed interface
- value class
- takeIf & takeUnless
1. sealed interface
이건 sealed class를 먼저 알아야 한다,..
sealed class:
- enum class와 비슷하게 타입을 제한적으로 사용할 수 있다. (그래서 when 구절에서 else문을 사용하지 않아도 됨!!)
- enum class와의 차이점: enum class의 인스턴스는 하나의 인스턴스로 한정되어 있지만, sealed class는 여러 인스턴스를 가질 수 있다.
예시를 보자.
enum class는 다음과 같이 생성자가 다른 인스턴스를 생성할 수 없고, 생성자가 똑같은 하나의 인스턴스만 만들 수 있다.
enum class UiState {
LOADING,
SUCCESS(val data: String), // Error
ERROR(val error: Exception) // Error
}
하지만 sealed class는 다음과 같이 다양한 생성자의 인스턴스를 생성할 수 있다.
sealed class UiState {
object Loading : UiState()
data class Success(val data: String) : UiState() // OK
data class Error(val error: Exception) : UiState() // OK
}
sealed class는 '봉인된' 이라는 이름처럼 abstract class이고, 생성자는기본 protected이고 private로 지정할 수 있다.
sealed interface:
- sealed class는 하위 클래스가 동일한 파일에 있어야 하지만
- sealed interface를 상속받으면 같은 모듈, 패키지 내에서도 하위 클래스를 만들 수 있다.
sealed interface Error
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error
이처럼 sealed interface는 복잡한 상태나 데이터 타입을 관리하는 데에 도움이 될 듯하다.
2. value class
value class:
- wrapper class로, (컴파일러가 객체를 생성하지 않고 값을 매핑해 주기 때문에) 객체 할당으로 인한 오버헤드를 줄일 수 있다.
이게 무슨 소리냐... 하면...
일단 wrapper class란, 기본형 변수를 감싸는 클래스들이다.
예를 들어 자바에서 기본형 boolean의 wrapper class는 Boolean이고, 기본형 int의 wrapper class는 Integer이다.
wrapper class는 해당 타입에 대한 다양한 메서드(.equals(), .toString()...)를 제공해주듯이, value class 또한 아래 예시 코드처럼 기본형 변수를 활용하기 좋게 만드는 클래스라고 봐도 좋을 거 같다.
// 기본적형 변수인 millis를 관리함. millis, seconds 메서드를 제공함
@JvmInline
value class Duration private constructor(
val millis: Long,
) {
companion object {
fun millis(millis: Long) = Duration(millis)
fun seconds(seconds: Long) = Duration(seconds * 1000)
}
}
// 다음과 같이 활용할 수 있음
fun reserveAlarm(duration: Duration) =
println("$duration.millis millis 후에 알람이 울립니다.")
value class를 사용해 Long 속성의 프로퍼티를 가진 Duration 클래스를 만들었다.
이는 reserveAlarm(duration)과 같이 사용되며, 런타임 시 Duration라는 class가 아닌 Long으로 처리된다!
Duration라는 class를 객체로 생성하지 않아 객체 할당으로 인한 오버헤드도 줄어들 수 있는 것이다.
프로퍼티를 명확히 표현하고 깔끔하게 관리할 때 사용하기 좋아보인다. 객체 생성 비용도 줄여주기 때문에 최적화할 때 좋을듯
3. takeIf & takeUnless
takeIf: 조건 결과가 참이면 자기 자신(this)을, 거짓이면 null 을 반환한다.
takeunless: 위와 반대로, 조건 결과가 참이면 null을, 거짓이면 자기 자신을 반환한다.
각 함수의 정의는 다음과 같다.
// takeIf: 조건 결과가 참이면 자기 자신, 거짓이면 Null 반환
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?
= if (predicate(this)) this else null
// takeUnless: 조건 결과가 거짓이면 자기 자신, 참이면 null 반환
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T?
= if (!predicate(this)) this else null
예시 코드는 다음과 같다.
val input = "Hello"
val validated = input.takeIf { it.length > 3 } ?: "Default"
// input 길이가 3이 넘으므로 참 => takeIf는 참일 때 자기 자신 반환
println(validated) // Hello
val negativeNumber = -5.takeUnless { it > 0 }
// -5는 0보다 크지 않으므로 거짓 => takeUnless은 거짓일 때 자기 자신 반환
println(negativeNumber) // -5
if문을 사용했다면 코드가 더 길어졌을텐데, takeIf를 사용함으로써 코드 길이를 줄이고 가독성을 높일 수 있다.
null을 반환할 수도 있다는 특성을 살려, 엘비스 연산자(?:)를 활용하여 코드를 더 짧게 만들 수 있다.
공식 문서나 참고한 블로그에서 wrapper class, 맹글링, 박싱/언박싱 등등 다양한 용어들도 같이 나왔는데, 이 글에서는 간단히(?) 소개만 해보려고 설명을 줄였다...
근데 간단하게 소개하는 것부터가.. 요약을 잘 할려면 내용을 잘 알고 있어야 해서 얼떨결에 열공함... 오랜만의 포스팅이라 가벼운 마음으로 쓰고 싶었는데 ㅠㅠㅋㅋㅋㅋ
암튼 맨날 눈팅만 하다가 글로 써보니까 이해도 잘 되고 재밌는 거 같다.
value class의 특징 및 data class와의 차이점도 알아두면 좋을 거 같아서 추가...
- 반드시 @JvmInline 어노테이션과 함께 사용됨
- val 프로퍼티 하나만 가질 수 있음
- equals(), toString(), hashCode() 메서드만 자동 생성 (data class는 여기에 copy(), componentN()까지 생성)
- === (동일성)연산 지원 안 함 (data class는 ==, === 지원)
자주 사용하는 것들 제외하고 자주 사용하지 않지만 유용해보이는 것들 위주로 올렸다!
원문은 7개의 기능을 소개해주니까 궁금한 사람들은 봐보시길
🚀 Kotlin Secrets: 7 Powerful Features Used By Advanced Developers
Have you ever felt like you’re not getting the most out of Kotlin? 🤔 I know I have.
proandroiddev.com
참고
[Kotlin] Sealed Class and Sealed Interface
Kotlin 에서의 원시값 포장과 그에 적합한 클래스 선택하기
Kotlin의 Value Class로 성능 최적화하기 (Codes like a class, works like an int.)
코틀린 의 takeIf, takeUnless 는 언제 사용하는가?