레코드 (Record)
한눈에 보기
레코드는 불변 데이터 클래스를 한 줄로 선언하게 해 주는 자바의 특수 클래스로 Java 16에서 정식화되었다. 자동 생성 요소, 컴팩트 생성자, 제약의 이유, 패턴 매칭과의 결합을 다룬다.
최종 업데이트 2026.09.17
레코드(Record)는 불변 데이터를 담는 것이 목적인 클래스를 한 줄로 선언할 수 있게 해 주는 자바의 특수한 클래스 형태로, Java 14에서 프리뷰로 등장해 Java 16에서 정식 기능이 되었다. 헤더에 컴포넌트(component)를 선언하면 컴파일러가 private final 필드, 생성자, 접근자, equals와 hashCode와 toString을 자동으로 만들어 준다. 값이 같으면 같은 객체로 취급하는 값 의미론(value semantics)을 언어 차원에서 지원하는 것이 핵심이다.
무엇이 자동으로 생성되는가
record Member(String name, int age)라고 선언하면 컴파일러는 다음을 생성한다. 각 컴포넌트에 대응하는 private final 필드, 모든 컴포넌트를 받는 표준 생성자(canonical constructor), 필드명과 같은 이름의 접근자 메서드(name(), age()), 모든 컴포넌트 값을 비교하는 equals와 hashCode, 컴포넌트를 나열하는 toString이다. get 접두사가 없는 접근자 이름이 관례와 다르다는 점에 주의해야 한다.
public record Member(String name, int age) {}
Member a = new Member("kim", 20);
Member b = new Member("kim", 20);
System.out.println(a.name()); // "kim"
System.out.println(a.equals(b)); // true, 값 기반 비교
System.out.println(a); // Member[name=kim, age=20]
일반 클래스로 같은 코드를 쓰면 수십 줄의 상용구(boilerplate)가 필요하고, equals와 hashCode를 손으로 유지하다 필드 추가 시 갱신을 빠뜨리는 버그가 흔했다. 레코드는 이 상용구를 없앨 뿐 아니라, 이 클래스는 데이터 그 자체라는 설계 의도를 선언적으로 드러낸다.
컴팩트 생성자와 커스터마이징
레코드에도 검증 로직이나 추가 메서드를 넣을 수 있다. 매개변수 목록 없이 쓰는 컴팩트 생성자(compact constructor)는 표준 생성자의 필드 대입 직전에 실행되어 검증과 정규화에 쓰인다. static 팩터리 메서드, 인스턴스 메서드, static 필드도 자유롭게 추가할 수 있다.
public record Money(String currency, long amount) {
public Money { // 컴팩트 생성자
if (amount < 0) {
throw new IllegalArgumentException("금액은 음수일 수 없다");
}
currency = currency.toUpperCase(); // 매개변수를 정규화하면 필드에 반영된다
}
public Money add(Money other) { // 불변이므로 새 객체를 반환한다
return new Money(currency, amount + other.amount);
}
}
제약 사항과 그 이유
레코드는 암묵적으로 final이라 상속할 수 없고, 내부적으로 java.lang.Record를 확장하므로 다른 클래스를 extends 할 수도 없다. 인스턴스 필드는 헤더에 선언한 컴포넌트가 전부이며 본문에 추가 인스턴스 필드를 둘 수 없다. 이 제약들은 모두 상태가 곧 컴포넌트라는 투명성을 지키기 위한 것으로, 그 대가로 컴파일러와 직렬화와 패턴 매칭이 레코드의 구조를 신뢰할 수 있게 된다. 인터페이스 구현은 자유롭게 가능하다.
주의할 점은 레코드의 불변이 얕은 불변(shallow immutability)이라는 것이다. 컴포넌트가 List 같은 가변 객체라면 참조는 바뀌지 않아도 내부 내용은 바뀔 수 있으므로, 컴팩트 생성자에서 List.copyOf로 방어적 복사를 해 주는 것이 안전하다.
패턴 매칭과의 결합
Java 21에서 정식화된 레코드 패턴(record pattern)을 쓰면 레코드를 컴포넌트 단위로 분해하면서 매칭할 수 있다. sealed 인터페이스와 조합하면 컴파일러가 모든 경우를 검사해 주는 데이터 중심 코드가 완성된다.
sealed interface Event permits Login, Logout {}
record Login(String userId, String ip) implements Event {}
record Logout(String userId) implements Event {}
static String describe(Event e) {
return switch (e) {
case Login(String id, String ip) -> id + "가 " + ip + "에서 로그인";
case Logout(String id) -> id + "가 로그아웃";
};
}
일반 클래스, Lombok과 비교
| 구분 | 레코드 | 일반 클래스 | Lombok @Value |
|---|---|---|---|
| 상용구 | 언어가 자동 생성 | 직접 작성 | 애너테이션 처리기가 생성 |
| 불변성 | 강제 (필드 final) | 선택 | 관례로 보장 |
| 상속 | 불가, 암묵적 final | 가능 | 설정에 따름 |
| 패턴 매칭 분해 | 레코드 패턴 지원 | 미지원 | 미지원 |
| 도구 의존성 | 없음 (표준) | 없음 | 외부 라이브러리 필요 |
실무에서는 DTO(Data Transfer Object), API 응답 모델, 복합 키, 스트림 중간 결과 묶음처럼 식별자 없이 값으로만 의미가 정해지는 자리에 레코드가 잘 맞는다. 반면 JPA 엔티티는 기본 생성자와 가변 상태와 프록시를 요구하므로 레코드로 만들 수 없다는 점을 함께 알아 두면 좋다. 잭슨(Jackson) 같은 직렬화 라이브러리와 스프링의 설정 바인딩은 레코드를 정식 지원한다.
면접에서 이렇게 물어본다
- 레코드를 선언하면 컴파일러가 무엇을 자동 생성하는지 나열해 보라.
- 레코드가 상속을 금지하고 추가 인스턴스 필드를 막는 이유는 무엇인가.
- 레코드의 불변성이 얕은 불변이라는 말의 의미와 대응 방법을 설명해 보라.
- 컴팩트 생성자는 언제 사용하며 표준 생성자와 어떻게 다른가.
- JPA 엔티티를 레코드로 만들 수 없는 이유를 설명해 보라.
Java에서 같이 읽기
- 오토 박싱과 오토 언박싱 (Auto Boxing and Unboxing)오토 박싱은 기본형 값을 래퍼 클래스 객체로, 오토 언박싱은 그 반대로 자동 변환하는 기능이다. Integer 캐시, == 비교 함정, NullPointerException 위험과 성능 비용을 JVM 메모리 관점에서 정리한다.
- Call by Value와 Call by Reference자바는 기본형이든 참조형이든 언제나 값에 의한 호출만 사용하며, 참조형은 객체가 아니라 참조값이 복사되어 전달된다. swap 실패 예제와 스택 프레임 관점으로 그 근거를 설명한다.
- 캐스팅 (Casting), 업캐스팅과 다운캐스팅캐스팅은 값이나 참조를 다른 타입으로 취급하는 변환이다. 기본형 확대와 축소 변환, 업캐스팅과 다형성, 다운캐스팅과 instanceof 패턴 매칭, ClassCastException 예방법을 다룬다.
- 자바 컴파일 과정 (Java Compile Process)javac가 소스를 바이트코드로 바꾸고 JVM이 로딩, 링킹, 초기화 후 인터프리터와 JIT로 실행하는 전체 흐름을 정리한다. 클래스 파일 구조와 부모 위임 모델, 계층형 컴파일까지 다룬다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기