ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6-2. 특별한 클래스 사용하기 : 데이터 클래스(data class)
    Study(종료)/Kotlin 22.09.13 ~ 12.18 2022. 10. 22. 22:23

    6장

    6-1. 특별한 클래스 사용하기 : 이넘(enun) 클래스

    6-2. 특별한 클래스 사용하기 : 데이터 클래스 클래스

    6-3. 특별한 클래스 사용하기 : 인라인 클래스(값 클래스)


    6.2 데이터 클래스(data class)

    1) 데이터 클래스와 데이터 클래스에 대한 연산

    참조 타입의 값은 기본적으로 참조가 가리키는 객체의 정체성(identify)이 같으면 동일하다.

    이 말은 두 객체가 같은 객체(메모리에서 같은 위치에 있는)라면  같다는 말이다.

    이 경우 인스턴스의 필드 값(John, Deo, 25)는 고려 대상이 아니다.

    class Person(val firstName: String, val lastName: String, val age: Int)
    
    fun main(){
        val person1 = Person("John", "Doe", 25)
        val person2 = Person("John", "Doe", 25)
        val person3 = person1
    
        println(person1 == person2) // false
        println(person1 == person3) // true
    }

    클래스에 커스텀 동등성 비교가 필요하다면, equals () 메서드와 이와 연관된 hashCode() 메서드를 구현하는게 일반적이지만

    데이터 클래스에 대해서는 코틀린이 클래스의 프로퍼티 목록을 기반으로 몇가지 메서드를 자동으로 생성한다.

    data class Person(val firstName: String, val lastName: String, val age: Int)
    
    fun main(){
        val person1 = Person("John", "Doe", 25)
        val person2 = Person("John", "Doe", 25)
        val person3 = person1
    
        println(person1 == person2) // true
        println(person1 == person3) // true
    }

    data class로 정의하면 컴파일러가 주생성자에 정의된 프로퍼티 값을 비교하는

    동등성 비교 연산을 자동으로 생성해주므로 두 결과 모두 true를 반환한다.

     

    프로퍼티의 값의 비교는 equals() 메서드를 이용한다.

    따라서 깊은 동등성(deep equality) 비교 여부는 프로퍼티의 타입에 따라 달라진다.

    data class Person(val firstName: String, val lastName: String, val age: Int)
    
    data class Mailbox(val address: String , val person: Person)
    
    fun main(){
        val box1 = Mailbox("여의도", Person("John", "Doe", 25))
        val box2 = Mailbox("여의도", Person("John", "Doe", 25))
    
        println(box1 == box2) // true
    }

    위 코드에서는 Person이 data class이므로 MailBox의 비교는 address 프로퍼티와 Person의 동등성 비교(box1의 Person의 프로퍼티와 box2의 프로퍼티를 비교)에 따라 결과가 결정된다.그래서 위 코드의 Person을 data class가 아닌 그냥 class로 비교하면 결과가 바뀌게 된다.

    class Person(val firstName: String, val lastName: String, val age: Int)
    
    data class Mailbox(val address: String , val person: Person)
    
    fun main(){
        val box1 = Mailbox("여의도", Person("John", "Doe", 25))
        val box2 = Mailbox("여의도", Person("John", "Doe", 25))
    
        println(box1 == box2) // false, 두 person이 다름
    }

    데이터 클래스는 toString() 메서드 구현도 생성해준다. 이 메서드는 클래스 인스턴스를 문자열로 변환해준다.

    data class Person(val firstName: String, val lastName: String, val age: Int)
    
    fun main(){
        val person = Person("John", "Doe", 25)
        println(person) // Person(firstName=John, lastName=Doe, age=25)
    }

    주생성자의 파라미터에서 선언한 프로퍼티만 equals() / hashCode() / toString() 메서드 구현에 사용된다.

    다른 프로퍼티들은 이 함수들의 생성에 영향을 미치지 못한다.

    data class Person(val firstName: String, val lastName: String){
        var age = 0
    }
    
    fun main(){
        val person1 = Person("John", "Doe").apply { age = 24 }
        val person2 = Person("John", "Doe").apply { age = 20 }
        println(person1) // Person(firstName=John, lastName=Doe)
        println(person2) // Person(firstName=John, lastName=Doe)
        println(person1 == person2)
    }

    데이터 클래스의 copy() 함수는 인스턴스를 복사할 수 있으며, 복사하면서 몇몇 프로퍼티를 변경할 수 있다.인스턴스를 쉽게 복사할 수 있는 기능은 불변 데이터 구조를 더 쉽게 사용하도록 해준다.

    var을 이용해 프로퍼티를 사용하기보다, val 을 이용하면 코드에 대한 추론이 쉬워지고 실수도 줄일 수 있다.

    data class Person(val firstName: String, val lastName: String, val age: Int)
    
    fun Person.show() = println("$firstName $lastName: $age")
    
    fun main(){
        val person = Person("John", "Doe", 25)
        person.show() // John Doe: 25
        person.copy().show() // John Doe: 25
        person.copy(lastName = "Smith").show() // John Smith: 25
        person.copy(firstName = "Jane", age = 30).show() // Jane Doe: 30
    }

    2) 구조 분해 선언(Destructuring Declaration)

    구조 분해 선언은 변수 이름을 하나만 사용하는 대신 괄호로 감싼 식별자 목록으로

    여러 변수를 한꺼번에 정의할수 있게 해주는 일반화된 지역 변수 구문이다.

    각 이름은 별도의 변수 선언과 같고 = 다음에 적은 데이터 클래스 인스턴스의 프로퍼티에 해당한다.

    import kotlin.random.Random
    
    data class Person(val firstName: String, val lastName: String, val age: Int)
    
    fun newPerson() = Person(readLine()!!, readLine()!!, Random.nextInt(100))
    
    fun main(){
        val person = newPerson()
        val firstName = person.firstName
        val lastName = person.lastName
        val age = person.age
        
        if(age < 18) println("$firstName $lastName is under-age")
    }

    위 코드를 구조 분해 선언을 통해 아래와 같이 변경할 수 있다.

    fun main(){
        val person = newPerson()
        val (firstName, lastName, age) = person
    //    val firstName = person.firstName
    //    val lastName = person.lastName
    //    val age = person.age
        ...
    }

    여기서 각 변수에 어떤 프로퍼티가 매핑되는지는 선언하는 변수의 이름에 의해 결정되는 것이 아닌

    데이터 클래스의 생성자에 있는 각 프로퍼티의 위치에 따라 결정된다.

    val (firstName, lastName, age) = Person("John", "Doe", 25)
    println("$firstName $lastName") // John Doe
    val (lastName, firstName, age) = Person("John", "Doe", 25)
    println("$firstName $lastName") // Doe John

    구조 분해 선언 전체는 타입이 없지만, 필요하면 각 컴포넌트 변수에 타입을 표기할 수 있다.

    val (firstName: String, lastName, age) = Person("John", "Doe", 25)

    구조 분해 선언에 데이터 클래스의 프로퍼티 수보다 적은 수의 변수가 들어갈 수 있다.

    이경우 생성자 뒷부분에 선언된 프로퍼티는 추출되지 않는다.

    val (firstName: String, lastName) = Person("John", "Doe", 25)
    println("$firstName $lastName") // John Doe
    val (name) = Person("John", "Doe", 25)
    println(name) // John

    중간 프로퍼티를 생략하고 싶을 경우 _ 를 사용해 선언하면 된다.

    val (_, name) = Person("John", "Doe", 25)
    println(name) // Deo

    다음글

    6-3. 특별한 클래스 사용하기 : 인라인 클래스(값 클래스)

    반응형

    댓글

Designed by Tistory.