Development/Code

자바 개발자를 위한: 개발 시간을 단축시킬 6가지 Lombok 기능

Danny Seo 2024. 6. 29. 18:48

목차

    자바 개발자를 위한, 개발 시간을 단축시킬 6가지 Lombok 기능

     

    자바 개발자는 종종 반복적인 보일러플레이트 코드를 작성하는 데 많은 시간을 소비하게 됩니다.

    Project Lombok은 이러한 문제를 해결하기 위해 다양한 어노테이션을 제공하여 많은 보일러플레이트 코드를 자동화해 줍니다.

    많은 개발자가 @Data@Getter 같은 Lombok의 인기 있는 어노테이션을 알고 있지만, 덜 알려진 기능들도 생산성을 크게 향상시킬 수 있습니다.

     

    이 글에서는 여섯 가지의 유용한 기능에 대해 설명하겠습니다.

    1. @Delegate

    @Delegate를 사용하면 다른 클래스의 메서드를 자신의 클래스에서 직접 쓸 수 있습니다.

     

    예를 들어, A라는 클래스에 sayHello()라는 메서드가 있고, B라는 클래스에서도 이 메서드를 사용하고 싶다면, B 클래스에 A 타입의 필드를 추가하고 @Delegate 어노테이션을 달면 됩니다. 그러면 B 클래스는 자신의 메서드처럼 sayHello() 메서드를 호출할 수 있습니다.

     

    예제를 보세요:

    public class A {
        public String sayHello(String name) {
            return "Hello " + name;
        }
    }
    
    public class B {
        @Delegate
        private A a = new A();
    
        public String sayHello2(String name) {
            return sayHello(name) + ". My name is Malvin.";
        }
    }

     

    컴파일 후 .class 파일이 어떻게 생겼는지 확인해봅시다:

    public class B {
        private A a = new A();
    
        public B() {
        }
    
        public String sayHello2(String name) {
            String var10000 = this.sayHello(name);
            return var10000 + ". My name is Malvin.";
        }
    
        public String sayHello(final String name) {
            return this.a.sayHello(name);
        }
    }

    이 방식의 가장 큰 장점은 클래스 계층이 너무 깊거나 지나치게 결합되지 않도록 하여 코드의 가독성과 유지보수성을 향상시킬 수 있다는 점입니다.

    2. @Cleanup

    @Cleanup은 입력 및 출력 스트림과 같은 다양한 자원을 자동으로 관리하고, close 메서드가 안전하게 호출되도록 합니다.

    선언된 자원 앞에 @Cleanup을 붙여 사용합니다.

     

    예를 들어:

    public class FileUtil {
        public InputStream openFile(final String name) throws IOException {
            @Cleanup InputStream in = new FileInputStream("some/file");
            return in;
        }
    }

    컴파일 후 .class 파일이 어떻게 생겼는지 확인해봅시다:

    public class FileUtil {
    
        public InputStream openFile(final String name) throws IOException {
            InputStream in = new FileInputStream("some/file");
    
            FileInputStream var3;
            try {
                var3 = in;
            } finally {
                if (Collections.singletonList(in).get(0) != null) {
                    in.close();
                }
            }
    
            return var3;
        }
    
    }

    이렇게 하면 코드가 실행을 마칠 때 Lombok이 자동으로 try-finally 블록에서 in.close() 메서드를 호출하여 자원을 해제합니다.

    3.@Singular와 @Builder의 조합

    @Builder는 체이닝 구문을 지원하도록 하고, @Singular는 컬렉션 타입의 필드를 더 쉽게 관리할 수 있게 합니다.

     

    @Singular 어노테이션은 컬렉션 타입 필드에 사용될 수 있으며, 하나의 요소를 추가하는 메서드와 전체 컬렉션을 추가하는 메서드 두 가지를 생성합니다.

     

    이 두 메서드는 @Builder가 생성한 다른 메서드와 체이닝하여 클래스의 모든 필드에 값을 할당할 수 있습니다.

     

    예제를 보세요:

    @Data
    @Builder
    public class Student {
        private String name;
        private int age;
        @Singular
        private Map<String, Integer> grades;
    }
    
    public class CalculateService {
    
        public Student addStudent() {
            return Student.builder()
                    .name("Malvin")
                    .age(28)
                    .grade("English", 100)
                    .grade("Math", 99)
                    .grades(Map.of("Chinese", 100, "Physics", 99))
                    .build();
        }
    }

    (이번에는 .class 파일이 상당히 복잡하기 때문에 공유하지 않겠습니다.)

     

    보시다시피, @Singular 어노테이션을 사용하면 컬렉션 타입의 필드를 추가할 때 컬렉션 객체를 직접 생성하고 초기화할 필요 없이 유연하게 추가할 수 있습니다.

     

    또한, @Singular 어노테이션을 사용하여 생성된 컬렉션 필드는 build() 메서드가 호출될 때 불변 컬렉션으로 변환되어 객체의 불변성과 스레드 안전성을 보장합니다.

     

    clear() 메서드를 사용하여 컬렉션 필드를 비울 수도 있습니다.

     

    예를 들어:

    public class CalculateService {
    
        public Student addStudent() {
            return Student.builder()
                    .name("Malvin")
                    .age(28)
                    .grade("English", 100)
                    .grade("Math", 99)
                    .clear()
                    .grades(Map.of("Chinese", 100, "Physics", 99))
                    .build();
        }
    }

     

    4. @UtilityClass - 정적 유틸리티 메서드를 위한 어노테이션

    자바에서 유틸리티 클래스는 종종 정적 메서드로 가득 차 있으며 인스턴스화되지 않습니다. 일반적으로 클래스에 final을 붙이고, 인스턴스화를 방지하기 위해 private 생성자를 추가하며, 모든 메서드를 정적으로 만들어야 합니다. Lombok은 @UtilityClass 어노테이션으로 이를 간단하게 만들어줍니다.

    import lombok.experimental.UtilityClass;
    
    @UtilityClass
    public class StringUtils {
        public String capitalize(String input) {
            if (input == null || input.isEmpty()) {
                return input;
            }
            return input.substring(0, 1).toUpperCase() + input.substring(1);
        }
    }

    .class 파일은 다음과 같습니다:

    public final class StringUtils {
        public static String capitalize(String input) {
            if (input != null && !input.isEmpty()) {
                String var10000 = input.substring(0, 1).toUpperCase();
                return var10000 + input.substring(1);
            } else {
                return input;
            }
        }
    
        private StringUtils() {
            throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
        }
    }

    @UtilityClass를 사용하면 Lombok이 자동으로 클래스를 final로 표시하고, private 생성자를 생성하며, 비정적 메서드를 추가하려고 하면 오류가 발생하도록 합니다. 이렇게 하면 유틸리티 클래스가 깔끔하고 집중된 상태를 유지할 수 있습니다.

    5. @Synchronized - 안전한 동기화

    자바의 synchronized 키워드는 기본적인 형태의 스레드 동기화를 제공합니다. 그러나 메서드에 직접 사용하면 객체의 내재 락이 외부에 노출될 수 있습니다. Lombok은 @Synchronized 어노테이션을 사용하여 더 안전한 대안을 제공합니다.

    import lombok.Synchronized;
    
    public class SafeCounter {
        private int count = 0;
    
        @Synchronized
        public int incrementAndGet() {
            return ++count;
        }
    }

    .class 파일은 다음과 같습니다:

    public class SafeCounter {
        private final Object $lock = new Object[0];
        private int count = 0;
    
        public SafeCounter() {
        }
    
        public int incrementAndGet() {
            synchronized(this.$lock) {
                return ++this.count;
            }
        }
    }

    Lombok은 인스턴스 또는 정적 메서드에 대해 각각 $lock 또는 $LOCK이라는 private 필드를 생성하고 해당 필드에 메서드를 동기화합니다. 이렇게 하면 락이 클래스 내부에 캡슐화되어 외부 간섭을 방지할 수 있습니다.

    6. @With - 불변 스타일 수정

    불변성은 안전하고 예측 가능한 코드를 작성하는 중요한 프로그래밍 방식입니다. 하지만, 자바의 장황함 때문에 불변성을 구현하는 것은 때로 어려울 수 있습니다. Lombok의 @With 어노테이션은 각 필드에 대해 "with" 메서드를 생성하여 이 패턴을 쉽게 구현할 수 있도록 합니다.

    import lombok.AccessLevel;
    import lombok.Getter;
    import lombok.With;
    
    @Getter
    public class ConfigurableSettings {
        @With(AccessLevel.PUBLIC)
        private final boolean debugMode;
        @With(AccessLevel.PUBLIC)
        private final int maxConnections;
        @With(AccessLevel.PUBLIC)
        private final String defaultHost;
    
        public ConfigurableSettings(boolean debugMode, int maxConnections, String defaultHost) {
            this.debugMode = debugMode;
            this.maxConnections = maxConnections;
            this.defaultHost = defaultHost;
        }
    }
    
    public class WithExample {
        public static void main(String[] args) {
            ConfigurableSettings settings = new ConfigurableSettings(false, 10, "localhost");
    
            // 'with' 메서드를 사용하여 다른 'debugMode' 값으로 새 인스턴스를 생성
            ConfigurableSettings modifiedSettings = settings.withDebugMode(true);
    
            System.out.println("Original debugMode: " + settings.isDebugMode());
            System.out.println("Modified debugMode: " + modifiedSettings.isDebugMode());
        }
    }

    .class 파일은 다음과 같습니다:

    public class ConfigurableSettings {
        private final boolean debugMode;
        private final int maxConnections;
        private final String defaultHost;
    
        public ConfigurableSettings(boolean debugMode, int maxConnections, String defaultHost) {
            this.debugMode = debugMode;
            this.maxConnections = maxConnections;
            this.defaultHost = defaultHost;
        }
    
        public boolean isDebugMode() {
            return this.debugMode;
        }
    
        public int getMaxConnections() {
            return this.maxConnections;
        }
    
        public String getDefaultHost() {
            return this.defaultHost;
        }
    
        public ConfigurableSettings withDebugMode(final boolean debugMode) {
            return this.debugMode == debugMode ? this : new ConfigurableSettings(debugMode, this.maxConnections, this.defaultHost);
        }
    
        public ConfigurableSettings withMaxConnections(final int maxConnections) {
            return this.maxConnections == maxConnections ? this : new ConfigurableSettings(this.debugMode, maxConnections, this.defaultHost);
        }
    
        public ConfigurableSettings withDefaultHost(final String defaultHost) {
            return this.defaultHost == defaultHost ? this : new ConfigurableSettings(this.debugMode, this.maxConnections, defaultHost);
        }
    }

    @With 어노테이션은 각 필드에 대해 새로운 객체 인스턴스를 반환하는 메서드를 생성하여 원본 객체는 변경되지 않도록 합니다. 이는 불변 객체와 관련된 유창한 프로그래밍 스타일을 가능하게 하여 setter의 편리함을 포기하지 않고도 사용할 수 있습니다.

     

    이러한 고급 Lombok 기능을 사용하면 자바 개발의 생산성을 끌어올릴 수 있으며, 비지니스 로직에 조금 더 집중하고 반복적인 코딩 패턴에 신경을 덜 쓸 수 있습니다.

     

    Lombok은 많은 편리한 기능을 제공하지만, 과용하거나 잘못 사용하면 코드를 이해하고 유지보수하기 어렵게 만들 수 있습니다.

    따라서, 이러한 기능을 사용할 때는 항상 신중을 기하고, 그 영향에 대해 충분히 고려하는 것이 중요합니다.

     

    읽어주셔서 감사합니다! 😊
    개발 관련 궁금증이나 고민이 있으신가요?
    아래 링크를 통해 저에게 바로 문의해 주세요! 쉽고 빠르게 도움 드리겠습니다.

    '개발자서동우' 프로필 보기