Record
Intro.
자바 14 이전에는 단순히 데이터를 저장시키고 조회하는 클래스를 만들려면 필드와 메서드가 포함된 클래스를 만들어야 했고 이는 개발자의 잦은 실수와 반복되는 작업을 일으켰다. 자바 14부터 record를 사용하여 이러한 문제를 해결할 수 있다. record는 enum처럼 제약을 가진 클래스의 일종으로 간결함을 얻기 위한 용도가 있다.
목적
일반적으로 데이터베이스의 결과, 쿼리 결과, 서비스 정보 같은 단순한 데이터를 가진 클래스를 작성한다. 많은 경우 데이터를 불변하게 생성하는데 이 과정에서 private, final, getter, constructor, equals and hashcode, toString을 작성하게 된다.
이것은 많은 getter 메서드를 필요로 하고 클래스의 목적을 모호하게 만든다. IDE는 많은 클래스를 자동으로 생성할 수 있지만 새 필드를 추가하게 되면 equals 메서드를 수정해야 한다. 더 좋은 방식은 우리의 클래스가 데이터 클래스임을 명시적으로 선언하는 것이다.
사용 방법
class 대신 record를 작성하여 생성한다. 레코드는 필드의 유형과 이름만 필요한 변경 불가능한 데이터 클래스이다.
equals and hashcode, toString, constructor, private 은 컴파일러에 의해 생성된다.
public record User(String name, String email) {
}
위 코드를 바이트코드로 확인해보자.
// class version 59.65535 (-65477)
// RECORD
// access flags 0x10031
public final class test/User extends java/lang/Record {
// compiled from: User.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
RECORDCOMPONENT Ljava/lang/String; name
RECORDCOMPONENT Ljava/lang/String; email
// access flags 0x12
private final Ljava/lang/String; name // private final
// access flags 0x12
private final Ljava/lang/String; email // private final
// access flags 0x1
public <init>(Ljava/lang/String;Ljava/lang/String;)V // 생성자
// parameter name
// parameter email
L0
...
// access flags 0x11
public final toString()Ljava/lang/String; // toString()
L0
...
// access flags 0x11
public final hashCode() // hashCode()
L0
...
// access flags 0x11
public final equals(Ljava/lang/Object;)Z // equals()
L0
...
// access flags 0x1
public name()Ljava/lang/String; // getter()
L0
...
// access flags 0x1
public email()Ljava/lang/String; // getter()
L0
...
}
컴파일러에 의해 private, final, getter, constructor, equals and hashcode, toString 모두 생성되는 것을 확인하였다.
테스트 코드
class UserTest {
String name = "yhh";
String email = "yhh@email.com";
User user = new User(name, email);
@DisplayName("getter")
@Test
void record_getter() {
assertEquals(name, user.name());
assertEquals(email, user.email());
}
@DisplayName("equals")
@Test
void record_equals() {
assertTrue(user.name().equals(name));
assertTrue(user.email().equals(email));
}
@DisplayName("hashcode")
@Test
void record_hashcode() {
User user2 = new User(name, email);
assertEquals(user.hashCode(), user2.hashCode());
}
@DisplayName("toString")
@Test
void record_toString() {
assertEquals("User[name=yhh, email=yhh@email.com]", user.toString());
}
}
커스터마이징
생성자
아래와 같이 생성자를 제한할 수 있다. 하지만 email이 final로 선언되어있기 때문에 한 번 생성되면 변경할 수 없다.
public record User(String name, String email) {
public User(String name) {
this(name, "Unknown");
}
}
record도 생성자에 Objects.requireNonNull(name) 같은 유효성 검사를 할 수 있다.
public record User(String name, String email) {
public User {
Objects.requireNonNull(name);
Objects.requireNonNull(email);
}
}
인수가 없는 생성자를 생성하고 인수 목록이 있는 생성자를 선언하면 컴파일 에러가 발생한다.
public record User(String name, String email) {
public User {
Objects.requireNonNull(name);
Objects.requireNonNull(email);
}
public User(String name, String email) { // 컴파일 에러
this.name = name;
this.email = email;
}
}
정적 변수 및 메서드
일반 클래스와 마찬가지로 정적 변수와 메서드를 포함할 수 있다.
public record User(String name, String email) {
public static final String HELLO = "hello"; // 정적 변수
private String noop; // 컴파일 에러
public String greeting() { // 메소드
return "안녕";
}
}
'개발 > Java' 카테고리의 다른 글
[Java] 제네릭 (2) | 2022.03.02 |
---|---|
[JUnit] @ParameterizedTest - @MethodSource 사용하기 (0) | 2022.02.27 |
[Java] package, import (2) | 2022.02.15 |
[JUnit] Parameterized Test - 테스트를 효율적으로 (1) | 2022.02.11 |
[Java] 리플렉션 (reflection) 개념 이해하기 (0) | 2022.01.29 |