Post

[Java] Record

[Java] Record

📌 Record란?

자바의 Record 는 Java 16에서 정식으로 도입된 특별한 종류의 클래스이다. Record의 핵심 목적은 불변 데이터를 담는 객체를 간결하게 작성하는 것이다.

Record가 없던 시절

보통 Record는 DTO와 같은 불변 객체를 만들기 위해 사용한다. 이를 Record를 사용하지 않고 작성하면 다음과 같이 작성할 수 있다.

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
31
32
33
34
35
36
37
38
39
40
public final class Person {

    private final String name;
    private final int age;

    public PersonClass(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonClass that = (PersonClass) o;
        return age == that.age && java.util.Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "PersonClass[" +
               "name='" + name + '\'' +
               ", age=" + age +
               ']';
    }
}

먼저 클래스에 final 키워드를 사용하여 상속을 막아야 하고, 필드를 private final 로 선언하고, 모든 필드를 초기화하는 생성자를 만들고, getter를 만들고, hashCodeequals, toString 메서드를 구현해야 한다. 이제 다른 불변 객체를 만들자. 방금 작성한 코드를 다시 작성하고 싶은가? 그저 불변 객체를 만들고 싶었을 뿐인데, 수십 줄의 코드를 일일히 작성해야 한다.

@Lombok 어노테이션의 등장 배경을 보면 알겠지만, 보일러플레이트 코드를 직접 작성하는 것은 어마무시한 생산성 저하를 가져온다.

이제, Record를 사용하여 다시 작성해보자.

1
public record Person(String name, int age) {}

놀라울 정도로 간단해진다. Record를 사용하면 이전에 작성한 private final 필드와 생성자, getter, equals, hashCode, toString 메서드를 자바 컴파일러가 컴파일 시점에 자동으로 생성해준다.

📌 컴팩트 생성자

Record만의 특별한 기능이 있는데, 컴팩트 생성자이다.

컴팩트 생성자는 Record가 자동으로 생성하는 생성자에 검증, 기본 값 할당 등과 같은 로직을 간단하게 추가하기 위해 등장한 개념이다. Record가 모든 필드를 인자로 받아 초기화하는 생성자를 만들기 전에 동작한다.

1
2
3
4
5
public Person {
    if (age < 0) {
        throw new IllegalArgumentException("나이는 음수일 수 없습니다.");
    }
}

여타 생성자와 가장 큰 특징은 파라미터를 받는 소괄호가 없다는 것이다. 암묵적으로 Record에 정의된 모든 필드를 받는다. 컴팩트 생성자 내부에서 사용하는 변수는 final 필드가 아니라 가변적인 파라미터로, 값 변경이 가능하다. 컴팩트 생성자의 모든 로직이 실행된 후 최종적으로 결졍된 파리미터 값들이 final 필드에 할당된다.

📌 사용 예시

언급했다시미 DTO를 Record를 통해 많이 구현한다.

1
2
3
4
5
6
7
8
public record Coordinate(int x, int y) {}

public class GameMap {

	Map<Coordinate, String> worldMap = new HashMap<>();
	
	// ...
}

그 외에도 JSON과 매핑되는 API의 데이터 모델, Map의 키로 사용될 복합적인 값을 표현할 때 사용한다.

📌 한계점

먼저 JPA Entity로 사용할 수 없다. JPA의 Lazy Loading 기능을 사용할 수 없기 때문인데, Lazy Loading을 통해 엔티티를 상속받는 프록시 객체를 사용해야 한다. 그러나 Record는 final 클래스이므로 상속이 불가능하다.

또한 모든 필드는 final 이므로 setter를 가질 수 없다. 즉, 상태 변경이 필요한 객체에는 Record가 적합하지 않다.

This post is licensed under CC BY 4.0 by the author.