[Java] 자바 Record 불변 데이터 객체 쉽게 만들기 (Java 14+)
레코드(Record)란?
Java 14부터 도입된 record는 불변(immutable) 데이터를 담기 위한 간결한 클래스 문법입니다.
우리가 흔히 사용하는 DTO(Data Transfer Object)나 VO(Value Object) 클래스보다 훨씬 간단하고 명확하게 데이터를 표현할 수 있죠.
불변(Immutable) 데이터란?
한 번 만들어진 이후, 내부 상태(값)를 바꿀 수 없는 객체를 의미.
즉, 객체가 생성되고 나면 필드 값을 변경할 수 없고 외부에서 setter로 값을 바꾸는 것도 불가능
불변 객체는 데이터를 바꿀 수 없으므로,
여러 스레드가 동시에 접근해도 동기화(synchronized) 처리 없이 사용할 수 있음.
예) 이 클래스는 setName()을 통해 객체의 상태(이름)를 바꿀 수 있어 → 가변
public class MutableUser {
private String name;
public MutableUser(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name; // 언제든 이름이 바뀔 수 있음
}
public String getName() {
return name;
}
}
예) name이 final이므로 한 번 값이 들어가면 바꿀 수 없음, setter도 없음 → 불변
public final class ImmutableUser {
private final String name;
public ImmutableUser(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
언제 레코드를 사용하면 좋을까요?
단순 데이터 전달 객체: DTO/VO 대체
JSON 응답 매핑: 값 변경 없이 읽기 전용으로 사용 가능
API 응답 데이터: 서버에서 클라이언트로 보내는 응답 데이터를 record로 정의하면, 응답 구조가 명확해지고 불변성을 보장할 수 있다.
데이터베이스 엔티티 객체: 데이터베이스에서 조회한 데이터를 전송할 때, 불변 객체로 정의하면 데이터 무결성을 보호할 수 있다.
컬렉션에 객체를 추가할 때: 두 개의 동일한 객체가 있는지 비교하고 싶을 때, record를 사용하면 자동으로 equals()와 hashCode()를 지원한다.
동일한 키로 여러 작업을 할 때: Map의 키로 객체를 사용할 때 record를 사용하면 성능이 향상되고 코드가 간결해진다.
시연코드
public class Main {
// 🟦 불변 데이터 객체 User
public record User(String name, int age) {}
// 🟦 생성자 유효성 검증과 메소드가 있는 Product
public record Product(String name, int price) {
// 컴팩트 생성자: 가격은 0 이상만 허용
public Product {
if (price < 0) {
throw new IllegalArgumentException("가격은 0 이상이어야 합니다.");
}
}
// 사용자 정의 메소드
public String label() {
return name + " - " + price + "원";
}
}
public static void main(String[] args) {
// ✅ User 레코드 사용 예제
User user = new User("Alice", 30);
System.out.println("사용자 이름: " + user.name()); // getter 역할
System.out.println("사용자 나이: " + user.age());
System.out.println("User toString(): " + user); // 자동 생성된 toString()
// ✅ Product 레코드 사용 예제
Product coffee = new Product("커피", 4500);
System.out.println("상품 라벨: " + coffee.label()); // 커피 - 4500원
// ⚠️ 유효하지 않은 가격 (예외 발생)
try {
Product invalid = new Product("녹차", -1000);
} catch (IllegalArgumentException e) {
System.out.println("예외 발생: " + e.getMessage());
}
// ✅ equals/hashCode 자동 동작 확인
User user1 = new User("Alice", 30);
User user2 = new User("Alice", 30);
System.out.println("user1 equals user2? " + user1.equals(user2)); // true
}
}