본문 바로가기

Language/Kotlin

코틀린[Kotlin] 특별한 클래스들 (Data, Enum, Sealed Classes And Object)

 

이번엔 코틀린에서 지원하는 특별한 클래스들에 대해서 다뤄볼 것입니다.

코틀린 공식문서를 참고해 작성했습니다.

 
 

Data Classes

데이터 클래스를 사용하면 값을 저장하는 데 사용되는 클래스를 쉽게 만들 수 있습니다. 이러한 클래스에는 복사하기(copy), 문자열 표현 가져오기(toString) 및 컬렉션의 인스턴스 사용을 위한 메서드가 자동으로 제공됩니다.

data class User(val name: String, val id: Int) {           // 1
    override fun equals(other: Any?) =
        other is User && other.id == this.id               // 2
}
fun main() {
    val user = User("KimSo", 1)
    // User(name=KimSo, id=1)
    println(user)                                          // 3

    val secondUser = User("KimSo", 1)
    val thirdUser = User("AnSeung", 2)

    // true
    println("user == secondUser: ${user == secondUser}")   // 4
    // false
    println("user == thirdUser: ${user == thirdUser}")

    // hashCode() function
    // 63347075
    println(user.hashCode())                               // 5
    // 63347075
    println(secondUser.hashCode())
    // 2390846
    println(thirdUser.hashCode())

    // copy() function
    println(user.copy())                                   // 6
    println(user === user.copy())                          // 7
    println(user.copy("Hyeo"))                              // 8
    println(user.copy(id = 3))                             // 9

    // KimSo
    println("name = ${user.component1()}")                 // 10
    // 1
    println("id = ${user.component2()}")
}
  1. data 한정자로 데이터 클래스를 정의합니다.
  2. 동일한 id를 가진 사용자를 동일한 사용자로 선언하여 기본 equals 메서드를 재정의합니다.
  3. 메소드 toString이 자동 생성되어 println 출력할 때 객체의 프로퍼티를 보여줍니다. 
  4. 커스텀 equals()는 ID가 동일한 경우 두 인스턴스를 동일한 것으로 간주합니다.
  5. 정확히 일치하는 속성을 가진 데이터 클래스 인스턴스는 동일한 hashCode를 갖습니다.
  6. 자동 생성된 copy() 함수는 새 인스턴스를 쉽게 생성할 수 있습니다.
  7. copy는 새 인스턴스를 생성하므로 객체와 복사된 객체는 메모리상에서 별개의 참조값을 가집니다.
  8. copy할 때 특정 속성의 값을 변경할 수 있습니다. copy는 클래스 생성자와 같은 순서로 인수를 받습니다.
  9. 프로퍼티 순서에도 불구하고 값을 변경하려면 named arguments와 함께 copy를 사용하십시오.
  10. 자동 생성된 componentN 함수를 사용하면 선언 순서대로 프로퍼티 값을 가져올 수 있습니다.

Enum Classes

enum, enumeration은 사전적 정의로 열겨, 목록이란 뜻이다.

enum 클래스는 방향, 상태, 모드 등과 같은 고유한 값의 유한 집합을 나타내는 유형을 모델링하는 데 사용됩니다.

enum class State {
    IDLE, RUNNING, FINISHED                           // 1
}

fun main() {
    val state = State.RUNNING                         // 2
    val message = when (state) {                      // 3
        State.IDLE -> "It's idle"
        State.RUNNING -> "It's running"
        State.FINISHED -> "It's finished"
    }
    println(message)
}
  1. 세 개의 열거형 상수가 있는 간단한 Enum 클래스를 정의합니다. 상수의 수는 항상 유한하며 모두 고유합니다.
  2. 클래스 이름을 통해 Enum 상수에 액세스합니다.
  3. Enum을 사용하면 컴파일러가 when-expression이 완전한지 추론할 수 있으므로 else-case가 필요하지 않습니다.

열거형에는 Enum 상수 목록과 세미콜론으로 구분된 다른 클래스와 같은 속성 및 메서드가 포함될 수 있습니다.

enum class Color(val rgb: Int) {                      // 1
    RED(0xFF0000),                                    // 2
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00);

    fun containsRed() = (this.rgb and 0xFF0000 != 0)  // 3
}

fun main() {
    val red = Color.RED
    println(red)                                      // 4
    println(red.containsRed())                        // 5
    println(Color.BLUE.containsRed())                 // 6
    println(Color.YELLOW.containsRed())               // 7
}
  1. 속성과 메서드를 사용하여 Enum 클래스를 정의합니다.
  2. 각 Enum 상수는 생성자 매개변수에 대한 인자를 전달해야 합니다.
  3. Enum 클래스 멤버는 세미콜론으로 상수 정의와 구분됩니다.
  4. 기본 toString은 상수 이름(여기서는 "RED")을 반환합니다.
  5. Enum 상수에 대한 메서드를 호출합니다
  6. Enum 클래스 이름을 통해 메서드를 호출합니다.
  7. RED 및 YELLOW의 RGB 값은 첫 번째 비트(FF)를 공유하므로 'true'가 출력됩니다.

Sealed Classes

Sealed은 사전적 정의로 봉인된, 밀폐된이란 뜻으로 Sealed 클래스를 사용하면 상속 사용을 제한할 수 있습니다. Sealed 클래스를 선언하면 Sealed 클래스가 선언된 동일한 패키지 내부에서만 상속을 받을 수 있습니다. Sealed 클래스가 선언된 패키지 외부에서 상속할 수 없습니다.

