Spring/Spring

Lombok 어노테이션 정리

ghyeong 2024. 10. 17. 01:49
반응형

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 같은 예외들을 일일이 처리하지 않아도 코드를 간결하게 유지할 수 있습니다. 하지만 예외를 무시하는 것과 동일한 결과를 초래할 수 있으므로, 신중하게 사용해야 합니다.

반응형