[Java] Record란?
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) {}