Kotlin - data, object, enum, Sealed, Inner class

하동혁 ·2023년 4월 5일
0

Kotlin

목록 보기
8/10
post-thumbnail

▪️ data class

  • data class 키워드로 선언해서 사용
  • 변수나 상수를 선언할 수 있다. (구분은 쉼표( , ))
  • 프로퍼티를 일반 클래스와 달리 초기화 해주지 않아도 됨
  • 생성과 동시에 클래스 내의 프로퍼티를 기준으로 생성자가 만들어진다.
  • DTO(Data Transfer Object)를 다룰 때 쓰면 유용하다.

data class User(val name: String, val age: Int)

fun main() {
    val user1 = User("John", 30)
    val user2 = User("John", 30)
    val user3 = User("Sarah", 25)

    println(user1) // User(name=John, age=30)
    println(user1 == user2) // true
    println(user1 == user3) // false
    println(user1.hashCode()) // 1524964254
    println(user2.hashCode()) // 1524964254
    println(user3.hashCode()) // 135747841
}
  • User 클래스는 name과 age 두 개의 프로퍼티를 가지고 있습니다.
  • data class에서는 주 생성자에 선언된 프로퍼티들을 기반으로 toString(), equals(), hashCode() 등의 메서드가 자동으로 생성됩니다.
    • toString() 메서드는 객체의 문자열 표현을 반환합니다.
    • equals() 메서드는 객체의 동등성 비교를 수행합니다.
    • hashCode() 메서드는 객체의 해시 코드를 반환합니다.

다음 예제는 data class를 명시적으로 프로퍼티를 선언하고, 초기화하는 방법입니다.

data class Person(var name: String = "", var age: Int = 0)


▪️ object class

  • Singleton 패턴을 구현하는데 사용된다.
  • 코드 블록을 재사용하거나, 코드 블록을 상수와 함께 저장하고 싶을 때 사용된다.
  • object 클래스는 클래스를 정의하면서 동시에 객체를 생성합니다.
    이 객체는 전역 객체(global object)이며, 프로그램 실행 도중 한 번만 생성됩니다.
    (프로그램이 실행되는 동안 저장된 데이터는 손실되지 않고, 프로그램이 종료되면 소멸)
  • object 클래스는 다른 클래스와 마찬가지로 생성자, 프로퍼티, 메서드를 가질 수 있다.
    하지만 object 클래스는 인스턴스(객체)를 생성할 수 없으므로, 생성자는 매개변수를 가질 수 없다.
  • 어느 클래스, 함수에서든 별도의 객체화 과정 없이 접근 가능
  • 위 특징 덕분에 안드로이드에서는 엑티비티, 프래그먼트 구분하지 않고 데이터 전달을 할 수 있다.

object Logger {
    fun log(message: String) {
        println("LOG: $message")
    }
}

fun main() {
    Logger.log("Hello, world!") // LOG: Hello, world!
}

위 코드에서 object 클래스를 사용하여 Logger 객체를 만들었습니다.
Logger 객체는 전역 객체이므로, 객체 생성 없이 Logger.log("Hello, world!")와 같이 객체에 접근할 수 있습니다.
log() 메서드는 객체의 메서드로, 인자로 받은 메시지를 출력합니다.



▪️ enum class

  • java와 동일한 열거형을 정의하는 데 사용된다.
  • [클래스 이름.name] 이름 값을 가져올 수 있음
  • [클래스 이름.ordinal]로 해당 값이 몇 번째에 기록되었는지 순서 값을 가져올 수 있다.
  • 인자가 있는 열거형 클래스인 경우 각 열거 값들을 표현하는 방식을 다채롭게 할 수 있다.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

위 예시에서는 Color라는 enum class를 정의하고, RED, GREEN, BLUE 세 개의 상수 값을 가지도록 하였습니다.
이제, 이 enum class를 사용하여 다음과 같이 색상을 출력할 수 있습니다.

fun main() {
    val red = Color.RED
    println("red: ${red.rgb}") // red: 16711680

}

위 예시에서 val red = Color.RED는 RED 상수 값을 변수 red에 할당하는 것을 의미합니다.
이후 red.rgb는 red 변수의 rgb 프로퍼티 값을 출력하는 것입니다.



enum class Color {
    RED,
    GREEN,
    BLUE
}

fun main() {
    val red = Color.RED
    val green = Color.GREEN
    val blue = Color.BLUE

    println(red.ordinal) // 0
    println(green.ordinal) // 1
    println(blue.ordinal) // 2

    println(red.name) // RED
    println(green.name) // GREEN
    println(blue.name) // BLUE
}

위 예시에서 Color enum class는 RED, GREEN, BLUE 세 개의 상수 값을 가집니다.

red.ordinal, green.ordinal, blue.ordinal은 각 상수 값의 순서를 나타내는데, RED는 0번째, GREEN은 1번째, BLUE는 2번째에 위치하므로 각각 0, 1, 2가 출력됩니다.

red.name, green.name, blue.name은 각 상수 값의 이름을 나타내는데, RED, GREEN, BLUE의 이름을 가지므로 각각 "RED", "GREEN", "BLUE"가 출력됩니다.



▪️ sealed class

