장쫄깃 기술블로그

[Java] Record란? 본문

Programming Language/Java

[Java] Record란?

장쫄깃 2024. 7. 26. 20:45
728x90


Record란?


record는 자바 14에서 처음 소개된 이후 16에서 정식으로 채용된 새로운 클래스 타입이다. 기존의 클래스와 비슷하지만, 더 간결하고 효율적으로 데이터 객체를 생성할 수 있도록 설계되었다.

 

특히, record는 보일러 플레이트 코드가 가지는 단점을 극복하기 위한 자바의 기능 중 하나이다.

보일러 플레이트 코드 (Boiler Plate)
여러 곳에서 재사용되며, 반복적으로 비슷한 형태를 띠는 코드
ex) getter, setter, toString, equals, hashCode 등

 

보일러 플레이트 코드를 lombok이나 IDE의 도움을 받아 해결할 수 있지만, 근본적인 해결책은 되지 못한다. 이러한 한계를 극복하기 위해 자바가 추가한 기능 중 하나가 바로 record이다.

 


Record의 특징


record의 특징은 다음과 같다.

 

  • 데이터를 간결하게 표현

객체 지향에 맞게 데이터를 간결하게 표현할 수 있다.

 

 

  • 데이터의 불변성 보장

record 헤더에 나열한 데이터는 private final로 정의된다. 때문에, 한 번 값이 정해지면 setter를 통해 데이터를 변경할 수 없다.

또, record는 불변 객체이기 때문에 abstract로 선언할 수 없다. 때문에, 상속을 통해 데이터를 변경할 수 없다.

참고로 record 내부 변수는 static 변수만 생성이 가능하다. 이는 헤더에서 정의한 데이터만을 record가 관리하기 위함이다.

 

 

  • equals, hashCode, toString 메소드 자동 생성

record는 필드를 기반으로 equals(), hashCode(), toString() 메소드를 자동으로 생성한다. 때문에, 불필요한 반복적인 코드나 어노테이션을 피할 수 있다.

참고로 record 내부 메소드는 static 메소드만 생성이 가능하다.

 

 

  • 데이터 필드 기반 생성자 자동 생성

record는 헤더에 나열한 데이터를 기반으로 생성자를 자동으로 생성한다. 필요에 따라 컴팩트 생성자를 정의할 수도 있다.

record는 생성자를 만들어주기 때문에 lombok의 @Builder 어노테이션을 사용하면 빌더 패턴 역시 사용이 가능하다.

 


Record의 구조


record를 사용하지 않은 객체는 다음과 같다.

public class User {

    private String name;

    private String tel;

    public User() {
    }

    public User(String name, String tel) {
        this.name = name;
        this.tel = tel;
    }

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

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

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTel() {
        return tel;
    }

    public void setTel(String tel) {
        this.tel = tel;
    }
}

 

이 객체를 record를 사용하면 다음과 같다.

public record User(String name, String tel) {}

 

class 선언 시 들어가는 class 대신 record를 사용한다. 여기서 record의 헤더에 나열되는 필드를 컴포넌트라고 부른다.

접근자나 생성자, toString, equals, hashCode를 자동으로 생성해 준다.

 

해당 record는 다음과 같이 사용할 수 있다.

User user = new User("장쫄깃", "010-1234-1234");

System.out.println(user.name());
System.out.println(user.tel());

 

 

혹은 Builder 패턴을 사용할 수도 있다.

@Builder
public record User(String name, String tel) {}
User user = User.builder()
        .name("장쫄깃")
        .tel("010-1234-1234")
        .build();
        
System.out.println(user.name());
System.out.println(user.tel());

 


컴팩트 생성자


만약 record에서 별도의 생성자가 필요하다면 표준 생성자를 만들 수도 있다.

public record User(String name, String tel) {

    public User(String name, String tel) {
        this.name = name;
        this.tel = tel;
    }
    
}

 

여기서 이러한 표준 생성자 말고도 컴팩트 생성자를 사용할 수 있다.

public record User(String name, String tel) {

    public User {
        Objects.requireNonNull(name);
        Objects.requireNonNull(tel);
    }
    
}

 

컴팩트 생성자는 생성자 매개변수를 받는 부분이 사라진 형태이다. 개발자가 인스턴스 필드를 명시적으로 초기화하지 않아도 컴팩트 생성자의 마지막 부분에 초기화 구문이 자동으로 삽입된다.

 

컴팩트 생성자 내부에서는 인스턴스 필드에 접근할 수 없다. 때문에, 컴포넌트로 들어온 값을 불변으로 만들거나 불변식을 만족하는지 검사(유효성 체크, null 체크 등) 등의 작업을 하기에 적합하다.

 

이렇게 선언한 컴팩트 생성자는 일반 생성자처럼 사용하면 된다.

public record User(String name, String tel) {}

 

 

728x90