티스토리 뷰

언어/Kotlin

[Kotlin]데이터 클래스(data class)

꼬마우뇽이(원종운) 2022. 3. 20. 17:11

데이터 클래스(data class)

자바를 사용해본 적이 있다면, 롬복(Lombok)이라는 플러그인을 사용해본 적이 있을 것입니다. 데이터 클래스는 롬복의 @Data 애너테이션과 매우 유사한 기능을 언어단에서 지원하여줍니다.

 

사용 방법

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

 

위와 같이 name, age 필드를 가지는 Student 클래스를 데이터 클래스로 선언할 경우 어떠한 기능을 코틀린에서 제공하여주는 걸까요?

 

대표적으로 코틀린에서는 데이터 클래스로 선언할 경우 다음과 같은 5가지의 기능을 기본적으로 구현하여 제공하여 줍니다.

 

1. toString 메서드

toString 메서드는 해당 클래스에 대한 정보를 바탕으로 문자열을 구성하여 반환해주는 메서드입니다. 일반적으로 구현하지 않을 경우 "클래스명@참조 주소"의 형태의 문자열이 반환됩니다.

 

코드

class Student(val name: String, val age: Int) {
    val nickname: String = name
}

fun main() {
    println(Student("JongWoon", 14))
}

 

결과

com.jongwoon.anonymous.Student@2c9f9fb0 // @ 뒤의 값은 매번 바뀔 수 있습니다.

 

하지만 데이터 클래스를 사용하면 어떤 문자열을 기본적으로 반환해주는 걸까요? 코드는 class 키워드 앞에 data 키워드만 붙이면 됩니다.

 

결과

Student(name=JongWoon, age=14)

 

"클래스명(프로퍼티명=값, 프로퍼티명=값,...)"의 형태로 문자열을 반환해주는 것을 알 수 있습니다. 여기서 의아한 점은 nickname 프로퍼티가 출력되지 않은 점입니다.

 

주의할 점은 출력되는 프로퍼티는 클래스의 주 생성자에 포함된 프로퍼티들이라는 점입니다.

 

2. equals 메서드

자바에서는 두 객체의 동등성을 비교하기 위해서는 "==" 연산자가 아닌 equals 메서드를 오버 라이딩하여 사용하라는 건 이제는 누구나 아는 사실입니다. 하지만 일반적인 통념상 두 객체가 동일한지 비교하는 행위는 "==" 연산자로 하는 것이 자연스럽습니다.

 

하지만 자바는 그렇지 않아 많은 개발자에게 질타를 받았고, 코틀린의 경우는 "==" 연산자로 두 객체의 동등성을 비교할 경우 언어단에서 두 객체에 대해서 equals 메서드를 호출하여줍니다. 이는 매우 자연스러운 사고 과정입니다.

 

이러한 equals 메서드는 피연산자의 타입이 자신과 일치하는지, 일치하다면 일반적으로 모든 프로퍼티의 값이 동일한지 검사를 하게 됩니다. 이는 매우 많은 코드 작성을 유도합니다. 요즘은 IDE의 도움을 받아 간단하게 작성할 수 있습니다.

 

코틀린은 위와 같은 작업을 데이터 클래스에서 기본적으로 제공하여 줍니다. equals와 동일하게 주의하여야 할 점이 있습니다.

 

주의할 점은 비교하는 프로퍼티는 클래스의 주 생성자에 포함된 프로퍼티들이라는 점입니다.

 

아래의 코드에서 nickname 프로퍼티는 동등성 비교를 위한 비교 대상이 아닙니다.

 

코드

data class Student(val name: String, val age: Int) {
    val nickname: String = name
}

fun main() {
    val student1 = Student("JongWoon", 14)
    val student2 = Student("JongWoon", 14)
    val student3 = Student("JongWoon", 16)
    println(student1 == student2)
    println(student1 == student3)
}

 

결과

true
false

 

3. hashCode 메서드

JVM 언어에서는 "equals() 메서드가 true를 반환하는 두 객체는 hashCode() 메서드의 결괏값이 동일하여야 한다"는 제약이 있습니다. 왜 그런 것일까요?

 

일반적으로 해시 코드를 사용하는 자료구조에서는 대부분 원소 비교에 드는 비용을 최소화하고자 두 원소의 해시 코드를 비교한 후, 동일한 경우만 두 객체에 대하여 동등성(equals) 비교를 하게 됩니다.

 

