[JAVA] record

Java 14부터 도입된 새로운 클래스 유형인 record에 대해 이야기해보자.


record란? (Java 14+)

recordJava 14부터 도입된 새로운 클래스 유형으로, 불변(immutable)한 데이터 객체를 간결하게 표현할 수 있도록 설계되었습니다.

👉 기존의 DTO(Data Transfer Object), VO(Value Object) 등을 만들 때 코드를 대폭 줄여줍니다.


1️⃣ record의 특징

  1. 자동으로 생성자, getter, toString(), equals(), hashCode() 제공
  2. 불변 객체 (Immutable)
  3. Java의 일반적인 클래스처럼 사용 가능
  4. Compact Constructor(압축된 생성자) 지원
  5. 상속 불가능 (final 클래스처럼 동작)

2️⃣ 기존 클래스 vs record 비교

🔹 기존 방식: Java 클래스로 DTO 만들기

class Person {
    private final String name;
    private final int age;

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

    public String getName() { return name; }
    public int getAge() { return age; }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
  • getter, toString(), equals(), hashCode()를 직접 구현해야 해서 코드가 길어짐.

🔹 record를 사용한 간단한 구현

record Person(String name, int age) {}
  • 위처럼 한 줄만 작성하면 동일한 기능을 제공함!
  • 자동으로 getter, toString(), equals(), hashCode()가 생성됨.

3️⃣ record의 내부 동작

위의 record Person(String name, int age)자동으로 다음과 같이 동작합니다:

public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) { // 생성자 자동 생성
        this.name = name;
        this.age = age;
    }

    public String name() { return name; } // getter 자동 생성
    public int age() { return age; }

    @Override
    public boolean equals(Object o) { /* 자동 생성 */ }

    @Override
    public int hashCode() { /* 자동 생성 */ }

    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
}

💡 이름이 getName()이 아니라 name()인 점이 특징!


4️⃣ record의 주요 기능

✅ 1. 기본 사용

record Person(String name, int age) {}

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Alice", 25);
        System.out.println(p.name());  // Alice (getter)
        System.out.println(p.age());   // 25 (getter)
        System.out.println(p);         // Person[name=Alice, age=25]
    }
}
  • name()age()getter 역할을 함 (getName()이 아님)
  • toString()이 자동 생성됨 → Person[name=Alice, age=25]

✅ 2. Compact Constructor (압축된 생성자)

  • 기본적으로 record모든 필드가 자동 초기화되지만, 추가 검증 로직을 넣을 수도 있음.
record Person(String name, int age) {
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
        }
    }
}
  • 위처럼 생성자에서 유효성 검사를 추가 가능.

✅ 3. 메서드 추가 가능

  • record도 일반적인 클래스처럼 메서드를 추가할 수 있음.
record Circle(double radius) {
    public double area() {
        return Math.PI * radius * radius;
    }
}

public class Main {
    public static void main(String[] args) {
        Circle c = new Circle(5);
        System.out.println("원의 넓이: " + c.area());  // 원의 넓이: 78.5398...
    }
}
  • record는 단순히 데이터를 저장하는 역할이지만, 관련 메서드를 추가하는 것도 가능.

✅ 4. 상속 불가능 (final 클래스처럼 동작)

  • record는 자동으로 final로 선언되므로 상속이 불가능함.
record Parent(String name) {}

// 오류: record는 상속할 수 없음
class Child extends Parent {}  // ❌ 컴파일 오류

왜 Java는 record를 final로 만든걸까?

  1. 불변성(Immutable)을 유지하려고
    • record는 모든 필드가 final이고, 생성 이후 변경이 불가능함
    • 누가 상속해서 setter를 추가하거나, 내부 상태를 바꾸면
      • record의 의도가 무너짐!
  2. 자동 생성된 메서드의 안정성 보장
    • equals(), hashCode(), toString()등을 자동 생성해주는데, 누가 상속해서 equals()만 살짝 바꾸면 일관성이 깨짐 -> 버그의 근원
  3. Java에서 상속은 “행위” 확장에 적합
    • 하지만 record는 “데이터”만 표현하고 싶을때 쓰는 구조
    • 즉, 철학이 다르다.
      • recordPOJO(Plain Old Java Object)보다 더 “데이터 그 자체”에 집중

🔴 하지만 인터페이스는 구현 가능

interface Printable {
    void print();
}

public record User(String name, int age) implements Printable {
    public void print() {
        System.out.println("User: " + name + ", " + age);
    }
}
  • 행위를 정의하는 인터페이스는 데이터 구조와 상관없기 때문에 구현할 수 있다!

5️⃣ record를 언제 사용할까?

불변 객체(Immutable Object)가 필요할 때
DTO, VO (Data Transfer Object, Value Object)를 만들 때
간단한 데이터 저장용 클래스가 필요할 때
불필요한 getter, toString(), equals() 코드 작성을 줄이고 싶을 때

상속을 해야 하는 경우에는 record를 사용할 수 없음
데이터 변경이 필요한 경우 (Mutable Object)는 record보다 일반 클래스를 사용


6️⃣ 정리

특징일반 클래스record
코드 길이길다 (필드, 생성자, getter, toString(), equals(), hashCode())짧다 (한 줄로 가능)
불변성(Immutable)X (final 필드 필요)✅ 기본적으로 불변
자동 생성X (수동으로 작성해야 함)✅ 생성자, getter, toString() 자동 생성
상속 가능 여부✅ 가능❌ 불가능 (final 클래스처럼 동작)
데이터 변경가능 (setter 추가 가능)❌ 변경 불가능

🚀 결론: record를 사용하면 불변 객체를 쉽게 만들 수 있으며, DTO나 VO 같은 데이터 클래스를 훨씬 간결하게 표현할 수 있다! 🚀