개발/Kotlin

[Kotlin] - 코틀린 기초 (특별 클래스)

dongdev 2022. 8. 20. 02:00

코틀린 공식문서에 있는 Example들을 하나씩 읽어보며 정리하자.

참고: https://play.kotlinlang.org/byExample/overview

 

 

Special Classes

Data Classes

Data Class를 사용하면 값을 저장하는 클래스(Dto, Vo)를 쉽게 만들 수 있다.

Data Class는 자동으로 메서드를 구현해 제공하고 override 가능하다.

  • equals() / hashCode()
  • toString()
  • copy()
  • componentN() 
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()}")
}

/*
User(name=Alex, id=1)
user == secondUser: true
user == thirdUser: false
63347075
63347075
2390846
User(name=Alex, id=1)
false
User(name=Max, id=1)
User(name=Alex, id=3)
name = Alex
id = 1
*/
  1. data 키워드로 data class 정의한다.
  2. equals() 메서드 override
  3. toString 메서드 자동 생성되어 출력된다.
  4. override 된 equals() 메서드를 통해 비교 (data calss 에서 == 사용 시, 자동 생성된 equals() 메서드로 값비교를 한다.)
  5. 정확히 일치하는 속성을 가진 데이터 클래스 인스턴스는 동일한 hashCode를 갖는다. (user.hashCode() = secondUser.hashCode())
  6. 자동 생성된 copy() 메서드로 새로운 인스턴스를 쉽게 만들 수 있다.
  7. copy는 새 인스턴스를 생성하므로 ===(주소값 비교) 시 false를 반환한다.
  8. 복사할 때 특정 속성값을 변경할 수 있다. copy는 클래스 생성자와 같은 순서로 매개변수를 받는다.
  9. 지정 매개변수와 함께 copy 사용 시, 생성자 매개변수 순서를 무시할 수 있다.
  10. 자동 생성된 componentN 함수를 사용하면 선언 순서대로 값을 가져올 수 있다.

Enum Classes

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)
}

/*
It's running
*/
  1. 3개의 열거형 상수가 있는 간단한 enum 클래스를 정의한다. 열거형 상수의 수는 항상 유한해야하고 모두가 구분되어야 한다.
  2. 클래스 이름으로 열거형 상수에 접근한다.
  3. 열거형을 사용하면 컴파일러가 when-expression이 완전한지 추론할 수 있으므로 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
}

/*
RED
true
false
true
*/
  1. 프로퍼티와 메서드를 갖는 열거형 클래스를 정의한다.
  2. 각 열거형 상수는 생성자의 매개변수에 대한 인수를 전달해야한다.
  3. 열거형 클래스의 멤버는 세미콜론으로 상수 정의와 구분된다.
  4. 기본 toString은 상수 이름(RED)을 반환한다.
  5. 열거형 상수에 대한 메서드를 호출한다.
  6. 열거형 클래스 이름을 통해 메서드를 호출한다.
  7. RED 와 YELLOW의 RGB 값은 첫번째 비트(FF)를 공유하므로 true가 출력된다.

Sealed Classes

Sealed classes를 사용하면 상속 사용을 제한할 수 있다.

sealed classes가 선언된 패키지 내부에서만 서브클래스를 만들 수 있다.

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")))
}

/*
Hello Snowy
*/
  1. sealed calss를 정의한다.
  2. 서브클래스를 정의한다. 모든 서브클래스는 동일한 패키지에 있어야한다.
  3. when expression의 인자로 sealed class의 인스턴스를 사용한다.
  4. 스마트캐스트가 이뤄져 Mammal 이 Human 으로 캐스팅된다.
  5. 스마트 캐스트가 이뤄져 Mammal 이 Cat 으로 캐스팅된다.
  6. else 케이스는 필요없다. sealed class의 가능한 모든 서브클래스가 when 케이스에 포함되어 있기때문이다. non-sealed 슈퍼클래스를 쓴다면 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()
}
 /*
 17
 80
 */
  1. 클래스를 정의한다.
  2. 메서드를 정의한다.
  3. 인스턴스를 생성한다.
  4. 인스턴스의 메서드를 호출한다.

코틀린에는 object 키워드도 있다.

이 키워드는 단일 구현으로 데이터 타입을 얻는데 사용된다.

자바의 싱글톤이라고 생각하면 된다. (싱글톤은 두 개의 스레드가 동시에 클래스의 인스턴스를 생성하려고 하더라도 오직 한 개의 인스턴스만 생성되는 것을 보장하는 것을 말함)

 

코틀린에서 이를 달성하기 위해서는 간단하게 object 키워드로 선언하면 된다.

object 키워드로 생성한 인스턴스에는 클래스도 없고 생성자도 없다. 오직 게으른 인스턴스(lazy instance)이다.

왜 게으르다(lazy) 할까? 왜나면 객체에 접근할 때 한번 생성되기 때문이다. 접근하기 전까지는 생성되지 않는다.

 

Object Expression

클래스 선언에서 사용할 필요가 없다. 단일 객체를 생성하고 멤버를 선언한 후 하나의 함수 내에서 접근한다.

이와 같은 객체는 자바에서 종종 익명 클래스의 인스턴스로 생성된다.

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
}

/*
Total price: $500
*/
  1. 파라미터를 갖는 함수를 생성한다.
  2. 결과값을 생성할 때 사용할 객체를 생성한다.
  3. 객체의 프로퍼티에 접근한다.
  4. 결과를 출력한다.
  5. 함수를 호출한다. 이때 실제로 객체가 만들어진다.

Object Declaration

object 선언으로도 사용할 수 있다. 이것은 expression 이 아니며 변수 할당에 사용할 수 없다.

object 선언된 객체에 직접 접근해서 사용해야한다.

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

fun main(){
    DoAuth.takeParams("foo", "qwerty")                          //3
}

/*
input Auth parameters = foo:qwerty
*/
  1. objec 선언
  2. object 메서드를 정의한다
  3. 메서드를 호출한다. 이때 실제로 객체가 생성된다.

Companion Objects

클래스 안에 있는 object 선언은 또 다른 유용한 사례인 companion object를 정의한다.

문법적으로 자바의 static 메소드와 유사하다. 클래스 이름을 한정자(qualifier)로 사용하여 객체 멤버를 호출한다.

만약 코틀린에서 companion object를 사용할 계획이라면, pakage-level 함수 대신에 사용할 것을 고려해봐야한다.

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
}

/*
BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG 
*/
  1. 클래스를 정의한다.
  2. companion object 정의, 이름은 생략 가능
  3. companion object 메소드 정의
  4. 클래스 이름을 통한 companion object 메소드 호출