1. Lombok 전성시대
자바 기반의 웹 애플리케이션을 개발하다 보면, 생성자/Getter 등의 기본적으로 객체들이 가져야할 코드들이 존재하게 됩니다.
public class Member {
private String name;
private String email;
private int age;
}
위와 같이 3개의 필드를 가진 클래스가 있다고 가정할 때, 생성자/Getter 등 객체들에게 필요한 메서드들이 추가되면 클래스가 아래와 같이 길어지게 됩니다.
public class Member {
private String name;
private String email;
private int age;
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String getName() {
return this.name;
}
public String getEmail() {
return this.email;
}
public int getAge() {
return this.age;
}
}
지금 예시는 필드가 3개라 괜찮지만, 실제 개발하면서 필드가 많아지고, 객체가 가지는 비지니스 로직에 대한 메서드가 추가되고, 필요에 따라 equals(), hashCode() 와 같은 Object 에서 override 되어야 하는 메서드가 추가된다면 하나의 클래스는 어마어마한 길이를 가지게 될 것 입니다.
이렇게 코드가 길어지는 것을 어느정도 해소하고자 Lombok 이라는 라이브러리를 사용하기 시작했습니다.
롬복의 공식 홈페이지에는 이러한 소개글(?) 이 게시되어 있습니다.
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
쉽게 말해, 자바 개발을 하는데 있어 getter 나 equals 같은 메서드를 일일이 만들지말고, annotation 하나로 해결할 수 있다는 의미입니다.(기타 builder, logging 포함)
위 예시로 만든 Member 클래스에 롬복을 적용해보면 아래와 같이 변경될 수 있습니다.
@AllArgsConstructor
@Getter
public class Member {
private String name;
private String email;
private int age;
}
놀랍게도 위 코드는 앞서 보여드린 길고 긴 Member 클래스와 동일한 코드 입니다. 훨씬 더 깔끔하고 중요한 비지니스 로직 메서드를 집중적으로 보여줄 수 있다는 점에서 롬복은 많은 자바 개발자들의 사랑을 받는 라이브러리 입니다.
2. Lombok의 아쉬운 점
개인적으로 롬복에 아쉬운 점이 있다면, 바로 "자바에서 공식적으로 지원하는 기능이 아니다." 라는 점 입니다.
보통 자바에서 공식적으로 지원하는 기능을 import 하게 되면 javax.* (최근에는 jakarta.*) 로 import 되지만, 롬복의 경우 의존성을 추가해서 사용하는 외부 라이브러리이기 때문에 별도의 import 를 가지게 됩니다.
```build.gradle
// == 중략 == //
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
// lombok 라이브러리 import
import lombok.Getter;
import lombok.AllArgsConstructor;
@AllArgsConstructor
@Getter
public class Member {
private String name;
private String email;
private int age;
}
이미 롬복은 수 많은 개발 환경에서 애용돼서 거의 기본으로 들고 다니는 라이브러리라고 하지만, 엄밀히 말하자면 자바에서 공식적으로 지원하는 API 가 아니기 때문에, 항상 의존성을 추가해야 한다는 점과 로컬 환경에서 IDE 사용 시, lombok 플러그인을 설치하지 않으면 에디터에서 오류로 인식할 수 있다는 다소 불편한 점들이 존재하게 됩니다.
하지만, 그럼에도 불구하고 annotation 으로 getter/setter, 생성자, builder 패턴, equals()/hashCode() 재정의 등 Object에 필요한 기본적인 기능들을 사용할 수 있다는 점은 아주 매력적입니다.
3. record 의 등장
이렇게 롬복이 수 많은 자바 개발자의 사랑을 받는 와중에 Java 14 버전이 release 되면서, 롬복의 기능을 대체할 수 있을 것 같은 기능이 새롭게 추가됩니다.
그 기능이 바로 record 입니다.
oracle 공식 홈페이지에서는 record 를 다음과 같이 소개합니다.
번역해보자면, "record 는 JDK 14 에서 새로 나온 데이터 종류로, 변경될 수 없는 데이터와 생성자 및 접근자(getter를 의미)와 같은 기본적인 메서드들을 포함하는 클래스인 "일반 데이터 매체" 에 적합하다." 고 소개하고 있습니다.
즉, record 타입으로 선언하면 getter 와 생성자는 만들어준다는 의미이므로, 위에서 예시로 썼던 Member 클래스를 record 로 바꿔보면 아래와 같이 바뀔 수 있습니다.
public record Member(String name, String email, int age) {}
이렇게 record 를 선언하면, 각 필드의 접근자와 객체를 생성하기 위한 생성자는 이미 포함되어 있다는 의미입니다.
4. record 사용 시, 주의사항
record 는 자동으로 생성해주는 메서드가 많은 만큼, 어떻게 생성해 주는지 확실하게 알고 사용할 필요가 있습니다.
- 각 필드는 final 로 생성된다.
- 생성자는 final 필드에 대해서만 적용된다.
- equals(), hashCode(), toString() 메서드 자동 생성
- 필드 접근 시, getXXX() 형태가 아닌 XXX() 형태로 접근 ( Ex. member.name(), member.age() 등 )
위 적용 사항으로 미루어 보았을 때, 위에서 record 로 생성한 Member 는 class 로 바꾸면 실제론 이런 모습일 것 입니다.
public class Member {
private final String name;
private final String email;
private final int age;
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String name() {
return this.name;
}
public String email() {
return this.email;
}
public int age() {
return this.age;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Member that = (Member) o;
return Objects.equals(name(), that.name())
&& Objects.equals(email(), that.email())
&& Objects.equals(age(), that.age());
}
@Override
public int hashCode() {
return Objects.hash(name(), email(), age());
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
5. record 의 한계
앞서 봤던 record 의 특징들을 봤을 때, 롬복을 완벽하게 대체할 수 있을까요?
전 불가능하다고 생각합니다.
당장 단적인 예를 들어 JPA 를 사용하는 프로젝트의 경우 아래와 같은 Entity 를 만든다고 가정해보겠습니다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
protected Member() {}
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String getName() {
return this.name;
}
public String getEmail() {
return this.email;
}
public int getAge() {
return this.age;
}
}
위와 같이 만든 JPA Entity 객체를 record 로 변경할 수 있을까요?
당장 @Id 와 @GeneratedValue 때문에 불가능할 것 입니다.
하지만, 위 코드는 롬복을 사용하면 아래처럼 간단해질 수는 있습니다.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
public Member(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
}
이렇게 순수 java object(a.k.a POJO) 가 아니고서야 JPA 같은 기술을 사용한 class 에는 record 적용이 어려워 보입니다.
그래서 record 소개 글에도 아래와 같은 문구가 있는게 아니었을까 싶습니다.
변경될 수 없는 데이터와 생성자 및 접근자(getter를 의미)와 같은 기본적인 메서드들을 포함하는 클래스인
"일반 데이터 매체" 에 적합하다.
여기서 말하는 "일반 데이터 매체" 가 어떠한 의존도 가지지 않는 순수한 자바 객체를 의미하는 것은 아닐까요?
6. 마치며
SpringBoot3 버전을 사용하는 프로젝트가 한 두 개씩 생기면서, 어느센가 record 라는 타입이 생긴 것을 볼 수 있습니다.
"이게 뭐지?" 라는 생각에 한 번 찾아보면서 롬복과의 연관관계랑 생각해보면 재밌겠다. 라는 생각에 포스팅을 해보았습니다.
아직 class 가 훨씬 익숙하지만, dto 같이 데이터를 보관하고 옮기는 역할로만 쓰이는 class 들에는 record 를 적용해보는 것도 재밌을 것 같습니다.
참고 및 출처.
'Java' 카테고리의 다른 글
[OOP] 객체지향 생활체조 원칙 (1) | 2024.10.06 |
---|
댓글