목차
자바 개발자는 종종 반복적인 보일러플레이트 코드를 작성하는 데 많은 시간을 소비하게 됩니다.
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은 많은 편리한 기능을 제공하지만, 과용하거나 잘못 사용하면 코드를 이해하고 유지보수하기 어렵게 만들 수 있습니다.
따라서, 이러한 기능을 사용할 때는 항상 신중을 기하고, 그 영향에 대해 충분히 고려하는 것이 중요합니다.
읽어주셔서 감사합니다! 😊
개발 관련 궁금증이나 고민이 있으신가요?
아래 링크를 통해 저에게 바로 문의해 주세요! 쉽고 빠르게 도움 드리겠습니다.
'Development > Code' 카테고리의 다른 글
Spring Boot: 사용 가능한 메모리보다 더 많은 데이터 쿼리하기 (0) | 2024.07.01 |
---|---|
Java 21의 레코드 패턴으로 클린 코드 작성하기 (0) | 2024.07.01 |
자바 개발자를 위한: 생산성을 향상시킬 10가지 Guava 기능 (0) | 2024.06.28 |
알아두면 좋은 Spring Boot 기능 10가지 (0) | 2024.06.28 |
Spring Boot의 성능을 향상시키는 10가지 방법 (0) | 2024.06.28 |