그런데 hashCode 메서드를 구현하지 않은 경우 일반적으로 해당 객체의 참조 주소를 해시 코드로 사용하게 되는데, 그렇게 될 경우 실제로는 동등한 객체이지만 해시 코드가 달라, 해시 코드를 사용하는 자료구조에서 두 객체를 다른 객체로 취급하여 문제가 발생할 가능성이 있습니다.

 

따라서 코틀린은 데이터 클래스에서 equals 구현을 기본적으로 제공하기 때문에 따라 hashCode 메서드도 기본 구현을 제공하여 줍니다. 기본 구현 알고리즘 라빈 카프 알고리즘을 사용합니다.

 

라빈 카프 알고리즘은 문자열의 해시 코드를 구할 때 주로 사용하는 방법입니다. 각 문자열의 문자 아스키 대신 각 프로퍼티의 개별 해시 코드를 사용한다고 생각하시면 됩니다.

 

4. copy 메서드

데이터 클래스는 일반적으로 불변 클래스로 사용을 많이 하는 편입니다. 하지만 일부 프로퍼티의 값을 변경하고 싶을 경우가 있을 수 있고, 그렇다고 해당 프로퍼티를 쓰기 가능한 프로퍼티로 열어두기는 싫은 경우가 있습니다.

 

그런 경우 일반적으로 새로 해당 클래스를 생성자를 통하여 일부 프로퍼티의 값만 변경하여 생성하여 사용하게 됩니다. 하지만 이는 불필요한 코드를 많이 생성하게 되므로 코틀린에서는 copy 메서드를 통하여 위와 같은 작업을 간편하게 제공하여 줍니다.

 

코드

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

fun main() {
    val student1 = Student("JongWoon", 14)
    val student2 = student1.copy(name = "JongUn")
    println(student1)
    println(student2)
}

 

결과

Student(name=JongWoon, age=14)
Student(name=JongUn, age=14)

copy 메서드에 변경하고 싶은 일부 프로퍼티의 값만 변경한 새로운 객체를 반환하여줍니다. 이렇게 사용할 경우 특정 프로퍼티의 값을 변경하기 위하여 프로퍼티를 쓰기 가능한 프로퍼티로 열어두지 않아도 되고, 생성자를 새로 호출하여 객체를 얻는 것보다 간편하고 직관적인 코드를 작성할 수 있습니다.

 

5. componentN 메서드

코틀린은 파이썬의 튜플 언패킹 또는 자바스크립트의 구조적 분해 문법을 지원합니다. 해당 문법을 사용하기 위해서는 내부적으로 작동하는 Syntax Sugar를 위한 메서드를 선언하여야 합니다. 주 생성자에 작성하는 프로퍼티의 순서대로 componentN의 N자리에 해당합니다.

 

아래의 코드에서는 name 프로퍼티의 값을 반환하여주는 component1 메서드, age 프로퍼티의 값을 반환하여주는 component2 메서드가 내부적으로 생기게 되고, 구조적 분해 문법을 사용하게 되면 componentN 메서드를 각각 호출하여 주는 방식입니다.

 

말로 하면 어려울 테니 다음과 같이 직접 사용하는 것을 보면 조금 이해가 편합니다.

 

 

코드

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

fun main() {
    val student1 = Student("JongWoon", 14)
    val (name, age) = student1
    println("name=$name, age=$age")
}

 

결과

name=JongWoon, age=14

 

"val (name, age)"라는 부분에서 알 수 있듯이 name 변수에는 student1의 componet1 메서드의 결괏값을, age 변수에는 component2 메서드의 결괏값을 할당하여 주는 것을 알 수 있습니다.

 

실제로 진짜 호출이 되는지 궁금하시다면 바이트 코드를 보시면 됩니다. 살짝 첨부를 하자면 다음과 같습니다.

Student student1 = new Student("JongWoon", 14);
String name = student1.component1();
int age = student1.component2();
String var3 = "name=" + name + ", age=" + age;
System.out.println(var3);

 

'언어 > Kotlin' 카테고리의 다른 글

[Kotlin]인터페이스(interface)  (0) 2022.03.20
[Kotlin]예외(Exception) 처리  (1) 2022.03.20
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함