본문 바로가기
개발

[Lombok] @value

by angeloper 2023. 7. 20.
반응형

현대적인 Java 프로그래밍에서는 객체 생성 및 메서드 호출과 같은 반복적인 과정이 매우 많기 때문에, developer는 자동화된 기능을 갖춘 라이브러리를 사용하여 이러한 불필요한 코드를 줄이는 노력을 해왔습니다. 이와 같이 개발자들이 쉽게 활용할 수 있는 로그라이브러리인 롬복 (Lombok)도 있으며 대표적으로 @Value 어노테이션이 있습니다. @Value 어노테이션은 가변적으로 사용할 수 없는 클래스를 만들어 컴파일 시점에 불변적으로 객체를 생성하도록 합니다. 이번 글에서는 @Value 어노테이션의 사용 방법과 장단점을 다룰 것입니다.

참고로 이 기능은 v0.11.4에서 실험적으로 도입되었으며, v0.12.0에서 패키지로 승격되어 정식적으로 사용이 가능합니다.

하기 사항은 @Value에 대해서 공식사이트에서 제공하고 있는 내용입니다. 

하기 사항은 공식사이트의 영어부분을 해석한 사항입니다. 제 영어실력으로 인하여 오류가 있을 수 있사오니, 명확한 부분을 확인하기 위해서는 https://projectlombok.org/features/Value를 확인해 주시기 바랍니다.

@Value는 @Data의 불변 변형입니다. 모든 변수는 기본적으로 final과 private 변수로 생성됩니다. 그리고, Setter메서드는 생성되지 않습니다. 불변성은 하위 클래스로 강제될 수 있는 것이 아니기 때문에 클래스는 기본적으로 final 속성으로 만들어지게 됩니다.  @Data와 마찬가지로 toString(), equals(), hashCode() 메서드가 생성되며, 각 필드의 getter메서드와 모든 인수를 포함하는 생성자도 생성됩니다.
실제로 @Value는 다음의 속성을 모두 포함하는 것으로 보셔도 됩니다. final @ToString, @EqualsAndHashCode, @AllArgsConstructor, @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE), @Getter (단, 관련 메서드를 직접 작성하는 경우, Lombok에 의한 부분은 생성되지 않으며 경고도 발생하지 않습니다.) 즉, ToString 메서드를 직접 작성해도 오류를 발생시키지 않으며, Lombok에 의한 ToString 메서드가 생성되지 않는다는 것입니다.
필드에 명시적으로 접근제어자 @NonFinal 또는 @PackagePrivate를 사용하여 final-by-default와 private-by-default를 재정의하는 것이 가능합니다. 또한, @NonFinal은 클래스에서 final 키워드를 제거하는 데 사용할 수 있습니다. 명시적으로 사용한 다른 주석에 의하여 @Value를 구성하는 부분을 재정의할 수 있습니다.

@Value 어노테이션은 제일 간단한 POJO 클래스를 생성합니다. 이 어노테이션은 JavaBean 패턴에서 getter 및 setter 메서드와 함께 setter는 생성되지 않는 불변적인 객체를 만듭니다. @Value 어노테이션은 필드를 final로 만들고, 이 필드 값을 객체 생성 후 변경할 수 없습니다. 또한, 해당 클래스 내에서 toString(), equals(), hashCode() 메서드를 자동으로 구현하며, 모든 매개변수는 생성자에 포함됩니다.

사용법

@Value 어노테이션은 일반 클래스와 똑같이 작성되며 클래스의 필드 앞에 붙인 후 롬복에서 생성자를 자동 생성합니다. 다음은 @Value 어노테이션을 사용하여 클래스를 만드는 예입니다.

import lombok.Value;

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

위 코드로 인하여 만들어진 코드는 하기와 같습니다. 위 설명과 동일한 것을 확인할 수 있습니다. (추가적인 옵션 또는 재정의 되는 부분은 실제로 코드를 작성해 보시기 바랍니다.)

public final class User {
    private final int id;
    private final String name;
    private final int age;
    private final String address;

    public User(final int id, final String name, final int age, final String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public int getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public String getAddress() {
        return this.address;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof User)) {
            return false;
        } else {
            User other = (User)o;
            if (this.getId() != other.getId()) {
                return false;
            } else if (this.getAge() != other.getAge()) {
                return false;
            } else {
                Object this$name = this.getName();
                Object other$name = other.getName();
                if (this$name == null) {
                    if (other$name != null) {
                        return false;
                    }
                } else if (!this$name.equals(other$name)) {
                    return false;
                }

                Object this$address = this.getAddress();
                Object other$address = other.getAddress();
                if (this$address == null) {
                    if (other$address != null) {
                        return false;
                    }
                } else if (!this$address.equals(other$address)) {
                    return false;
                }

                return true;
            }
        }
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        result = result * 59 + this.getId();
        result = result * 59 + this.getAge();
        Object $name = this.getName();
        result = result * 59 + ($name == null ? 43 : $name.hashCode());
        Object $address = this.getAddress();
        result = result * 59 + ($address == null ? 43 : $address.hashCode());
        return result;
    }

    public String toString() {
        int var10000 = this.getId();
        return "User(id=" + var10000 + ", name=" + this.getName() + ", age=" + this.getAge() + ", address=" + this.getAddress() + ")";
    }
}

@Value의 추가옵션

  •  staticConstructor : 이 추가옵션은 @RequiredArgsConstructor의 추가옵션인 staticName과 동일한 기능을 하는 추가 옵션입니다. 하기 예제를 확인하시면, of라는 메서드가 추가되어 있는 부분을 확인하실 수 있습니다.

@Value의 장단점

@Value 어노테이션은 일반적인 POJO 클래스를 쉽게 만들 수 있습니다. 이 어노테이션을 사용하면 필드의 값이 변경되지 않으므로 안정적인 코드를 작성할 수 있습니다. 그러나 일반적인 클래스와 달리 @Value 어노테이션에서 필드를 변경할 수 없으므로 적용할 때 장단점을 고려해야 합니다.

@Value의 장점

  • Immutable Object: @Value 어노테이션에는 필드가 불변이고 객체가 변경될 수 없는 불변 객체를 생성하는 기능이 있습니다. 이로 인해 안전성과 유지 보수성이 향상됩니다.
  • 간결성: @Value 어노테이션이 일부 메서드를 자동으로 생성하므로, 코드량이 줄어듭니다.
  • 테스트 용이성: @Value 어노테이션을 사용하면 객체를 최종 상태에서 테스트하고 검증할 수 있으므로, 단위 테스트를 쉽게 수행할 수 있습니다. 

@Value의 단점

  • 변경 불가능: @Value 어노테이션의 객체는 불변적인 객체이므로, 일부 상황에서 예상치 못한 동작을 일으킬 수 있습니다.
  • 클래스에 대한 제한: @Value 어노테이션은 final 클래스에만 적용됩니다. 즉, 가변성 메서드를 호출할 수 없습니다.
  • 호환성 없음: 자바 릴리스 규칙을 따르지 않는 일부 라이브러리는 롬복 어노테이션을 취급하지 않는 경우가 있습니다.

결론

롬복의 @Value 어노테이션은 필드에 대한 setter 메서드를 생성하지 않는 불변 클래스를 쉽게 작성할 수 있도록 도와줍니다. 다른 롬복 어노테이션을 사용하여 추가 설정을 제공할 수도 있습니다. 이 글에서는 @Value 어노테이션의 사용 방법과 장단점에 대해 알아보았습니다. 프로젝트의 요구 사항에 따라 롬복의 사용 여부를 결정할 수 있습니다.

 
반응형