카테고리 없음

자바 개발자를 위한: 개발 시간을 단축시킬 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은 많은 편리한 기능을 제공하지만, 과용하거나 잘못 사용하면 코드를 이해하고 유지보수하기 어렵게 만들 수 있습니다.

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

 

끝까지 읽어주셔서 감사합니다. (_ _) !!