[Java] 레코드 클래스 살펴보기 (Record Class)
Record Class
자바의 레코드 클래스는 Java 14부터 프리뷰 상태로 도입되어 Java 16에서 정식으로 지원하게 된 새로운 클래스다. 레코드는 데이터를 담기 위한 클래스를 더욱 간결하고 안전하게 작성하기 위한 기능을 제공한다. 레코드 클래스를 사용하면 기존 POJO 클래스를 작성할 때 필요한 보일러플레이트 코드를 줄이는 효과를 얻을 수 있다.
레코드의 특징
- final 클래스로 상속이 불가능하다.
- 인터페이스 구현이 가능하다.
- 모든 필드는 불변(private final)으로 값을 변경할 수 없다.
- getter 메서드를 기본으로 제공한다.
- 모든 필드를 포함한 생성자를 기본으로 제공한다.
- toString(), equals(), hashCode() 메서드를 기본으로 제공한다.
클래스와 레코드 비교
아래 두 예제는 동일한 역할을 한다.
클래스
public final class Person {
private final String name;
private final int age;
private final LocalDate birthDate;
private final String email;
public Person(String name, int age, LocalDate birthDate, String email) {
this.name = name;
this.age = age;
this.birthDate = birthDate;
this.email = email;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public LocalDate getBirthDate() {
return birthDate;
}
public String getEmail() {
return email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person that = (Person) o;
return getAge() == that.getAge() &&
Objects.equals(getName(), that.getName()) &&
Objects.equals(getBirthDate(), that.getBirthDate()) &&
Objects.equals(getEmail(), that.getEmail());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge(), getBirthDate(), getEmail());
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", birthDate=" + birthDate +
", email='" + email + '\'' +
'}';
}
}
기존 클래스에서는 모든 필드를 명시적으로 선언하고, 생성자에서 초기화해야 하며, 모든 필드에 대해 getter 메서드를 작성해야 한다. 또한, 기존 클래스에서는 equals와 hashCode, toString 메서드를 직접 구현해야 한다.
레코드
public record Person(
String name,
int age,
LocalDate birthDate,
String email
) {
}
레코드 클래스에서는 필드를 간단하게 선언하는 것으로 생성자와 getter, equals, hashCode, toString 메서드를 모두 자동으로 생성된다.
기본 사용
레코드 클래스는 기존 클래스와 동일하게 커스텀 메서드, 생성자 검증, 정적 변수 및 메서드 등을 작성하여 활용할 수 있다.
생성자 검증
public record Person(
String name,
int age
) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("나이는 0보다 작을 수 없습니다.");
}
}
public static void main(String[] args) {
Person person = new Person("홍길동", -1); // 에러 발생
System.out.println("person = " + person);
}
}
정적 변수와 정적 메서드
public record Person(
String name,
int age
) {
public static final int MIN_AGE = 0;
public static final String DEFAULT_NAME = "홍길동";
public static int getMinAge() {
return MIN_AGE;
}
public static String getDefaultName() {
return DEFAULT_NAME;
}
public static void main(String[] args) {
System.out.println(Person.getMinAge()); // 0
System.out.println(Person.getDefaultName()); // 홍길동
}
}
커스텀 메서드
public record Person(
String name,
int age
) {
public static final int ADULT_AGE = 18;
public boolean isAdult() {
return this.age >= ADULT_AGE;
}
public static void main(String[] args) {
Person person = new Person("홍길동", 18);
System.out.println(person.isAdult()); // true
}
}
- 기존 클래스의 보일러플레이트 코드를 줄여 더욱 간결하고 가독성 높은 코드를 작성할 수 있다.
- 불변 객체로 유지보수성이 향상될 수 있다.
활용
레코드 클래스는 DTO 클래스로 활용하기 좋다. 레코드 클래스에 스프링 프레임워크에서 제공하는 Bean Validation을 사용하여 제약조건을 쉽게 검증할 수 있다.
의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
Bean Validation 사용
public record Person(
@NotBlank
@Size(min = 2, max = 20, message = "이름은 최소2자 최대20자입니다.")
String name,
@NotBlank
@Max(value = 150, message = "나이는 150세 이하여야 합니다.")
@Min(value = 0, message = "나이는 0세 이상이어야 합니다.")
int age,
@NotNull
LocalDate birthDate,
@Email(message = "이메일 형식이어야 합니다.")
String email
) {
}
컨트롤러 작성
@RestController
@Validated
public class PersonController {
@PostMapping("/person")
public ResponseEntity<PersonResponse> createPerson(@Valid @RequestBody PersonRequest personRequest) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(new PersonResponse("홍길동", 20, LocalDate.now(), "gildong@example.com"));
}
}
코틀린 Data Class와 자바 레코드 비교
자바의 Record는 코틀린 data class와 같이 데이터 중심의 클래스를 정의하는 데 중점을 둔다. 또한, 공통적으로 간결한 문법과 자동으로 생성되는 메서드를 통해 보일러플레이트를 줄이는 역할을 한다. 하지만, 이 둘에는 약간의 차이점이 존재한다.
자바 레코드
public record Person(
String name,
int age,
LocalDate birthDate,
String email
) {
public static final int MINIMUM_AGE = 0;
public Person {
if (age < 0) {
throw new IllegalArgumentException("나이는 0보다 작을 수 없습니다.");
}
}
public static int getMinimumAge() {
return MINIMUM_AGE;
}
public boolean isAdult() {
return age >= 18;
}
}
코틀린 데이터 클래스
data class Person(
var name: String,
var age: Int,
val birthDate: LocalDate,
val email: String
) {
init {
require(age >= 0) { "나이는 0보다 작을 수 없습니다." }
}
companion object {
const val MINIMUM_AGE = 0
fun getMinimumAge() = MINIMUM_AGE
}
fun isAdult() = age >= 18
}
차이점
- 자바의 레코드 필드는 기본적으로 final이므로 불변성을 보장하지만, 코틀린의 데이터 클래스는 val 키워드를 통해 불변성을 보장한다. var 키워드를 사용하면 가변 필드가 된다.
- 레코드와 데이터 클래스 둘 다 toString(), equals(), hashCode()를 기본으로 자동 생성하지만, 코틀린의 데이터 클래스는 추가적으로 copy(), componentN() 메서드를 생성해 준다.
- 자바 레코드는 정적 메서드와 필드를 직접 추가할 수 있지만, 코틀린 데이터 클래스는 companion object를 통해 정적 멤버를 정의할 수 있다.