sealed class Mammal(val name: String)                                                   // 1

class Cat(val catName: String) : Mammal(catName)                                        // 2
class Human(val humanName: String, val job: String) : Mammal(humanName)

fun greetMammal(mammal: Mammal): String {
    when (mammal) {                                                                     // 3
        is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"    // 4
        is Cat -> return "Hello ${mammal.name}"                                         // 5     
    }                                                                                   // 6
}

fun main() {
    println(greetMammal(Cat("Snowy")))
}
    1. Sealed 클래스를 정의합니다.
    2. 하위 클래스를 정의합니다. 모든 하위 클래스는 동일한 패키지에 있어야 합니다.
    3. Sealed 클래스의 인스턴스를 when 식의 인수로 사용합니다.
    4. 스마트캐스트가 수행되어 Mammal를 Human으로 캐스팅합니다.
    5. 스마트캐스트가 수행되어 Mammal을 Cat으로 캐스팅합니다.
    6. Sealed 클래스의 가능한 모든 하위 클래스가 포함되므로 else-case는 여기에서 필요하지 않습니다. 봉인되지 않은 수퍼 클래스의 경우 else가 필요합니다.

Object Keyword

Kotlin의 클래스와 객체는 대부분의 객체 지향 언어와 동일한 방식으로 작동합니다. 클래스는 청사진이고 객체는 클래스의 인스턴스입니다.

일반적으로 클래스를 정의한 다음 해당 클래스의 여러 인스턴스를 만듭니다.

import java.util.Random

class LuckDispatcher {                    //1 
    fun getNumber() {                     //2 
        var objRandom = Random()
        println(objRandom.nextInt(90))
    }
}

fun main() {
    val d1 = LuckDispatcher()             //3
    val d2 = LuckDispatcher()
    
    d1.getNumber()                        //4 
    d2.getNumber()
}
  1. 청사진을 정의합니다. 
  2. 메서드를 정의합니다. 
  3. 인스턴스를 생성합니다. 
  4. 인스턴스에서 메서드를 호출합니다.

Kotlin에는 object 키워드도 있습니다. 단일 구현으로 데이터 타입을 얻는 데 사용됩니다.

Java 사용자이고 "단일"이 무엇을 의미하는지 이해하려면 Singleton 패턴을 생각할 수 있습니다. 2개의 스레드가 생성을 시도하더라도 해당 클래스의 인스턴스 하나만 생성되도록 합니다.

Kotlin에서 이를 달성하려면 클래스 없이 생성자 없이 지연 인스턴스만 있는 객체만 선언하면 됩니다. 지연 인스턴스인인 이유는 객체에 접근할 때 한 번 생성되기 때문입니다. 그렇지 않으면 생성되지도 않습니다.

 

object 표현식

다음은 객체 표현식의 기본적인 일반적인 사용법입니다. 간단한 객체/속성 구조입니다. 클래스 선언에서는 그렇게 할 필요가 없습니다. 단일 개체를 만들고 해당 멤버를 선언하고 하나의 함수 내에서 접근합니다. 이와 같은 객체는 종종 Java에서 익명 클래스 인스턴스로 생성됩니다.

fun rentPrice(standardDays: Int, festivityDays: Int, specialDays: Int): Unit {  //1

    val dayRates = object {                                                     //2
        var standard: Int = 30 * standardDays
        var festivity: Int = 50 * festivityDays
        var special: Int = 100 * specialDays
    }

    val total = dayRates.standard + dayRates.festivity + dayRates.special       //3

    print("Total price: $$total")                                               //4

}

fun main() {
    rentPrice(10, 2, 1)                                                         //5
}
  1. 매개변수가 있는 함수를 만듭니다.
  2. 결과 값을 계산할 때 사용할 개체를 만듭니다.
  3. 개체의 속성에 액세스합니다.
  4. 결과를 출력합니다.
  5. 함수를 호출합니다. 객체가 실제로 생성됩니다.

object 선언

객체 선언을 사용할 수도 있습니다. 표현식이 아니며 변수 할당에 사용할 수 없습니다. 멤버에 직접 액세스하려면 이를 사용해야 합니다.

object DoAuth {                                                 //1 
    fun takeParams(username: String, password: String) {        //2 
        println("input Auth parameters = $username:$password")
    }
}

fun main(){
    DoAuth.takeParams("foo", "qwerty")                          //3
}
  1. object 선언을 만듭니다.
  2. object 메서드를 정의합니다.
  3. 메서드를 호출합니다. 객체가 실제로 생성됩니다.

Companion Objects

클래스 내부의 객체 선언은 또 다른 유용한 경우인 컴패니언 객체를 정의합니다. 구문적으로 Java의 정적 메서드와 유사합니다. 클래스 이름을 한정자로 사용하여 객체 멤버를 호출합니다.

class BigBen {                                  //1 
    companion object Bonger {                   //2
        fun getBongs(nTimes: Int) {             //3
            for (i in 1 .. nTimes) {
                print("BONG ")
            }
        }
    }
}

fun main() {
    BigBen.getBongs(12)                         //4
}
  1. 클래스를 정의합니다.
  2. 컴패니언을 정의합니다. 이름은 생략할 수 있습니다.
  3. 컴패니언 개체 메서드를 정의합니다.
  4. 클래스 이름을 통해 컴패니언 객체 메서드를 호출합니다.