2025년 3월 20일 작성
Java Record Class - 불변하는 정보를 저장하는 특별한 객체 만들기
Java record는 Java 16에서 정식 도입된 불변 data 객체로, 적은 code로 DTO나 VO를 효율적으로 표현할 수 있는 특별한 형태의 class입니다.
Java의 Record : Immutable Data Object
- Java record는 Java 14에서 preview로 도입되고, Java 16에서 정식 기능으로 채택된 특별한 형태의 class입니다.
public record RecordName(String field1, int field2, ...) {
// field, constructor, method
}
- record는 data를 보관하는 목적의 class를 간결하게 정의할 수 있도록 설계되었습니다.
- immutable data 객체를 생성하는 데 최적화된 선언적 방식을 제공합니다.
- DTO(Data Transfer Object), VO(Value Object), 또는 불변 data의 container로 사용하기 적합합니다.
- record는 field 값으로만 식별되므로, 동일한 값을 가진 두 record는 동일한 객체로 취급됩니다.
- 불변 객체이므로, 동일한 값을 가진 객체는 동일한 참조 값을 가질 수 있습니다.
- 값 기반 identity를 가지기 때문에, 객체의 동등성(equality) 비교가 간단해집니다.
기능 | Record | 일반 Class |
---|---|---|
불변성 | 기본적으로 불변 | 가변 또는 불변 모두 가능 |
Getter | 자동 생성 (field 이름과 동일) | 수동 구현 필요 |
생성자 | Canonical Constructor 자동 생성 | 수동 구현 필요 |
equals/hashCode | 자동 생성 | 수동 구현 필요 |
toString | 자동 생성 | 수동 구현 필요 |
상속 | 불가능 | 가능 |
Mutable field | 불가능 | 가능 |
- record는 주로 불변 data를 표현하고 교환하는 상황에서 사용하면 좋습니다.
- API response data modeling에 적합합니다.
- database query result를 담는 객체로 활용할 수 있습니다.
- 복잡한 계산의 중간 결과를 저장하는 용도로 사용할 수 있습니다.
- event-driven system의 message 객체로 활용할 수 있습니다.
- configuration data를 담는 객체로 사용할 수 있습니다.
- 불변 객체를 생성하는 데 최적화되어 있기 때문에, 값이 변해야 하거나 기능 위주의 객체를 만들 때는 적합하지 않습니다.
- mutable 객체가 필요한 경우에는 적합하지 않습니다.
- 상속을 통한 기능 확장이 불가능합니다.
- 객체의 life cycle 동안 field 값이 변경되어야 하는 경우에는 적합하지 않습니다.
- lazy loading과 같은 최적화 기법을 적용하기 어렵습니다.
Record 객체의 특징
- 기본적으로 불변(immutable) 객체를 생성합니다.
- 모든 field는 자동으로
final
로 선언됩니다. - 객체 생성 후에는 field 값을 변경할 수 없습니다.
- 모든 field는 자동으로
equals()
,hashCode()
,toString()
method가 자동으로 생성됩니다.- 자동으로 생성된 method들은 record의 모든 component를 고려하여 생성됩니다.
- 각 field에 대한 getter method가 자동으로 생성됩니다.
- 일반적인 JavaBean 방식의
getXxx()
형태가 아닌 field 이름과 동일한 method가 생성됩니다.
- 일반적인 JavaBean 방식의
- 모든 field를 초기화하는 canonical constructor가 자동으로 생성됩니다.
- canonical constructor : 모든 field를 parameter로 받는 생성자.
- 모든 field에 값을 지정하기 위한 생성자를 따로 선언하지 않아도 됩니다.
- 상속이 불가능합니다.
- record는 암시적으로
final
class로 선언됩니다. - 다른 class를 상속받을 수 없으며, 다른 class가 record를 상속받을 수도 없습니다.
- record는 암시적으로
- interface 구현은 가능합니다.
- interface를 구현하는 record class는 interface의 method를 구현해야 합니다.
Record Class의 기본 문법
record
keyword를 사용하여 record class를 선언합니다.
public record Person(String name, int age) {
// 추가 field, method, constructor 등을 선언할 수 있습니다.
}
기본 사용법
// record 정의
public record Person(String name, int age) {}
// 사용 예시
Person person = new Person("Kim", 30);
String name = person.name(); // "Kim" - getter method 호출
int age = person.age(); // 30 - getter method 호출
System.out.println(person); // Person[name=Kim, age=30] - toString() 자동 구현
Custom Constructor 추가
public record Person(String name, int age) {
// Compact constructor - Parameter 목록 없이 정의
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty");
}
}
// 추가 constructor
public Person(String name) {
this(name, 0); // canonical constructor 호출
}
}
Static Member 추가
public record Person(String name, int age) {
// Static field
public static final String UNKNOWN = "Unknown";
// Static method
public static Person createAnonymous() {
return new Person(UNKNOWN, 0);
}
}
Method 추가
public record Person(String name, int age) {
// Instance method
public boolean isAdult() {
return age >= 18;
}
// toString() 재정의
@Override
public String toString() {
return name + " (" + age + " years old)";
}
}
Record가 불변하기 때문에 얻는 많은 이점들
- 일반적으로 더 많은 data가 불변성을 가질수록, 더 좋은 code를 작성할 수 있습니다.
- 생성된 후 상태가 변경되지 않으므로 예측 가능한 동작을 보장합니다.
- side effect를 방지하여 code의 안정성을 높입니다.
- 방어 logic을 위한 복사본을 만들 필요가 없어 code가 간결해집니다.
- record는 불변 객체이기 때문에, 성능상 여러 이점을 가집니다.
- thread 안전성을 보장하여 동시성 문제를 방지합니다.
- memory 효율성을 높여 JVM 최적화 기회를 늘립니다.
- GC 성능을 향상시켜 memory 사용량을 줄입니다.
- 실제로, record를 사용하여 이점을 얻을 수 있는 다양한 상황이 있습니다.
- big data processing application에서 record를 사용하여, memory 사용량과 GC pressure를 줄일 수 있습니다.
- 분산 system의 message 객체로 record를 사용하여, thread 안전성을 보장하면서 성능을 향상시킬 수 있습니다.
- micro-service architecture에서 service 간 data 전송 객체(DTO)로 record를 사용하여, serialization/deserialization 과정이 단순화할 수 있습니다.
- 불변성을 중요히 여기는 functional programming pattern과 잘 어울려, side effect 없는 code 작성에 도움이 됩니다.
Thread 안전성
- record는 불변 객체이므로 여러 thread에서 동시에 접근해도 안전합니다.
- thread 간 동기화가 필요 없어 lock overhead가 발생하지 않습니다.
- race condition이나 deadlock과 같은 동시성 문제를 방지할 수 있습니다.
- 분산 system이나 동시성 높은 application에서 data 무결성을 보장합니다.
메모리 효율성
- record를 사용하는 경우, boilerplate code가 적어져 class file 크기가 작아집니다.
- boilerplate code는 반복적으로 작성되는 code를 의미합니다.
- 일반 class의 경우 getter, setter, equals, hashCode, toString 등 많은 method가 포함됩니다.
- record는 이런 code를 compiler가 자동 생성하므로 byte code 크기가 줄어듭니다.
- JVM의 최적화 기회가 많아집니다.
- 불변 객체는 JVM이 더 효율적으로 관리할 수 있습니다.
- 동일한 값을 가진 record instance는 내부적으로 동일 참조로 최적화될 가능성이 있습니다.
- JVM은 동일한 값을 가진 record instance를 caching하여 재사용할 수 있습니다.
JVM 최적화와 Constant Pool
- 불변 객체는 JVM의 string constant pool과 유사한 방식으로 caching될 수 있습니다.
- 동일한 값을 가진 record instance는 재사용될 가능성이 높아집니다.
- JIT compiler가 더 효과적으로 최적화할 수 있게 됩니다.
- 단, JVM이 모든 record 객체를 constant pool에 저장하는 것은 아닙니다.
- 특정 조건에서만 최적화가 이루어지며, 이는 JVM 구현체에 따라 다를 수 있습니다.
- String.intern()과 같은 명시적 caching mechanism은 record에 기본 제공되지 않습니다.
GC(Garbage Collection) 성능
- record 객체는 불변이므로 GC 관점에서 여러 이점이 있습니다.
- 불변 객체는 수명이 짧은 경우가 많아 young generation에서 빠르게 수집됩니다.
- 또한, 참조가 변경되지 않아 GC의 mark-and-sweep 단계가 효율적으로 수행됩니다.
- 참조 graph 변경이 적어 GC pause time도 줄일 수 있습니다.
- 많은 양의 record 객체를 생성하더라도 GC overhead가 일반 객체보다 적습니다.
- 불변 객체는 generational hypothesis에 잘 맞아 GC 효율이 높아집니다.
- 값 기반 identity로 인해 참조 추적이 단순화됩니다.