자바의 Record

최창효·2024년 3월 3일
post-thumbnail

Record란

  • record타입은 JDK14에서 처음 등장했고, JDK16때 정식으로 포함되었습니다.
  • Record객체를 사용하면 쉽고 간편하게 '불변 객체'를 생성할 수 있습니다.
  • Record는 필드의 타입과 이름만 선언하면 equals메서드, hashCode메서드, toString메서드, 접근자 메서드, 생성자를 자동으로 만들어 줍니다.

예시

기본 사용법

Record 예시

public record MyRecord(String name, int age) {
}

인텔리제이에는 record타입을 일반 클래스로 변경해주는 기능을 지원합니다. (인텔리제이의 버전에 따라 다를 수 있을거 같습니다. 또한 얼티밋이 아닌 커뮤니티 버전에서도 사용 가능한지는 잘 모르겠습니다.)

record타입의 MyRecord를 클래스로 변환하면 다음과 같습니다.

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

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

    public String name() {
        return name;
    }

    public int age() {
        return age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (MyRecord) obj;
        return Objects.equals(this.name, that.name) &&
                this.age == that.age;
    }

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

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


}
  • 그냥 클래스가 아니라 final class입니다. 다른 클래스는 final class를 상속받을 수 없습니다.
  • 접근자 메서드의 모양이 getter와는 조금 다릅니다. getName()과 getAge()가 아니라 name(), age()로 구현되어 있습니다.

살펴보기

record에 조금 더 코드를 추가한 뒤 클래스로 변경해 차이점을 살펴보겠습니다.

record타입

public record MyRecord(String name, int age) {

    private final static int ZERO = 0;
//    private final int ONE; // 불가능

    public MyRecord {
        if(age<0) {
            throw new RuntimeException("cannot be negative");
        }
    }

    // 생성자 오버로딩
    public MyRecord(String name) {
        this(name,0);
    }

    public static void func1() {
        System.out.println("hello");
    }

    public void func() {
        System.out.println("world");
    }

}
  • 인스턴스 변수는 body에서 선언이 불가능합니다. 만약 private final int One;을 넣고 싶다면 name과 age처럼 괄호 안에서 정의해야 합니다.
  • 반면 클래스 변수는 body안에서 선언이 가능합니다.
  • 모든 매개변수를 포함하는 생성자를 선언할 때 ()를 붙이지 않습니다. 이를 Compact Constructor라고 부릅니다.
    • Compact Constructor는 validation의 용도로 주로 사용됩니다.
  • 특정 매개변수만을 활용하는 생성자를 만들 수 있습니다.
  • 메서드, 정적 메서드를 자유롭게 정의할 수 있습니다.

클래스

변환된 클래스를 살펴보면 이해가 한층 더 쉽습니다.

public final class MyRecord {

    private final static int ZERO = 0;
    private final String name;
    private final int age;


    public MyRecord(String name, int age) {
        if (age < 0) {
            throw new RuntimeException("cannot be negative");
        }
        this.name = name;
        this.age = age;
    }

    // 생성자 오버로딩
    public MyRecord(String name) {
        this(name, 0);
    }

    public static void func1() {
        System.out.println("hello");
    }

    public void func() {
        System.out.println("world");
    }

    public String name() {
        return name;
    }

    public int age() {
        return age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (MyRecord) obj;
        return Objects.equals(this.name, that.name) &&
                this.age == that.age;
    }

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

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


}

느낀점

  • 단순한 형태이며 값이 변하지 않는 VO 또는 DTO를 생성할 때 활용할 수 있을거 같다.
  • 문법이 엄청 코틀린스럽다.

References

profile
기록하고 정리하는 걸 좋아하는 백엔드 개발자입니다.

0개의 댓글