baeldung.com: java-record-keyword
Java 14부터 포함된 불변 객체를 만들기 위한 방법이다.
class를 사용해서 만드는 방법보다 훨씬 간단하다.
Spring으로 개발을 하다보면 DTO class를 만드는 경우가 많은데 이 때 record를 사용해서 만들면 훨씬 쉽고 안전하게 만드는 것이 가능하다. 불변객체를 만들려는 의도를 확실하게 보여줄 수 있는 것도 덤으로 느껴진다.
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로 만들었다. 당황스러울 정도로 코드가 짧아졌다.
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로 구성하는 방법보다는 간단하기도 하고 의도를 보여주기에는 좋다고 생각된다.
써먹는 방법들은 간단하게 봤고 문서는 아래 자료들을 보면 도움이 될 것 같다.