Post

[Java] 레코드(Record) Java 16+

인프런 얄코의 제대로 파는 자바 강의를 듣고 정리한 글입니다.

레코드란?


record불변 데이터를 표현할 때 쓰는 간결한 클래스 정의 방법이다.
기존에는 값을 담는 용도로 field, constructor, getter, equals(), hashCode(), toString() 등을 일일이 작성해야 했는데,
record 에서는 이것을 자동으로 생성해준다.

사용에 주의할 점

  • 불변 객체에 적합: DTO, VO, 컬렉션의 key(Map, Set) 등
  • 로직 중심 클래스에는 적합하지 않음

기본문법


1
public record Person(String name, int age) {}

위 코드처럼 class 대신 record 로 작성하게 되면,
다음과 같은 클래스를 자동으로 생성해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class Person {
    // Field
    private final String name;
    private final int age;

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

    // Getter
    public String name() { return name; }
    public int age() { return age; }

    // Override
    public boolean equals(Object o) { /* ... */ }
    public int hashCode() { /* ... */ }
    public String toString() { /* ... */ }
}
  • recordfinal 이기 때문에 상속이 불가능하다.
  • abstract 로 선언할 수 없다.
  • 필드들은 private final 로 설정된다.
  • 모든 필드를 받는 생성자가 만들어진다.
  • 💡 필드명과 동일한 getter 메서드가 생성된다.
  • 불변성: 필드가 final 이므로 객체 생성 후 값을 변경할 수 없다.
  • 인스턴스 필드를 가질 수 없고, 오직 클래스 필드만 가질 수 있다.

예제1️⃣


Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public record Person(
    String name,
    int age
) {
    // 💥 사용 불가
    // public String a = "aaa";
    // private String b = "bbb";

    public static String a = "aaa";
    private static String b = "bbb";

    // 이 경우엔 Getter 메소드를 직접 만들어야한다.
    public String a() {
        return a;
    }

    public String b() {
        return b;
    }

    // ⭐️ 추가 기능: 생성자 정의
    public Person {
        // 유효성 검사
        if (age < 0) throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
    }

    // ⭐️ 추가 기능: 메서드 정의
    public String greeting() {
        return "안녕하세요 " + name + "입니다.";
    }
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Person person = new Person("홍길동", 30);
// Person 아무개 = new Person("아무개", -1); // 💥 예외 발생

String personName = person.name(); // 홍길동
int personAge = person.age(); // 30

String personA = Person.a; // "aaa"
String personGetA = person.a(); // "aaa"
// String personB = Person.b; // ❌ 불가
String personGetB = person.b(); // "bbb"

String personToString = person.toString(); // "Person[name=홍길동, age=30]"

String personGreeting = person.greeting(); // "안녕하세요 홍길동입니다."

예제2️⃣


Gender.java

1
2
3
4
5
6
7
public enum Gender {
    MALE("👦🏻"), FEMALE("👧🏼");

    private String emoji;
    Gender(String emoji) { this.emoji = emoji; }
    public String getEmoji() { return emoji; }
}

Child.java

1
2
3
4
5
6
// ⭐️  레코드로 작성
public record Child(
        String name,
        int birthYear,
        Gender gender
) {}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Child child1 = new Child("홍길동", 2020, Gender.MALE);
//  💡 toString 메소드 구현 (Object에서 상속받아 Override)
String childStr = child1.toString(); // "Child[name=홍길동, birthYear=2020, gender=MALE]"

Child[] children = new Child[] {
        new Child("김순이", 2021, Gender.FEMALE),
        new Child("이돌이", 2019, Gender.MALE),
        new Child("박철수", 2020, Gender.MALE),
        new Child("최영희", 2019, Gender.FEMALE),
};

for (Child child : children) {
    System.out.printf(
            "%s %d년생 %s 어린이%n",
            child.gender().getEmoji(),
            child.birthYear(),
            child.name()
    );
}

record

예제3️⃣


InfoPrinter.java

1
2
3
public interface InfoPrinter {
    void printInfo();
}

Button.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Button {
    public enum ClickedBy {
        LEFT('좌'), RIGHT('우');
        
        private char indicator;

        ClickedBy(char indicator) {
            this.indicator = indicator;
        }

        public char getIndicator() {
            return indicator;
        }
    }

    // 다른 클래스에 내부로 포함 가능
    // 인터페이스 구현 가능 (클래스 상속은 불가)
    public record ClickInfo(
        int x,
        int y,
        ClickedBy clickedBy
    ) implements InfoPrinter {
        // 💡 클래스 필드를 가질 수 있음 (인스턴스 필드는 불가)
        static String desc = "버튼 클릭 정보";

        // 💡 인스턴스/클래스 메소드를 가질 수 있음
        @Override
        public void printInfo() {
            System.out.printf(
                "%c클릭 (%d, %d)%n",
                clickedBy.indicator, x, y
            );
        }
    }

    public ClickInfo func(int x, int y, ClickedBy clickedBy) {
        System.out.println("버튼 동작");
        return new ClickInfo(x, y, clickedBy);
    }
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Button button = new Button();

Button.ClickInfo click1 = button.func(123, 456, Button.ClickedBy.LEFT);
Button.ClickInfo click2 = button.func(492, 97, Button.ClickedBy.LEFT);
Button.ClickInfo click3 = button.func(12, 36, Button.ClickedBy.RIGHT);

for (Button.ClickInfo click : new Button.ClickInfo[] { click1, click2, click3 }) {
    click.printInfo();
}

System.out.println("\n- - - - -\n");

Button.ClickInfo click4 = button.func(111, 222, Button.ClickedBy.LEFT);
Button.ClickInfo click5 = button.func(111, 222, Button.ClickedBy.LEFT);

// ⭐️ 레코드 역시 참조형
// 내용이 같은지 여부는 equals 메소드로 확인
boolean click4n5Same = click4 == click5; // false
boolean click4n5Equal = click4.equals(click5); // true
boolean click4n1Equal = click4.equals(click1); // false

📌 추가사항

  • 클래스 내부에 정의된 record는 내부 정적 클래스처럼 사용할 수 있다.
1
Button.ClickInfo click6 = new Button.ClickInfo(111, 222, Button.ClickBy.LEFT);