자바와 다르게, 코틀린은 다양한 Class 를 제공해준다. 그 중 하나로 Data Class 라는 것이 있는데, 이는 자바 개발자들의 고충과 니즈를 정확히 파악한 기능을 갖고 있다. 정말 감동적이지 아니할 수 없다!
이번 포스팅에선, Data Class (데이터 클래스) 가 우리에게 선사하는 감동을 느껴보자!
일반 클래스와 달리, 다양한 메소드를 자동으로 생성해주는 클래스이다. 만약 자동으로 생성되는 메소드들이 별로 필요하지 않는 녀석들이었다면, 개발자들 사이에서 쓰이지도 않았을 터이다. 하지만 매우 폭발적인 편리함을 제공하는 유용한 메소드들이 자동 생성된다.
hashCode()
copy()
equals()
toString()
componentsN()
또한 아래와 같은 여러 가지 다양한 특징을 갖고 있다.
val
또는 var
로 선언해야 함sealed
클래스는 상속받을 수 있으며, 인터페이스는 구현할 수 있음 (v1.1 이후 기준)abstract
, open
, sealed
, inner
등 키워드를 붙일 수 없음이론은 여기까지 해두고, 이제부턴 Data Class 의 특장점에 대해서 코드로 설명하며 열거해보겠다.
Java 를 활용하여 이름과 나이를 갖는 People 이라는 클래스를 사용한다고 가정해보자. 그럼 아래와 같이 클래스를 정의할 수 있다. toString()
을 오버라이딩하고, Getter / Setter 등을 정의해줘야 한다.
class People {
String name;
int age;
@Override
public String toString(){
return "[People] name : " + name + ", age : " + Integer.toString(age);
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
분명 꼭 필요한 코드는 맞는데, 간단한 클래스더라도 보일러 플레이트 코드가 너무 비대하다. 이거 해줘야 되고 저거 해줘야 되고 여간 귀찮은 일이 아니다. 그럼, 같은 동작을 하는 코틀린 코드를 살펴보자.
data class People(
val name: String,
val age: Int
)
이렇게 하면 위에서 든 자바 예제 클래스와 같은 동작을 한다!
뭐 없어보이는데, 놀랍게도 이 안에 매우 유용한 기능들이 담겨있다. 공중제비를 돌 수 밖에 없다.
그리고 아래처럼 데이터 클래스 객체 생성해서 사용하면 된다.
fun main() {
val peopleA = People("H43RO", 23)
val peopleB = People("LULU", 21)
}
이렇게 간단하게 생성했지만, 글 초입부에서 설명했듯 다양한 기능들이 존재한다! 하나씩 살펴보자.
toString()
메소드생성한 객체를 고대로 출력해보면 갖고 있는 프로퍼티의 값들이 알아서 출력되는 것을 확인할 수 있다.
toString()
이 구현되어있기 때문이다! 디버깅 시 로그캣을 통해 매우 편리하게 사용할 수 있다.
필자가 생각하기엔 이 메소드를 자동으로 생성해주는 것이 가장 베스트 장점이 아닐까 싶다.
println(peopleA)
// People(name=H43RO, age=23)
copy()
메소드copy()
메소드 역시 사용할 수 있다. 특정 필드값만 바꿔서 복사하기에 간편하다.
val olderPeopleA = peopleA.copy(age = 33)
println(olderPeopleA)
// People(name=H43RO, age=33) -> 나이가 23 에서 33 으로 변경되었음!
hashCode()
메소드프로퍼티 값이 완전히 같은 두 Data Class 객체를 만들고, hashCode()
를 출력해보자.
val peopleA = People("H43RO", 23)
val peopleB = People("H43RO", 23)
println(peopleA.hashCode())
println(peopleB.hashCode())
2110922579
2110922579
두 객체의 hashCode()
가 같은 값을 출력함을 확인할 수 있다. 일반 클래스라면, 다른 값을 갖게 된다.
equals()
메소드이어서 두 객체를 비교하는 연산도 수행해보자.
println(peopleA == peopleB)
// true (두 객체의 프로퍼티가 완전히 같음)
'==' 연산자 하나로, 두 객체가 동일한 값을 담고있는지 쉽게 검사할 수 있다.
또한, ===
연산도 가능하다. 메모리 상 다른 객체이므로 false
를 출력한다.
println(peopleA == peopleB)
// false (두 객체의 메모리 주소가 다름)
🤚🏻 Java vs Kotlin 동등성 비교
- 갖고 있는 값이 동일한지 검사
- Java :
equals()
- Kotlin :
==
- 메모리상 같은 객체인지 검사
- Java :
==
- Kotlin :
===
componentN()
메소드Data Class 는 기본적으로 componentN()
메소드가 생성이 되기 때문에, 각 프로퍼티에 번호가 붙어 구조 분해가 가능한 형태가 된다. 그럼 한 번 직접 구조 분해 (Destructuring Declarations) 를 해보자!
val peopleA = People("H43RO", 23)
val (name, age) = peopleA
println("Destructuring Declarations : $name, $age")
// Destructuring Declarations : H43RO, 23
이런 식으로 프로퍼티의 값들을 가독성있게 분해해냈다. 매우 편리한 기능이다!
내부적으론 다음과 같이 동작한다. 우선, 다시 People 클래스의 구조를 살펴보자.
data class People(
val name: String,
val age: Int
)
이 클래스는 프로퍼티가 2개이다. 이 때 선언 순서가 name
다음에 age
형태로 되어있기 때문에, component1()
메소드에 name
필드가 대응되고, component2()
메소드에 age
필드가 대응되게 된다. 이 모든 과정을 컴파일러가 알아서 해준다! 실제로 .
을 찍어서 멤버를 확인해보면, 메소드가 생성되어 있다.
따라서, 컴파일러에 의해 아래와 같이 구조 분해가 일어나게 된다.
val name = peopleA.component1()
val age = peopleA.component2()
개발자가 val (name, age) = peopleA
라고만 적어줘도, 위 동작을 내부적으로 수행해준다.
(해당 포스팅에선 구조 분해에 대해서 자세히 다루진 않겠다)
이렇듯 Data Class 가 기본적으로 제공해주는 기능들만 해도, 비대한 보일러 플레이트 코드를 줄일 수 있어 매우 간결할 뿐더러 개발자 입장에서 말 그대로 데이터를 담기 위한 클래스를 다루기 매우 편리해졌다. 다양한 분야에 Data Class 를 접목해볼 수 있고, 코틀린만 갖고 있는 문법인 만큼 유용하게 활용해보도록 하자.
또한, hashCode()
와 구조 분해에 대하여 자세히 다루지 않았기 때문에, 이러한 부분들도 차후 다뤄볼 예정이다.
사실은 자바에서도 JDK 14 (프리뷰, 정식 기준 JDK 16) 부터 Record Class 라는 것이 도입되면서, 자바에서도 코틀린의 Data Class 와 동일한 기능을 사용할 수 있게 되었다고 한다. 아무래도 보일러 플레이트 코드의 불필요성은 자각한 모양이다. 하지만 엄연히 차이가 명확하다!
copy()
메소드는 코틀린의 Data Class 만 제공해줌
자바의 Record Class 는 모든 필드가 private
+ final
인 반면, 코틀린의 Data Class 는 선언 방식에 따라 필드값 변경도 가능함
코틀린의 Data Class 는 데이터 클래스 그 자체인데, 자바의 Record Class 는 Record
라는 베이스 클래스를 상속받은 형태임
가장 큰 차이점은, 인스턴스 변수를 정의할 수 있냐 없냐의 차이임. Data Class 는 인스턴스 변수를 만들 수 있지만, Record Class 는 인스턴스 변수를 허용하지 않음
좋은 글 감사합니다^^
오타가 하나 있는데
println(peopleA == peopleB)
// false (두 객체의 메모리 주소가 다름)
여기서 =이 하나 더 붙어야 될 것 같습니다^^