Lombok 어노테이션 정리
Lombok은 자바(Java) 개발에서 반복되는 코드를 자동으로 생성해주는 매우 유용한 라이브러리입니다. 주로 자바 모델 클래스(POJO)에서 자주 쓰이는 Getter, Setter, toString(), Equals와 같은 메서드들을 어노테이션으로 간단하게 처리할 수 있어 코드의 가독성과 생산성을 크게 높여줍니다. Lombok은 코드의 중복을 줄이고 유지보수를 쉽게 만들어주는 라이브러리로, 특히 엔터프라이즈급 애플리케이션을 개발할 때 많은 이점을 제공합니다. 이번 포스팅에서는 Lombok에서 자주 사용되는 어노테이션을 정리해보겠습니다.
@Getter / @Setter
- @Getter: 클래스의 모든 필드에 대해 getter 메서드를 자동으로 생성합니다.
- @Setter: 클래스의 모든 필드에 대해 setter 메서드를 자동으로 생성합니다.
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class User {
private String name;
private int age;
}
특정 필드에만 적용하기
public class User {
@Getter
private String name;
@Setter
private int age;
}
위와 같이 특정 필드에만 어노테이션을 붙여서 getter/setter를 선택적으로 생성할 수 있습니다.
@ToString
- @ToString은 클래스의 toString() 메서드를 자동으로 생성해주는 Lombok 어노테이션입니다. 주로 디버깅이나 로그를 남길 때 객체의 상태를 확인하는 용도로 사용됩니다.
- @ToString 기본 사용 예시
import lombok.ToString;
@ToString
public class User {
private String name;
private int age;
}
User 객체를 출력하면 User(name=홍길동, age=25)처럼 객체의 필드값을 포함한 문자열을 반환합니다.
특정 필드 제외
필드 중 특정 필드는 toString() 메서드에 포함되지 않도록 할 수 있습니다
import lombok.ToString;
@ToString(exclude = "age", callSuper = true, of = "name")
public class User extends Person {
private String name;
private int age;
private String address;
}
@ToString 옵션 사용 예시
- exclude: 특정 필드를 toString() 메서드에서 제외할 수 있습니다.
- of: 특정 필드만 포함하도록 제한할 수 있습니다.
- callSuper: 상위 클래스의 toString() 메서드도 호출할지 여부를 지정합니다.
- exclude = "age": age 필드는 출력에서 제외됩니다.
- callSuper = true: 상위 클래스인 Person의 toString() 결과도 포함됩니다.
- of = "name": name 필드만 toString()에 포함됩니다.
User user = new User();
user.setName("홍길동");
user.setAge(30);
user.setAddress("서울");
System.out.println(user); // 출력: User(name=홍길동)
@EqualsAndHashCode
- @EqualsAndHashCode는 equals()와 hashCode() 메서드를 자동으로 생성해줍니다. 이 두 메서드는 객체의 동등성을 비교할 때 중요한 역할을 합니다. equals()는 객체의 필드 값이 동일한지를, hashCode()는 객체를 해시로 나타내기 위한 고유한 숫자를 반환합니다.
- @EqualsAndHashCode 기본 사용 예시
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class User {
private String name;
private int age;
}
위 예시는 name과 age 필드를 기반으로 equals()와 hashCode()를 자동 생성합니다.
User user1 = new User("홍길동", 25);
User user2 = new User("홍길동", 25);
System.out.println(user1.equals(user2)); // true
@EqualsAndHashCode 옵션 사용 예시
- exclude: 특정 필드를 equals()와 hashCode()에서 제외합니다.
- callSuper: 상위 클래스의 equals()와 hashCode() 메서드도 포함할지 여부를 지정합니다.
- onlyExplicitlyIncluded: 어노테이션으로 명시된 필드만 비교 대상으로 포함합니다.
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(exclude = "age", callSuper = true)
public class User extends Person {
private String name;
private int age;
}
- exclude = "age": age 필드는 동등성 비교에서 제외됩니다.
- callSuper = true: 상위 클래스인 Person의 필드도 equals()와 hashCode()에 포함됩니다.
User user1 = new User("홍길동", 30);
User user2 = new User("홍길동", 40);
System.out.println(user1.equals(user2)); // true (age는 비교 제외)
명시적으로 필드를 포함하는 예시
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
@EqualsAndHashCode.Include
private String name;
@EqualsAndHashCode.Exclude
private int age;
}
- EqualsAndHashCode.Include: 이 필드만 equals()와 hashCode()에서 사용됩니다.
- @EqualsAndHashCode.Exclude: 이 필드는 비교에서 제외됩니다.
User user1 = new User("홍길동", 30);
User user2 = new User("홍길동", 40);
System.out.println(user1.equals(user2)); // true (age는 제외됨)
@NoArgsConstructor / @AllArgsConstructor / @RequiredArgsConstructor
- @NoArgsConstructor: 파라미터가 없는 기본 생성자를 생성합니다.
- @AllArgsConstructor: 모든 필드를 파라미터로 받는 생성자를 생성합니다.
- @RequiredArgsConstructor: final 키워드가 붙은 필드나 @NonNull이 지정된 필드만을 파라미터로 받는 생성자를 생성합니다.
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드에 대한 생성자
@RequiredArgsConstructor // final 필드에 대한 생성자
public class User {
private final String name;
private int age;
}
@Data
- @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor가 결합된 어노테이션입니다. 자주 사용하는 메서드들을 한 번에 모두 생성해줍니다.
import lombok.Data;
@Data
public class User {
private String name;
private int age;
}
@Builder
- @Builder는 빌더 패턴을 사용해 객체를 생성할 수 있도록 해줍니다. 생성자나 setter를 직접 사용하지 않고, 객체 생성 시 더 유연하고 가독성 높은 방식으로 인스턴스를 생성할 수 있습니다\
import lombok.Builder;
@Builder
public class User {
private String name;
private int age;
}
사용 예시:
User user = User.builder()
.name("홍길동")
.age(25)
.build();
@Value 어노테이션
@Value는 Lombok에서 제공하는 불변 객체(Immutable Object)를 만들기 위한 어노테이션입니다. 주로 @Data의 변형된 버전으로, 모든 필드를 final로 선언하고, getter는 생성하지만 setter는 생성하지 않으며, 객체 생성 후에는 값이 변하지 않는 불변성을 보장합니다.
@Value 기본 사용 예시
import lombok.Value;
@Value
public class User {
String name;
int age;
}
@Value를 사용하면, User 클래스는 아래의 특징을 갖습니다.
- 모든 필드가 final: 생성된 이후에는 변경 불가.
- getter 생성: 모든 필드에 대해 getter 메서드가 자동 생성.
- setter 미생성: 필드 값을 수정할 수 없음.
- 기본 생성자: 모든 필드를 매개변수로 받는 생성자가 자동 생성.
User user = new User("홍길동", 30);
System.out.println(user.getName()); // "홍길동"
user.setAge(40); // 컴파일 에러: setter 메서드가 없음.
@Value와 함께 사용되는 주요 Lombok 기능
- @EqualsAndHashCode: 자동으로 생성됩니다.
- @ToString: 자동으로 생성됩니다.
- @AllArgsConstructor: 모든 필드를 초기화하는 생성자가 자동으로 생성됩니다.
User user1 = new User("홍길동", 30);
User user2 = new User("홍길동", 30);
System.out.println(user1.equals(user2)); // true
System.out.println(user1.toString()); // User(name=홍길동, age=30)
불변 객체를 사용할 때의 장점
- 불변성 보장: 필드 값이 한 번 설정되면 변경되지 않기 때문에, 멀티스레드 환경에서도 안전하게 객체를 공유할 수 있습니다.
- Thread-safe: 객체가 불변이므로 동기화(synchronization)에 대한 걱정을 덜 수 있습니다.
@Value
public class Address {
String street;
String city;
}
Address address = new Address("서울", "강남구");
// 주소 변경 불가
// address.setCity("마포구"); // 컴파일 에러
@Slf4j (로그를 쉽게 사용하기)
- @Slf4j는 log 객체를 자동으로 생성해주며, 이를 통해 손쉽게 로그를 남길 수 있습니다
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserService {
public void createUser() {
log.info("User is being created");
}
}
log.info()를 사용해 간단하게 로그를 기록할 수 있습니다.
@SneakyThrows 어노테이션
@SneakyThrows는 Lombok에서 제공하는 어노테이션으로, 예외 처리를 자동으로 해주는 기능을 합니다. 특히, Java에서 Checked Exception(예외를 반드시 처리해야 하는 예외)를 처리할 때 매우 유용합니다.
일반적으로 Java에서는 Checked Exception을 반드시 try-catch 블록으로 처리하거나, 메서드에 throws 키워드를 사용해 상위로 던져야 하지만, @SneakyThrows를 사용하면 이러한 예외를 명시적으로 처리하지 않고도 코드를 간단하게 작성할 수 있습니다.
주요 특징
- Checked Exception을 자동으로 감싸서 처리해주므로, 코드에서 try-catch 또는 throws를 명시적으로 작성할 필요가 없습니다.
- 런타임 시에 예외가 발생하면 Lombok이 해당 예외를 던집니다.
@SneakyThrows 사용 방법 및 예시
1. Checked Exception을 처리하는 기본 예시
보통 Java에서는 파일을 읽을 때 IOException을 처리해야 합니다. 하지만 @SneakyThrows를 사용하면 예외 처리 코드를 생략할 수 있습니다
import lombok.SneakyThrows;
import java.io.IOException;
public class FileService {
@SneakyThrows
public void readFile() {
throw new IOException("파일을 찾을 수 없습니다.");
}
}
위 코드에서는 IOException이 발생할 수 있지만, @SneakyThrows 덕분에 try-catch 블록이나 throws IOException 선언 없이도 코드를 작성할 수 있습니다. 컴파일러는 예외 처리를 강제하지 않습니다.
public class Main {
public static void main(String[] args) {
FileService fileService = new FileService();
fileService.readFile(); // 예외 발생 시 런타임에 던져짐
}
}
이 코드에서 IOException은 런타임 시에 자동으로 던져지며, 프로그램이 정상적으로 컴파일됩니다.
2. 특정 예외를 처리하는 예시
@SneakyThrows는 여러 종류의 Checked Exception을 처리할 때도 유용합니다. 예를 들어, InterruptedException이나 IOException 등 다양한 Checked Exception을 던질 수 있습니다.
import lombok.SneakyThrows;
public class TaskService {
@SneakyThrows
public void executeTask() {
// Checked Exception 발생
Thread.sleep(1000); // InterruptedException 던질 수 있음
}
}
Thread.sleep()은 일반적으로 InterruptedException을 발생시킬 수 있지만, @SneakyThrows를 사용하면 이 예외를 따로 처리할 필요가 없습니다.
public class Main {
public static void main(String[] args) {
TaskService taskService = new TaskService();
taskService.executeTask(); // 예외 처리 없이 실행
}
}
3. 특정 타입의 예외만 던지도록 제한하기
@SneakyThrows(ExceptionType.class) 형태로 특정 예외 타입만 던지도록 제한할 수도 있습니다. 이렇게 하면 지정된 예외 타입만 처리하고, 나머지는 기존 방식대로 예외 처리를 요구합니다.
import lombok.SneakyThrows;
import java.io.IOException;
public class FileService {
@SneakyThrows(IOException.class)
public void readFile() throws ClassNotFoundException {
throw new IOException("파일을 찾을 수 없습니다.");
}
}
위 코드에서 @SneakyThrows(IOException.class)를 사용하여 IOException에 대해서만 예외 처리를 자동으로 수행합니다. 하지만 메서드 시그니처에 명시된 ClassNotFoundException은 여전히 명시적으로 처리해야 합니다.
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
FileService fileService = new FileService();
fileService.readFile(); // IOException은 자동 처리, ClassNotFoundException은 직접 처리
}
}
@SneakyThrows 사용 시 주의사항
- @SneakyThrows는 Checked Exception을 무시하는 것과 비슷한 효과를 주지만, 실제로는 런타임 시 예외를 던지기 때문에 예외가 발생할 수 있는 코드를 잘 이해하고 있어야 합니다.
- 예외 처리를 명시적으로 하지 않기 때문에, 개발 중에는 편리할 수 있지만 예외가 발생할 가능성이 높은 코드에서는 런타임 에러를 쉽게 놓칠 수 있습니다.
- Checked Exception의 명시적인 처리가 없는 대신, 예외 발생 시 런타임에 문제가 될 수 있으므로 적절한 상황에서만 사용해야 합니다.
@SneakyThrows는 Checked Exception을 간단하게 처리할 수 있게 해주는 Lombok의 편리한 기능입니다. 자주 발생하는 IOException, InterruptedException 같은 예외들을 일일이 처리하지 않아도 코드를 간결하게 유지할 수 있습니다. 하지만 예외를 무시하는 것과 동일한 결과를 초래할 수 있으므로, 신중하게 사용해야 합니다.