sealed class는 코틀린에서 추상화된 클래스 타입 중 하나로, 특정 클래스 타입을 상속받은 하위 클래스들을 제한하는 용도로 사용됩니다. 즉, sealed class는 해당 클래스를 상속하는 하위 클래스들을 제한하고, 제한된 하위 클래스들 중 하나를 나타내는 값을 갖는 클래스 타입을 선언하는 데 사용됩니다.

sealed class는 다음과 같은 특징을 가집니다.

  • sealed class는 abstract 클래스입니다. 따라서 sealed class 타입의 인스턴스를 직접 생성할 수는 없습니다.

  • sealed class는 하위 클래스를 선언할 때, 해당 클래스를 선언한 파일 내에서만 선언할 수 있습니다.
    즉, sealed class의 하위 클래스는 그 클래스를 선언한 파일 내에서만 선언할 수 있습니다.

  • sealed class의 하위 클래스는 data class, enum class, interface, class 등 어떤 클래스 타입이던 상관 없습니다.

  • sealed class는 when 표현식과 함께 사용할 때 유용합니다. when 표현식에서 sealed class의 모든 하위 클래스에 대한 분기를 처리하면, else 분기를 사용하지 않아도 컴파일러가 모든 가능한 경우를 체크하도록 보장합니다.

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: Throwable) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

fun handleResult(result: Result<Int>) {
    when (result) {
        is Result.Success -> {
            val data = result.data
            // handle success case
        }
        is Result.Failure -> {
            val error = result.error
            // handle failure case
        }
        Result.Loading -> {
            // handle loading case
        }
    }
}

위 코드에서 ConnectionState는 sealed class로 정의되어 있습니다.
Connected, Connecting, Error 세 개의 하위 클래스를 가지고 있습니다.
Connected와 Connecting 하위 클래스는 object로 정의되어 있으며, Error 하위 클래스는 data class로 정의되어 있습니다.

handleConnectionState 함수에서 when 표현식을 사용하여 ConnectionState 클래스의 모든 하위 클래스에 대한 분기를 처리하고 있습니다.
이렇게 하면, ConnectionState 클래스의 모든 하위 클래스에 대한 처리를 안전하게 보장할 수 있습니다.



▪️ Inner class

Kotlin에서 내부 클래스(Inner class)는 다른 클래스 내부에 정의된 클래스입니다.
Java와 마찬가지로, 내부 클래스는 바깥 클래스의 멤버 변수와 메서드에 접근할 수 있습니다.
하지만, Kotlin에서는 Java와 달리 내부 클래스는 기본적으로 정적(static) 클래스로 생성되지 않습니다.
즉, 기본적으로 바깥 클래스의 인스턴스와 내부 클래스의 인스턴스는 서로 다릅니다.

다음은 Kotlin에서 내부 클래스를 정의하는 예제입니다.

class Outer {
    private val outerValue: Int = 10
    
    inner class Inner {
        fun printOuterValue() {
            println("outerValue: $outerValue")
        }
    }
}

위 예제에서 Outer 클래스 내부에 Inner 내부 클래스를 정의하고 있습니다.
Inner 클래스에서 Outer 클래스의 멤버 변수 outerValue에 접근하고 있습니다.

내부 클래스를 사용하기 위해서는, 내부 클래스의 인스턴스를 생성하기 위해 외부 클래스의 인스턴스를 먼저 생성해야 합니다.
예를 들어, 다음과 같이 사용할 수 있습니다.

val outer = Outer()
val inner = outer.Inner()
inner.printOuterValue()

위 예제에서는 먼저 Outer 클래스의 인스턴스를 생성하고, 그 다음에 Inner 클래스의 인스턴스를 생성하고 있습니다.
Inner 클래스의 인스턴스를 생성하기 위해서는 Outer 클래스의 인스턴스를 사용해야 하기 때문입니다.

내부 클래스는 바깥 클래스와 강한 결합을 갖고 있기 때문에, 내부 클래스를 사용하는 경우에는 메모리 누수 등의 문제가 발생할 수 있습니다.
따라서, 내부 클래스를 사용하기 전에 반드시 잘 고려해야 합니다.


📌중첩 클래스는 자신이 속한 외부 클래스의 인스턴스에 접근할 수 없습니다.
다음은 중첩 클래스와 내부 클래스를 사용하여 외부 클래스의 필드에 접근하는 예제입니다.

class Outer {
    private val outerValue: Int = 10
    
    class Nested {
        fun printOuterValue() {
            // 중첩 클래스에서는 외부 클래스의 필드에 접근할 수 없습니다.
            //println("outerValue: $outerValue") // 컴파일 오류 발생
        }
    }
    
    inner class Inner {
        fun printOuterValue() {
            println("outerValue: $outerValue")
        }
    }
}

fun main() {
    val outer = Outer()
    
    // 중첩 클래스에서는 외부 클래스의 인스턴스에 접근할 수 없습니다.
    val nested = Outer.Nested()
    //nested.printOuterValue() // 컴파일 오류 발생
    
    // 내부 클래스에서는 외부 클래스의 인스턴스에 접근할 수 있습니다.
    val inner = outer.Inner()
    inner.printOuterValue() // 출력: outerValue: 10
}

0개의 댓글