Octoping의 블로그
article thumbnail

이전에 Java에서 불변 데이터 객체를 만들기 위해서는 여러 보일러플레이트들이 많이 필요했다.

 

그리고 이런 불편을 해소하기 위해 JDK 14부터 record라는 녀석이 추가되었다.

getter, equals, hashcode, toString 등을 자동으로 만들어주어, 불변 데이터 객체를 쉽게 만들 수 있게 해준다.

 

하지만 이런 record를 사용할 때 조심해야 할 점이 있다.

 

public record Person(
    String[] names,
    int age
) {
}

바로 다음과 같이 레코드의 구성요소에 배열(array)이 있을 경우이다.

 

레코드의 구성요소에 배열이 존재할 경우, 레코드가 보일러플레이트들을 자동으로 생성해줌에도 불구하고 직접 equals, hashcode, toString을 재정의해주어야 한다.

 

실제로 SonarQube에도 관련된 이슈가 major 단계로 등록되어 있다.

 

Java static code analysis

Unique rules to find Bugs, Vulnerabilities, Security Hotspots, and Code Smells in your JAVA code

rules.sonarsource.com

 

그렇다면 이제 왜 이런 이슈가 존재하는지 그 이유를 알아보자.

 

이유

 

Record (Java SE 17 & JDK 17)

Direct Known Subclasses: UnixDomainPrincipal public abstract class Record extends Object This is the common base class of all Java language record classes. More information about records, including descriptions of the implicitly declared methods synthesize

docs.oracle.com

오라클의 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
) {
}

 

List 최고

 

 

 

profile

Octoping의 블로그

@Octoping

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!