Kotlin Examples > Special Classes

HH·2022년 3월 11일
0

Kotlin Examples

목록 보기
4/8

2022-03-08 작성완료

Data Classes


Data Classes는 값을 저장하는 클래스를 생성하기 쉽게 만든다. 이런 클래스들은 자동으로 복사, 문자열 표현 획득, collections에서의 인스턴스 사용을 위한 메서드를 제공한다. 이 메서드들을 클래스 선언 내에서 구현해 오버라이딩 할 수 있다.

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("Alex", 1)
    println(user)                                          // 3

    val secondUser = User("Alex", 1)
    val thirdUser = User("Max", 2)

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

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

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

    println("name = ${user.component1()}")                 // 10
    println("id = ${user.component2()}")
}
  1. data 수정자로 data class를 정의한다.
  2. 유저들이 같은 id를 가질 경우 동일하다고 선언함으로써 equals 메서드를 오버라이딩한다.
  3. toString 메서드는 자동으로 생성되고, 이것이 println 아웃풋을 보기 좋게 한다.
  4. 커스텀 equals는 아이디가 같다면 두 인스턴스가 동일하다고 취급한다.
  5. 정확히 일치하는 속성들을 가진 데이터 클래스 인스턴스는 동일한 hashCode를 가진다.
  6. 자동 생성된 copy 함수는 새 인스턴스를 만들기 쉽게 한다.
  7. copy는 새 인스턴스를 생성한다. 그러므로 객체와 그 카피는 별개의 참조를 가진다.
  8. 카피 시 특정 프로퍼티를 변경할 수 있다. copy는 클래스 생성자와 같은 순서로 아규먼트를 받아들인다.
  9. copy를 명명된 아규먼트와 함께 사용해 프로퍼티 순서와 상관 없이 값을 바꿀 수 있다.
  10. 자동 생성된 componentN 함수는 선언 순서대로 값을 얻을 수 있게 해 준다.

Enum Classes


Enum classes는 고유 값들의 유한 집합을 표현하는 타입을 모델링하는데에 사용된다(예: 방향, 상태, 모드).

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. 세 열거형 상수로 간단한 열거형 클래스를 정의한다. 상수의 수는 언제나 유한하고 모든 값은 고유하다(distinct).
  2. 클래스 이름을 통해 열거형 상수에 접근한다.
  3. 열거형을 사용할 때, when 표현식이 철저해서 else-case 가 필요하지 않은지를 컴파일러가 추론할 수 있다.

열거형은 다른 클래스들과 마찬가지로 프로퍼티와 메서드들을 가질 수 있다. 이들은 세미콜론을 통해 열거형 상수와 분리된다.

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. 프로퍼티와 메서드를 가진 열거형 클래스를 정의한다.
  2. 각 열거형 상수는 반드시 생성자 파라미터로 아규먼트를 넘겨주어야 한다.
  3. 열거형 클래스 멤버들은 세미콜론으로 상수 정의와 구별된다.
  4. 기본 toString이 상수의 이름을 반환한다. 여기서는 RED이다.
  5. 열거형 상수의 메서드를 호출한다.
  6. 열거형 클래스 이름을 통해 메서드를 호출한다.
  7. REDYELLOW의 RGB 값이 첫 비트들(FF)를 공유하므로 true를 출력한다.

Sealed Classes


Sealed Classes는 상속을 제한할 수 있도록 한다. 클래스를 sealed로 선언하면
sealed class가 선언된 패키지의 서브패키지에서만 서브클래싱 할 수 있다. sealed class가 선언된 패키지 외부에서는 서브클래싱 할 수 없다.

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 class를 선언한다.
  2. 하위 클래스를 선언한다. 모든 하위클래스가 같은 패키지에 위치해야 함을 주의한다.
  3. sealed class의 인스턴스를 when 표현식에서 아규먼트로 사용한다.
  4. 스마트캐스트가 수행되어 MammalHuman으로 캐스팅된다.
  5. 스마트캐스트가 수행되어 MammalCat으로 캐스팅된다.
  6. sealed class의 모든 가능한 하위 클래스가 커버되었으므로 else 케이스가 필요하지 않다. non-sealed superclass를 가지면 else가 필수적이다.

Object Keyword


코틀린의 클래스와 오브젝트는 대부분의 객체지향 언어와 동일한 방식으로 동작한다: 클래스는 청사진이고, 객체는 클래스의 인스턴스이다. 대개 클래스를 정의하고 클래스로부터 여러 인스턴스를 생성한다:

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. 인스턴스의 메서드를 호출한다.

코틀린에는 object keyword가 있다. 이것은 단일 구현으로 데이터 타입을 얻기 위해 사용한다.

당신이 자바 유저이고 "single"이 무엇을 의미하는지 이해한다면 싱글톤 패턴을 생각할 수 있을 것이다. 이는 두 스레드가 인스턴스를 생성하려고 시도해도 오직 하나의 인스턴스만이 생성된다는 것을 보장한다.

이를 코틀린에서 사용하기 위해서는 object를 선언하면 된다. 여기에는 클래스도 없고, 생성자도 없고, 오직 lazy instance만 있다. 오브젝트에 접근할 때 한 번만 생성되므로 이것은 lazy 하다. 접근하지 않으면 생성되지조차 않는다.


object 표현식


여기 object 표현식의 기본적이고 전형적인 사용법이 있다. 간단한 오브젝트 프로퍼티 스트럭처를 만드는 것이다. 이것은 클래스 선언에서 할 필요가 없다. 단일 오브젝트를 생성하고, 그 멤버들을 선언하고, 한 함수 내부에서 그것에 접근하면 된다. 이와 같은 오브젝트는 종종 자바에서 익명 함수 인스턴스로 생성된다.

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 선언 또한 사용할 수 있다. 이것은 표현식이 아니고, 변수 할당에 사용될 수 없다. 멤버에 직접 접근하기 위해 이것을 사용해야 한다.

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

fun main(){
    DoAuth.takeParams("foo", "qwerty")                          //3
}
  1. 오브젝트 선언을 생성한다.
  2. 오브젝트 메서드를 정의한다.
  3. 메서드를 호출한다. 여기서 오브젝트가 실제로 생성된다.

Companion Objects


클래스 내부에서의 오브젝트 선언은 또 다른 유용한 케이스를 정의한다: 동반자 객체이다. 문법적으로 이것은 자바의 static 메서드와 유사하다: qualifier로써 클래스 이름을 사용해 오브젝트 멤버를 호출할 수 있다. 코틀린에서 동반자 객체를 사용할 계획이 있다면 패키지 수준 함수 사용을 고려해 보자.

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. 클래스명으로 동반자 객체 메서드를 호출한다.

0개의 댓글