
enum class Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
// 생성자를 이용한 초기화
enum class NetworkState(val code: Int) {
SUCCESS(200),
ERROR(404),
TIMEOUT(408)
}
enum 클래스를 이용해 각 상수를 유일한 인스턴스(객체)로 정의할 수 있다.MONDAY, TUESDAY 등 은 Day 클래스의 각 상수는 하나의 객체로만 존재한다.고정된 상수 집합 표현 (요일, 색상 등 미리 정해져서 안 바뀌는 값)
단순한 값이나 상태를 나열 (열거된 값들로 상태 명확히 구분할 수 있을 때)
반복 가능한 값을 활용 (열거된 값 순회 또는 비교)
enum class NetworkState(val code: Int) {
SUCCESS(200),
ERROR(404),
TIMEOUT(408)
}
fun main() {
// values() 사용
val states = NetworkState.values()
println("All network states:")
states.forEach { state ->
println("State: ${state.name}, Code: ${state.code}")
}
// entries 사용
val stateEntries = NetworkState.entries
stateEntries.forEach { state ->
println("State: ${state.name}, Code: ${state.code}")
}
}
values()또는entries속성을 통해 모든 값 목록을 가져올 수 있다.
entries는 코틀린 1.82에서 추가된 메서드로 enum class의 상수들을 호출할 때 values()보다 entries()를 호출하는 것을 권장한다.
코틀린 1.9.0에선 values()를 대체하는 entries 프로퍼티가 도입됐다.
이것은 미리 할당된 enum 상수의 불변 리스트를 반환한다.
이는 컬렉션으로 작업 시 유용하며 성능 문제를 방지하는 데 도움이 될 수 있다.
values()는 상수들을 선언한 순서대로 담은 Array를 리턴한다.
entries 는 EnumEntries 타입의 불변 리스트로,
enum 상수를 immutable List로 제공하여 직접 수정할 수 없고 안전하게 사용하도록 보장한다.
이 기능은 sealed class로는 제공되지 않는다.
안정성, 성능 개선 : 이미 할당된 불변 리스트를 재사용하므로 메모리 효율이 높다.
일관성 유지 : 상수 목록 고정되어 있으므로 수정으로 인한 에러 발생 X
sealed class는 enum class처럼 특정 상태 집합을 표현하지만, 단일 타입 값만 가질 수 있는 enum과 달리 여러 속성과 다양한 형태의 하위 클래스를 가질 수 있다.
sealed 클래스는 추상 클래스로, 자신을 상속받는 여러 서브 클래스들을 가질 수 있고, 인스턴스를 직접 생성하지 않고 하위 클래스를 통해 생성한다.
sealed 클래스는 자신을 상속받는 하위 클래스가 동일 파일 내에 정의되어야 한다. 이렇게 하면 모든 하위 타입이 컴파일 타임에 고정된다.
sealed 키워드를 사용해 선언하며, 이를 통해 하위 클래스 집합을 강제하여 타입 안정성을 확보할 수 있다.
sealed 클래스를 사용하면 컴파일러가 모든 가능한 하위 타입을 알고 있기 때문에, when 구문에서 모든 경우에 대한 처리가 되었는지 검사하여 타입 안정성을 보장할 수 있다.
when 구문에서 else 구문이 필요하지 않다.
class SignUpContract {
// Events that user performed
sealed class Event : UiEvent {
data class OnUsernameChanged(val username: String) : Event()
data class OnPasswordChanged(val password: String) : Event()
data class OnHobbyChanged(val hobby: String) : Event()
data object OnPasswordVisibilityToggle : Event()
data object OnSignUpButtonClicked : Event()
}
// Possible states for SignUp
enum class SignUpStatus {
IDLE, SUCCESS, FAILURE
}
data class SignUpState (
val username: String = "",
val password: String = "",
val hobby: String = "",
var isPassWordVisibility: Boolean = false,
val status: SignUpStatus = SignUpStatus.IDLE
) : UiState
// Side effects
sealed class Effect : UiEffect {
data class ShowToast(val message: String) : Effect()
}
}
// 일부 코드 생략 (간략히 표현)
override fun handleEvent(event: UiEvent) {
when (event) {
is SignUpContract.Event.OnUsernameChanged -> {
setState(currentState.copy(username = event.username))
}
is SignUpContract.Event.OnPasswordChanged -> {
setState(currentState.copy(password = event.password))
}
is SignUpContract.Event.OnHobbyChanged -> {
setState(currentState.copy(hobby = event.hobby))
}
is SignUpContract.Event.OnPasswordVisibilityToggle -> {
setState(currentState.copy(isPassWordVisibility = !currentState.isPassWordVisibility))
}
is SignUpContract.Event.OnSignUpButtonClicked -> {
if (currentState.username.isValidLength() && currentState.password.isValidLength() &&
currentState.hobby.isValidLength()
) {
...
}
이 클래스의 모든 하위 타입이 같은 파일에 정의되므로 정의된 하위 클래스의 집합이 고정된다.
모든 이벤트를 처리하지 않으면 컴파일 오류가 발생하기 때문에, 실수로 특정 이벤트를 놓치는 상황을 방지할 수 있다.
가독성 향상, 유지보수성 향상, 확장용이, else 문 처리할 필요 없어서 코드가 더 명확해진다.
sealed class를 사용하면, 다중 상속이 불가능하다는 한계가 존재한다.
컴파일러에 자신을 구현하는 타입을 알려주는 인터페이스이다. 따라서 일반적인 interface와 같이 하나의 클래스에서 여러 sealed interface를 구현할 수 있다.
sealed interface 활용// 다수의 sealed interface를 implementation 하는 서브 클래스
sealed interface GeneralError {
object NetworkIssue : GeneralError
object Timeout : GeneralError
object UnknownError : GeneralError
}
// 파일 작업 관련 에러 정의
sealed interface FileError {
data class FileNotFound(val filePath: String) : FileError
data class FileCorrupted(val filePath: String) : FileError
data class General(val error: GeneralError) : FileError // 공통 에러 포함
}
// 네트워크 작업 관련 에러 정의
sealed interface NetworkError {
data class ConnectionLost(val url: String) : NetworkError
data class InvalidResponse(val url: String) : NetworkError
data class General(val error: GeneralError) : NetworkError // 공통 에러 포함
}
// 에러 처리 함수
fun handleFileError(error: FileError): String = when (error) {
is FileError.FileNotFound -> "File not found at path: ${error.filePath}"
is FileError.FileCorrupted -> "File is corrupted at path: ${error.filePath}"
is FileError.General -> handleGeneralError(error.error)
}
fun handleNetworkError(error: NetworkError): String = when (error) {
is NetworkError.ConnectionLost -> "Connection lost while accessing: ${error.url}"
is NetworkError.InvalidResponse -> "Received invalid response from: ${error.url}"
is NetworkError.General -> handleGeneralError(error.error)
}
// 공통 에러 처리 함수
fun handleGeneralError(error: GeneralError): String = when (error) {
GeneralError.NetworkIssue -> "There is a network issue."
GeneralError.Timeout -> "The operation timed out."
GeneralError.UnknownError -> "An unknown error occurred."
}
// 사용 예시
fun main() {
val fileError = FileError.General(GeneralError.Timeout)
println(handleFileError(fileError)) // Output: The operation timed out.
val networkError = NetworkError.ConnectionLost("https://example.com")
println(handleNetworkError(networkError)) // Output: Connection lost while accessing: https://example.com
}
handleError 함수에서LoginErrors 중 CommonError가 발생하면 중첩된 분기 처리를 통해 공통 에러를 처리한다.CommonErrors 인터페이스로 통합해서, 파일 작업과 네트워크 작업 관련 에러가 동일한 공통 에러 정의를 공유한다.싱글톤 패턴을 더 쉽게 사용하기 위해 코틀린에서 사용하는 객체 선언 키워드
Singleton
thread-safe
lazy initialized (외부에서 객체가 사용되는 시점에 초기화 된다.)
코틀린의 object와 companion object 내부 멤버들은 자바의 static처럼 접근되지 않는다.
자바의 static 과 유사하게 사용하기 위해 @JvmStatic과 @JvmField 어노테이션 사용
const val
자바의 static 변수로 간주
고정된 상수 값을 컴파일 타임에 제공하기 때문에 자바의 static final 상수처럼 동작한다.
@JvmStatic과 @JvmField
@JvmStatic : 내부의 변수나 함수를 자바에서 static처럼 접근할 수 있도록 하기 위해 사용한다.
@JvmField : 필드를 자바 static 필드처럼 사용하기 위해 사용한다.
object Config {
var apiUrl = "api.example.com" // 직접적인 static 변수처럼 접근할 수 없다. (사용 불가)
const val TIMEOUT = 5000 // 자바의 static final 상수처럼 동작
@JvmStatic var versionCode = "1.0.0"
@JvmField val connectionType = "WiFi"
@JvmStatic
fun logMessage(message: String) {
println("Log: $message")
}
}
fun main() {
println(KotlinClass.INTEGER_ONE)
println(KotlinClass.BIG_INTEGER_ONE)
KotlinClass.printVersion() // Output: Version: 1.0.0
}
public final class Config {
@NotNull
public static final Config INSTANCE = new Config();
private Config() { }
// ----------------------------------------------------
// @JvmStatic var
@NotNull
private static String versionCode = "1.0.0";
@NotNull
public static final String getVersionCode() {
return versionCode;
}
public static final void setVersionCode(@NotNull String var0) {
Intrinsics.checkNotNullParameter(var0, "<set-?>");
versionCode = var0;
}
// ----------------------------------------------------
// @JvmField
@JvmField
@NotNull
public static final String connectionType = "WiFi";
// ---------------------------------------------------
// var
private static final String apiUrl = "https://api.example.com";
@NotNull
public final String getApiUrl() {
return apiUrl;
}
public final void setApiUrl(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
apiUrl = var1;
}
@JvmStatic
public static void getVersionCode$annotations() {
}
// ----------------------------------------------------
// const val
@NotNull
public static final int TIMEOUT = 5000;
// ----------------------------------------------------
@JvmStatic
public static final void logMessage(@NotNull String message) {
Intrinsics.checkNotNullParameter(message, "message");
String var1 = "Log: " + message;
System.out.println(var1);
}
public static final Config INSTANCE = new Config();
new Config()를 통해 Config 클래스의 인스턴스를 생성하여 할당된다.
static final로 선언되었기 때문에, Config 클래스가 처음 로드될 때 한 번만 생성된다.
이를 통해 싱글톤 패턴을 구현해 모든 접근에서 동일한 객체를 사용할 수 있다.
private Config() { }
val, varconst valConfig.FIELD 형식으로 바로 접근 가능하다.@JvmStatic@JvmFieldConfig.FIELD 형식으로 바로 접근 가능하다.클래스 내부 객체 선언을 위한 object 키워드 (클래스 내부에서 싱글톤 패턴 사용하기 위해 사용한다.)
해당 클래스가 로드 될 때 초기화된다.
companion object 내부 멤버들은 자바의 static처럼 접근되지 않는다.
@JvmStatic : companion object 내부의 변수나 함수를 자바에서 static처럼 접근할 수 있도록 하기 위해 사용한다.안드로이드 공식 문서에 나와있는 식
class KotlinClass {
companion object {
const val INTEGER_ONE = 1
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun printVersion() {
println(versionName)
}
}
}
fun main() {
println(Config.TIMEOUT)
println(Config.versionCode)
println(Config.connectionType)
Config.logMessage("Hello, Config!")
// 일반 val 변수는 직접 접근 불가 (실제 사용할 경우 INSTANCE가 필요)
println(Config.apiUrl)
}
public final class KotlinClass {
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
// const val
public static final int INTEGER_ONE = 1;
// @JvmField
@JvmField
public static final BigInteger BIG_INTEGER_ONE;
static {
BIG_INTEGER_ONE = BigInteger.ONE;
}
// @JvmStatic
@JvmStatic
public static final void printVersion() {
Companion.printVersion();
}
//
public static final class Companion {
private Companion() { }
@JvmStatic
public final void printVersion() {
String var1 = "Version: 1.0.0";
System.out.println(var1);
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
KotlinClass 클래스의 정적 필드로, Companion이라는 이름을 갖는 Companion 타입의 인스턴스를 참조
companion object로 선언된 멤버들을 Java에서 정적 멤버처럼 접근할 수 있게 하려는 목적으로 생성된 필드이다.
이 필드를 통해 Companion 클래스의 멤버에 접근할 수 있다
new Companion((DefaultConstructorMarker)null);
Companion 클래스의 인스턴스를 생성한다.
Companion의 private 생성자를 통해 생성되며, 외부에서 직접 생성하지 못하도록 제한한다.
Companion 필드는 public static final로 선언되어 KotlinClass가 처음 로드될 때 한 번만 생성된다.
public static final class Companion { ... }
companion object는 Java에서 Companion이라는 중첩 클래스로 변환되며, 이 클래스 안에 Kotlin의 companion object 멤버들이 포함된다.
생성자는 private로 선언되어 있으며, 외부에서 인스턴스 생성을 하지 못하게 막는다.
kotlin 1.9.0에 출시된 데이터 홀더 객체이다.
sealed class, sealed interface 에서 유용하게 사용할 수 있는 타입이다.
데이터와 관련된 정보를 하나의 싱글톤 객체로 관리할 때 유용하다.
Singleton 이기 때문에 내부 상태가 동적으로 변하는 객체보다는 고정된 값이나 상태를 표현하는데 적합하다.object + data class
Singleton, Immutable , data class 처럼 equals, hashCode, toString 메서드가 자동으로 제공된다.