본문 바로가기
Language/Java

[JAVA] Java 14/16의 Record 키워드 : 데이터 클래스의 혁명

by Papa Martino V 2026. 1. 23.
728x90

Java 14/16의 Record 키워드
Java 14/16의 Record 키워드

 

자바 개발자라면 누구나 한 번쯤은 단순한 데이터 저장을 위한 클래스를 작성하면서 반복적이고 지루한 코드에 지쳤던 경험이 있을 것입니다. 필드를 선언하고, 생성자를 만들고, 모든 필드에 대한 Getter 메서드를 정의하고, equals(), hashCode(), toString() 메서드까지 수동으로 구현하거나 IDE의 도움을 받아야 했습니다. 이러한 상용구(boilerplate) 코드는 코드의 가독성을 저해하고 유지보수를 어렵게 만드는 주범이었습니다. 하지만 Java 14에서 미리보기(preview) 기능으로 도입되어 Java 16에서 정식 기능으로 확정된 record 키워드는 이러한 문제에 대한 자바 플랫폼의 우아하고 강력한 해답을 제시합니다. record는 불변(immutable) 데이터를 간결하게 정의할 수 있도록 설계된 새로운 종류의 클래스입니다. 본 포스팅에서는 record 키워드가 무엇이며, 어떻게 자바 개발을 혁신하는지 심층적으로 다룹니다.


1. 왜 Record가 필요한가? - 상용구 코드의 고통

기존 자바에서 데이터 운반 객체(DTO, Data Transfer Object)를 정의할 때 발생했던 전형적인 문제를 살펴보겠습니다.


// 기존 방식의 User 클래스
public class User {
    private final String name;
    private final String email;
    private final int age;

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

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && name.equals(user.name) && email.equals(user.email);
    }

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

    @Override
    public String toString() {
        return "User{" +
               "name='" + name + '\'' +
               ", email='" + email + '\'' +
               ", age=" + age +
               '}';
    }
}

단순히 세 개의 필드를 가진 클래스임에도 불구하고 약 40줄에 달하는 코드가 필요합니다. 이는 명백히 데이터의 본질적인 목적을 흐리게 하는 불필요한 복잡성입니다. record는 이러한 문제를 해결하기 위해 등장했습니다.


2. Record의 등장: 간결함의 미학 (JEP 359, 384, 395)

record 키워드를 사용하면 위 User 클래스를 단 한 줄로 표현할 수 있습니다.


// Record를 사용한 User 클래스 (Java 16 이상)
public record User(String name, String email, int age) {}

놀랍도록 간결합니다! 이 한 줄의 코드는 컴파일 시 다음과 같은 요소들을 자동으로 생성해줍니다.

  • 모든 컴포넌트를 매개변수로 받는 정규 생성자(canonical constructor)
  • 각 컴포넌트에 대한 접근자 메서드(accessor method) (예: name(), email(), age())
  • 모든 컴포넌트를 고려한 equals()hashCode() 메서드 구현
  • 모든 컴포넌트의 값을 포함하는 toString() 메서드 구현
  • 불변(immutable) 클래스: 모든 필드는 final로 선언됩니다.

[Sample Example: Record 활용]


public class RecordExample {
    // 레코드 선언
    public record Product(String name, double price, int stock) {
        // 압축 생성자 (Compact Constructor) 예시
        // 유효성 검사 등 추가 로직을 넣을 수 있으나, 매개변수 목록을 생략한다.
        public Product {
            if (price < 0) {
                throw new IllegalArgumentException("가격은 0 이상이어야 합니다.");
            }
            if (stock < 0) {
                throw new IllegalArgumentException("재고는 0 이상이어야 합니다.");
            }
        }

        // 추가 메서드 정의 가능
        public double calculateTotalPrice() {
            return price * stock;
        }
    }

    public static void main(String[] args) {
        // 레코드 인스턴스 생성
        Product laptop = new Product("노트북", 1200.50, 10);

        // 자동 생성된 접근자 메서드 사용
        System.out.println("제품 이름: " + laptop.name());
        System.out.println("가격: " + laptop.price());
        System.out.println("재고: " + laptop.stock());

        // 자동 생성된 toString() 사용
        System.out.println(laptop); // Product[name=노트북, price=1200.5, stock=10]

        // 자동 생성된 equals() 및 hashCode() 사용
        Product anotherLaptop = new Product("노트북", 1200.50, 10);
        System.out.println("두 제품이 동일한가? " + laptop.equals(anotherLaptop)); // true

        // 추가 정의한 메서드 사용
        System.out.println("총 재고 가치: " + laptop.calculateTotalPrice());

        // 불변성 확인 (필드 직접 변경 불가)
        // laptop.price = 1300.0; // 컴파일 오류 발생
    }
}

3. Record의 특징과 제약사항

특징 설명 비고
불변성(Immutability) 모든 컴포넌트는 final로 선언되어 초기화 후 변경 불가 안정적인 데이터 관리, 동시성 문제 감소
상속 불가 final 클래스로 컴파일되어 다른 클래스를 상속할 수 없음 상속 계층 구조 복잡성 방지, 단순 데이터 모델 지향
인터페이스 구현 인터페이스는 구현 가능 다형성(Polymorphism) 활용 가능
필드 선언 제한 명시적으로 인스턴스 필드를 선언할 수 없음 (컴포넌트 외) 데이터의 본질에 집중, 복잡한 상태 방지
정적 필드 및 메서드 정적 필드 및 메서드는 정의 가능 유틸리티 메서드나 상수 정의에 활용
압축 생성자(Compact Constructor) 매개변수 없이 유효성 검사 등 로직 추가 가능 코드 간결성 유지하며 초기화 로직 삽입

4. Record의 활용 시나리오

record는 다음과 같은 상황에서 매우 유용하게 사용될 수 있습니다.

  • DTO(Data Transfer Object): API 응답, 메시지 큐 데이터 등 단순 데이터 전달 객체.
  • Configuration Class: 애플리케이션의 설정값을 담는 불변 객체.
  • Multi-Value Return: 메서드에서 여러 값을 반환해야 할 때 (예: 좌표, 최소/최대값).
  • Tuple-like Structures: 임시적으로 데이터를 묶어 전달할 때.
  • 스트림 API 중간 처리: 스트림 파이프라인에서 데이터를 가공하는 중간 단계의 객체.

5. 결론 및 미래 전망

record 키워드는 자바 언어의 진화를 명확하게 보여주는 사례입니다. 상용구 코드를 줄여 개발자의 생산성을 향상시키고, 코드의 가독성을 높이며, 불변성을 기본으로 하여 더 안전한 애플리케이션을 구축할 수 있도록 돕습니다. 이는 특히 함수형 프로그래밍 스타일과의 결합에서 시너지를 발휘하며, 더 적은 코드로 더 많은 기능을 구현하는 현대 자바 개발의 방향성을 제시합니다. 자바 16 이상 버전을 사용하는 개발자라면 record를 적극적으로 활용하여 데이터 클래스 정의의 패러다임을 전환할 필요가 있습니다. 이는 궁극적으로 시스템의 안정성과 유지보수성을 크게 향상시킬 것입니다.

 

출처:

  • OpenJDK Project - JEP 395: Records
  • Java Language Specification, Java SE 16 Edition
  • Oracle Java Documentation - Records
728x90