Record

김도비·2025년 1월 14일
post-thumbnail

Pull Request 요청을 받고 코드 리뷰를 하는데
팀원이 Controller에서 반환 타입을 ResponseEntity<ResponseDto>가 아닌 ResponseEntity<ResponseProj>로 작성해 의문이 들었다.

ResponseProj는 아래와 같았다.

public record ResponseProj(
	Long id,
    String name,
    ...
) {}

JPA projection 사용하니까 suffix를 Proj로 한 것까진 알겠는데 record는 뭐고 필드가 왜 저기 적혀있지...?

이전 회사에서도 record라는 타입을 본 적이 없었기에 궁금해서 검색해보았다.


불변 데이터 클래스 작성

우리는 그동안 변경 불가능한 데이터 클래스를 작성하기 위해 아래와 같은 6가지 작업을 반복해왔다.

public class Person {

	// 1. private 필드
    private final String name;
    private final String address;

	// 2. public 생성자
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
    
    // 3. 각 필드에 대한 getter
    public String getName() {
    	return name;
    }

	// 4. 모든 필드가 일치할 때 동일한 값을 반환하는 hashCode 메서드
    @Override
    public int hashCode() {
        return Objects.hash(name, address);
    }

	// 5. 모든 필드가 일치할 때 동일한 클래스의 객체에 대해 true를 반환하는 equals 메서드
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (!(obj instanceof Person)) {
            return false;
        } else {
            Person other = (Person) obj;
            return Objects.equals(name, other.name)
              && Objects.equals(address, other.address);
        }
    }

	// 6. 클래스 이름과 각 필드 이름 및 해당 값을 포함하는 toString 메서드
    @Override
    public String toString() {
        return "Person [name=" + name + ", address=" + address + "]";
    }
}

이렇게 하면 불변 클래스를 만들 수는 있지만 2개의 문제점이 있다.


문제 1: boilerplate code

equals, hashCode 및 toString 메서드와 생성자 생성 등 각 데이터 클래스에 대해 지루한 동일 과정을 반복해야 한다.

IDE에서 자동 생성 기능을 제공하긴 하지만, 새 필드를 추가할 때 클래스를 자동으로 업데이트 하지는 못 해 equals와 같은 메서드를 수동으로 업데이트 해야 한다.


문제 2: 모호해진 클래스의 목적

추가 코드들로 인해 이름과 주소 딱 2개의 String 필드를 갖는 간단한 데이터 클래스라는 점을 모호하게 한다.


이러한 문제들을 해결하기 위해서는 해당 클래스가 데이터 클래스라고 명시적으로 선언해야 한다.



Record

위의 문제를 해결하기 위해 Java 14에서 처음 등장한 타입으로, 필드 타입과 이름만 필요한 불변 데이터 클래스다.

코드는 아래와 같이 작성한다.

public record Person (String name, String address) {}

특징

메서드와 생성자 자동 생성(Automatic Generation)

equals, hashCode, toString 메서드와 private, final 필드, public 생성자는 Java Compiler에 의해 자동으로 생성된다.


getter 기본 지원

필드 이름과 동일한 이름을 가진 getter 메서드도 사용할 수 있다.

@Test
public void givenValidNameAndAddress_whenGetNameAndAddress_thenExpectedValuesReturned() {
    String name = "John Doe";
    String address = "100 Linda Ln.";

    Person person = new Person(name, address);

    assertEquals(name, person.name());
    assertEquals(address, person.address());
}

사용자 정의 생성자

기본 public 생성자는 컴파일러가 자동으로 생성해주는데, 필요할 경우 사용자 정의 생성자를 만들 수 있다.

단, 기본 생성자와 동일한 arguments를 갖는 생성자를 만들면 컴파일 오류가 발생한다.

public record Person(String name, String address) {
	// Null 체크 생성자
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }
}

public record Person(String name, String address) {
	// 다른 argument를 갖는 생성자
    public Person(String name) {
        this(name, "Unknown");
    }
}

public record Person(String name, String address) {
    public Person {
        Objects.requireNonNull(name);
        Objects.requireNonNull(address);
    }
    
    // 컴파일 오류
    public Person(String name, String address) {
        this.name = name;
        this.address = address;
    }
}

static 사용 가능

일반 클래스와 마찬가지로 정적 변수와 메서드도 생성 가능하다.

public record Person(String name, String address) {
	// 정적 변수
    public static final String UNKNOWN_ADDRESS = "Unknown";
    
    // 정적 메서드
    public static Person unnamed(String address) {
        return new Person("Unnamed", address);
    }
}

불변성(Immutability)

필드가 한 번 설정되면 값을 변경할 수 없다. 이는 불변성을 보장하며, 데이터의 안정성을 높인다.


final 선언 생략

final 선언을 하지 않아도 컴파일러가 해당 필드를 불변으로 판단하고 자동 final로 처리한다.


클래스 상속 불가

다른 클래스를 상속받을 수도, 다른 클래스가 상속할 수도 없다.


private final 이외의 필드 선언 불가

선언되는 다른 모든 필드는 static이어야 한다.


참고 Java Record Keyword



응답 값은 불변 데이터이기에 팀원이 record를 사용했다는 것을 알게 되었다.

덕분에 새로운 타입 공부도 했고, 앞으로 불변 데이터 전달 시에는 record를 사용하는 게 더 좋겠다는 생각이 들었다.

profile
Java Backend 4년차 Developer

0개의 댓글