Java: Record

Buddy·2023년 9월 10일
0

baeldung.com: java-record-keyword

Java 14부터 포함된 불변 객체를 만들기 위한 방법이다.
class를 사용해서 만드는 방법보다 훨씬 간단하다.
Spring으로 개발을 하다보면 DTO class를 만드는 경우가 많은데 이 때 record를 사용해서 만들면 훨씬 쉽고 안전하게 만드는 것이 가능하다. 불변객체를 만들려는 의도를 확실하게 보여줄 수 있는 것도 덤으로 느껴진다.

User class

public class User {
	private final long id;
    private final String name;
    private final int age;
    
    public long getId() {
    	return this.id;
    }
    
    public String getName() {
		return this.name;
	}
    
    public int getAge() {
    	return this.age;
    }
    
    @Override
    public int hashCode() {
	    ...
    }
    
        @Override
    public boolean equals(Object o) {
	    ...
    }
    
        @Override
    public String toString() {
	    ...
    }
}

위의 구현은 너무 길다. IDE가 equals, hashCode, toString, getter/setter 생성하는 기능을 지원하고 있기는 해서 코드를 짜는데 문제가 있지는 않지만, 자세히 보기 전에는 복잡한 구성으로 느낄 수 있겠다.

lombok 어노테이션을 사용하면 위 코드를 아래처럼 줄일 수는 있다.

@Value
public class User {
	long id;
    String name;
    int age;
}

@Value 어노테이션 하나면 equals, hashCode, toString, getter 메서드를 전부 정의할 수 있다. @Data 어노테이션을 사용하는 경우 setter 까지 추가된다. @Value, @Data 어노테이션은 변경이 가능한가, 불가능한가 따라서 사용하면 되는 것인데 GitHub이나 회사코드를 보면 변경이 불가능한 불변 객체로 만들어야 하는 경우에도 @Data 어노테이션을 사용한 경우가 종종 있더라. (예전에 짠 내 코드에도 있었다..) 물론 정의 해놓고 안쓰면 그만이긴 한데, 변경이 막혀있는게 의도를 전달하기가 좋으니까..

User record

아래는 User를 record로 만들었다. 당황스러울 정도로 코드가 짧아졌다.
lombok의 @Value 어노테이션을 붙인 User class와 마찬가지로 equals, hashCode, toString, getter가 전부 생성된 것과 동일하다.

getter가 기존에 사용하던 getId(), getName(), getAge() 와 같이 생성되지 않고 id(), name(), age() 같이 get 없이 필드명으로만 생성되는 점은 조금 다르다. get 키워드가 붙지 않는 덕에 get이 붙는 경우 set도 있나? 라고 생각할 수 있는 것이 방지되는 도움도 나름 있는 것 같다. (억지로 찾은 장점일수도...)

public record User(long id, String name, int age) {}

역직렬화 시 문제점

그래서 이렇게 간단하고 좋은 record를 잘 써먹어보고 싶었는데 실제 적용하려니 역직렬화에서 에러가 발생했었다. Spring Boot에서는 jackson 라이브러리를 사용해 직렬화/역직렬화를 수행하는 것이 기본적이고 이는 기본 생성자로 객체를 생성한 후 getter/setter로 해당 클래스의 필드들을 파악해 reflection으로 값을 넣어주는데 여기에서 문제가 생기는 것 같았다.

뭐.. 그래서 해결 방법은

public record User(long id, String name, int age) {

    @JsonCreator
    public User(
        @JsonProperty("id") long id,
        @JsonProperty("name") String name,
        @JsonProperty("age") int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

이렇게 어떤 생성자 써먹을건지 property 이름은 무엇인지 @JsonCreator, @JsonProperty 어노테이션을 써서 설명해주면 된다.

이 문제가 생겨서 해결 방법을 적용하기 전에는 record 구성이 굉장히 간단하고 좋다고 생각했었는데..
이제는 별로 아닌 것 같다는 느낌이 들기도 한다..

lombok의 @Value 어노테이션을 사용하는 방식이 더 간단하고 깔끔한 것 같다.
다만 외부 의존성을 최대한 줄인다고 lombok을 사용하지 않는 경우에는 record를 사용하는 방법이 class로 구성하는 방법보다는 간단하기도 하고 의도를 보여주기에는 좋다고 생각된다.

reference

써먹는 방법들은 간단하게 봤고 문서는 아래 자료들을 보면 도움이 될 것 같다.

profile
가볍게 쓰는 내용들이라 주관과 틀린 내용이 많습니다. 비판적인 시각으로 봐주시고 지적은 환영 :)

0개의 댓글