본문 바로가기
Java

[Java 14~] record 란? (feat. Lombok)

by 덩라 2023. 10. 24.

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 이라는 라이브러리를 사용하기 시작했습니다.

https://projectlombok.org/

 

Project Lombok

 

projectlombok.org

 

롬복의 공식 홈페이지에는 이러한 소개글(?) 이 게시되어 있습니다.

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 플러그인을 설치하지 않으면 에디터에서 오류로 인식할 수 있다는 다소 불편한 점들이 존재하게 됩니다.

IntelliJ 에서 설치가능한 lombok plugin

 

하지만, 그럼에도 불구하고 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 는 자동으로 생성해주는 메서드가 많은 만큼, 어떻게 생성해 주는지 확실하게 알고 사용할 필요가 있습니다.

  1. 각 필드는 final 로 생성된다.
  2. 생성자는 final 필드에 대해서만 적용된다.
  3. equals(), hashCode(), toString() 메서드 자동 생성
  4. 필드 접근 시, 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 를 적용해보는 것도 재밌을 것 같습니다.


참고 및 출처.

https://docs.oracle.com/en/java/javase/14/language/records.html#GUID-6699E26F-4A9B-4393-A08B-1E47D4B2D263

 

Java Language Updates

JDK 14 introduces records, which are a new kind of type declaration. Like an enum, a record is a restricted form of a class. It’s ideal for "plain data carriers," classes that contain data not meant to be altered and only the most fundamental methods suc

docs.oracle.com

https://projectlombok.org/

 

Project Lombok

 

projectlombok.org

 

'Java' 카테고리의 다른 글

[OOP] 객체지향 생활체조 원칙  (1) 2024.10.06

댓글