이전에 Java에서 불변 데이터 객체를 만들기 위해서는 여러 보일러플레이트들이 많이 필요했다.
그리고 이런 불편을 해소하기 위해 JDK 14부터 record
라는 녀석이 추가되었다.
getter
, equals
, hashcode
, toString
등을 자동으로 만들어주어, 불변 데이터 객체를 쉽게 만들 수 있게 해준다.
하지만 이런 record를 사용할 때 조심해야 할 점이 있다.
public record Person(
String[] names,
int age
) {
}
바로 다음과 같이 레코드의 구성요소에 배열(array)이 있을 경우이다.
레코드의 구성요소에 배열이 존재할 경우, 레코드가 보일러플레이트들을 자동으로 생성해줌에도 불구하고 직접 equals, hashcode, toString을 재정의해주어야 한다.
실제로 SonarQube에도 관련된 이슈가 major 단계로 등록되어 있다.
그렇다면 이제 왜 이런 이슈가 존재하는지 그 이유를 알아보자.
이유
오라클의 record 명세를 보면 다음과 같이 적혀있는 것을 볼 수 있다.
한글로 번역해서 정리해보자면 다음과 같다.
레코드의 equals 메소드는 해당 레코드의 모든 매개변수 인스턴스가 상대 인스턴스와 동등할 때 true를 반환한다.
레코드 클래스의 동등성(Equality)을 비교하는 방법은 다음과 같다.
- 레코드 구성요소 c가 참조 타입 (reference type)일 경우, Objects.equals(this.c, r.c)의 결과로 판단한다
- 레코드 구성요소 c가 원시 타입일 경우, 래퍼 클래스로 바꿔서 Integer.compare(this.c, r.c)와 같이 compare의 결과로 판단한다.
배열(array)은 참조 타입이므로 Objects::equals
의 결과로 동등성을 판단하게 되고, 배열은 참조가 같을 경우에만 true를 반환하기 때문에 레코드의 equals 연산은 우리가 바라던 대로 흘러가지 않게 된다.
그리고 이는 hashcode
, toString
에도 똑같은 논리로 적용되어 원치 않는 결과를 만들어낼 수 있다.
해결법
직접 equals, hashcode, toString을 재정의한다
문제에 대한 직관적인 해답이다.
직접 배열에 대응하도록 이 셋을 재정의해주어야 한다.
public record Person(
String[] names,
int age
) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Arrays.equals(names, person.names);
}
@Override
public int hashCode() {
int result = Objects.hash(age);
result = 31 * result + Arrays.hashCode(names);
return result;
}
@Override
public String toString() {
return "Person{" +
"names=" + Arrays.toString(names) +
", age=" + age +
'}';
}
}
하지만 이렇게 되면 코드도 굉장히 복잡해지고, 사실상 Record를 사용하는 맛이 없다.
배열 대신 List를 사용한다
훨씬 간단하고 영리한 방법이다.
Objects::equals
를 제대로 지원하지 않는 배열 대신 List를 사용하면 된다.
public record Person(
List<String> names,
int age
) {
}
'언어 > Java' 카테고리의 다른 글
잘못된 값을 입력하면 예외를 발생시키고 입력 다시 받기 (0) | 2023.11.07 |
---|---|
클래스 내의 모든 변수 이름, 값 가져오기 (0) | 2022.06.18 |
언박싱 메소드 (0) | 2022.06.18 |
Divided by Zero 에러 double 형 (0) | 2022.06.18 |