Java Lombok 사용법

Lombok이란?

  • 어노테이션을 선언하면, getter, setter, toString, equals등의 “반복적으로 작성하는 코드"를 컴파일시에 자동 생성해 준다.
  • 하지만 Getter, Setter 자동 생성은 함부로 하게 되면, 객체 지향을 파괴하는 것이기도 하다.
  • 그러기에 사용하고 있는 프레임워크에서 Getter, Setter를 필요로 하기에 어쩔 수 없는 상황인 경우가 아니라면 사용해서는 안되지 않을까?

환경 설정

Lombok은 툴에 따라, 따로 플러그인을 설치하거나 설정이 있어야 한다. 인텔리제이 IDEA 최근 버전 같은 경우에는 따로 플러그인은 설치하지 않고, 설정만 해도 구동이 된다. 각 툴마다 설치 방법은 검색해서 찾아 설정 바란다.

Hello World

build.gradle를 아래와 같이 작성한다.

plugins {
    id 'java'
}

group 'com.devkuma'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'

    compileOnly 'org.projectlombok:lombok:1.18.20'
    annotationProcessor 'org.projectlombok:lombok:1.18.20'

    testCompileOnly 'org.projectlombok:lombok:1.18.20'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
}

HelloLombokTutorial.java 파일을 생성하여 코드를 아래과 같이 작성한다.

package com.devkuma.tutorial.lombok;

import lombok.Data;

@Data
public class HelloLombokTutorial {

    private String string;
    private int number;
    
    public static void main(String[] args) {
        HelloLombokTutorial tutorial = new HelloLombokTutorial();

        tutorial.setString("Hello Lombok!!");
        tutorial.setNumber(999);

        System.out.println(tutorial);
    }
}

실행결과

Main(string=Hello Lombok!!, number=999)
  • 클래스에 @Data 어노테이션을 선언하여 getter, setter, toString등의 메소드가 자동으로 생성되었다.
  • Lombok 자체는 컴파일시에만 사용하기 때문에 종속 범위는 compileOnly으로 한다.

val 변수

ValTutorial.java

package com.devkuma.tutorial.lombok;

import lombok.val;

import java.util.Arrays;
import java.util.HashMap;

public class ValTutorial {

    public static void main(String... args) {
        val list = Arrays.asList("devkuma", "araikuma", "kimkc");
        list.forEach(System.out::println);

        val map = new HashMap<String, Long>();
        map.put("hoge", 1L);
    }
}
  • val 라는 타입으로 변수를 정의하면, 대입한 값에서 적당한 타입을 추론해 주고 final이 선언이 된다.

위에 소스 코드중에 map을 val로 선언한 코드에서 대해서도 아래와 같이 일반 유형도 제대로 로드되는 걸 볼 수 있다.

lombok

val에서 정의 된 변수에는 final이 선언된 것이기에 다시 할당 할 수 없다.

lombok

map = null; 코드를 작성하여 실행하면 아래와 같이 에러 발생한다.

D:\dev\\java-lombok-tutorial\src\main\java\com\devkuma\tutorial\lombok\ValTutorial.java:17: error: cannot assign a value to final variable map
        map = null;
        ^

@NonNull

NonNullTutorial.java

package com.devkuma.tutorial.lombok;

import lombok.NonNull;

public class NonNullTutorial {

    public static void main(String[] args) {
        method("devkuma");
        method(null);
    }

    private static void method(@NonNull String value) {
        System.out.println(value);
    }
}

실행 결과

devkuma

Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.1.1/userguide/command_line_interface.html#sec:command_line_warnings
2 actionable tasks: 2 executed
Exception in thread "main" java.lang.NullPointerException: value is marked non-null but is null
	at com.devkuma.tutorial.lombok.NonNullTutorial.method(NonNullTutorial.java:11)
	at com.devkuma.tutorial.lombok.NonNullTutorial.main(NonNullTutorial.java:8)
  • @NonNull으로 메소드의 인수를 주석하면 null 체크가 자동으로 생성된다.

@Cleanup

CleanupTutorial.java

package com.devkuma.tutorial.lombok;

import lombok.Cleanup;

public class CleanupTutorial {
    public static void main(String[] args) {
        @Cleanup CleanupTutorial cleanupTutorial = new CleanupTutorial();
    }

    public void close() {
        System.out.println("close 메소드가 호출되었습니다.");
    }

    public void close(String arg) {
        System.out.println("close(String) 메소드가 호출되었습니다.");
    }
}

실행 결과

close 메소드가 호출되었습니다.
  • 로컬 변수에 @Cleanup 어노테이션을 선언하면, 스코프에에서 벗어나면 close() 메소드가 호출된다.

@Cleanup에 메소드 지정

아래와 같이 실행하는 메소드를 지정할 수도 있다.

package com.devkuma.tutorial.lombok;

import lombok.Cleanup;

public class CleanupTutorial2 {
    public static void main(String[] args) {
        @Cleanup("dispose") CleanupTutorial2 cleanupTutorial = new CleanupTutorial2();
    }

    public void close() {
        System.out.println("close 메소드가 호출되었습니다.");
    }

    public void dispose() {
        System.out.println("dispose 메소드가 호출되었습니다.");
    }
}

실행 결과

dispose 메소드가 호출되었습니다.
  • value에 dispose를 지정하여 dispose() 메소드가 실행되었다.

@Getter , @Setter

기본 사용 방법은 아래와 같다.

package com.devkuma.tutorial.lombok;

import lombok.Getter;
import lombok.Setter;

public class GetterSetterTutorial {

    @Getter
    @Setter
    private String value;

    public static void main(String[] args) {
        GetterSetterTutorial tutorial = new GetterSetterTutorial();

        tutorial.setValue("Hello @Getter, @Setter");
        System.out.println(tutorial.getValue());
    }

}

실행 결과

Hello @Getter, @Setter
  • @Getter에서 getter 메소드를 @Setter에서 setter 메소드를 자동 생성 할 수 있다.

접근제어자를 지정할 수도 있다.

package com.devkuma.tutorial.lombok;

import lombok.AccessLevel;
import lombok.Getter;

public class GetterAccessLevelTutorial {
    @Getter(AccessLevel.PRIVATE)
    private String value;
}
  • valueAccessLevel를 전달하여 접근제어자를 지정할 수 있다.

@Getter(lazy=true)

package com.devkuma.tutorial.lombok;

import lombok.Getter;

public class GetterLazyTutorial {
    public static void main(String[] args) {
        GetterLazyTutorial tutorial = new GetterLazyTutorial();
        System.out.println("Main instance is created");
        tutorial.getLazy();
    }

    @Getter
    private final String notLazy = createValue("not lazy");

    @Getter(lazy=true)
    private final String lazy = createValue("lazy");

    private String createValue(String name) {
        System.out.println("createValue(" + name + ")");
        return null;
    }
}

실행 결과

createValue(not lazy)
Main instance is created
createValue(lazy)

@Getterlazytrue로 설정하면 값을 초기화하는 getter 메소드가 처음 호출 될 때까지 지연시킬 수 있다.

@ToString

package com.devkuma.tutorial.lombok;

import lombok.ToString;

import java.util.Arrays;
import java.util.List;

@ToString(exclude="ignore")
public class ToStringTutorial {

    private int id = 100;
    private String value = "devkuma";
    private List<String> list = Arrays.asList("araikuma", "kimkc");
    private double ignore = 999;

    public static void main(String[] args) {
        System.out.println(new ToStringTutorial());
    }
}

실행 결과

ToStringTutorial(id=100, value=devkuma, list=[araikuma, kimkc])
  • 클래스에 @ToString 어노테이션을 선언하면 toString()메소드가 자동으로 생성된다.
  • exclude 속성에 출력하지 않는 필드를 지정할 수도 있다.
  • 클래스가 상호 의존가 있다면 toString()를 호출할 때 무한 루프가 발생하므로 exclude으로 제외가 필요할 수 있다.

@EqualsAndHashCode

package com.devkuma.tutorial.lombok;

import lombok.EqualsAndHashCode;

import java.util.Arrays;
import java.util.List;

@EqualsAndHashCode
public class EqualsAndHashCodeTutorial {

    private int id = 100;
    private String value = "devkuma";
    private List<String> list = Arrays.asList("araikuma", "kimkc");

    public static void main(String[] args) {
        EqualsAndHashCodeTutorial a = new EqualsAndHashCodeTutorial();
        EqualsAndHashCodeTutorial b = new EqualsAndHashCodeTutorial();

        System.out.println("a.hash = " + a.hashCode());
        System.out.println("b.hash = " + b.hashCode());
        System.out.println(a.equals(b));
    }

}

실행 결과

a.hash = -1469387943
b.hash = -1469387943
true
  • @EqualsAndHashCode 어노테이션을 선언하면 equals() 메소드와 hashCode() 메소드가 자동으로 생성된다.
  • 비교는 모든 필드가 각각 일치 여부에서 확인한다.
    • DDD으로 값 객체에서 사용할 수 있을 것 같다.

생성자 자동 생성

@NoArgsConstructor

package com.devkuma.tutorial.lombok;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class NoArgsConstructorTutorial {

    public NoArgsConstructorTutorial(String string) {
    }

    public static void main(String[] args) {
        new NoArgsConstructorTutorial();
    }
}
  • @NoArgsConstructor 어노테이션을 선언하여 인수가 없는 생성자를 생성할 수 있다.

@RequiredArgsConstructor

Main.java

package com.devkuma.tutorial.lombok;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class RequiredArgsConstructorTutorial {
    private String optional;
    private final int required;

    public static void main(String[] args) {
        new RequiredArgsConstructorTutorial(1);
    }
}
  • @RequiredArgsConstructor 어노테이션을 선언하여 final로 선언된 필드만 인수로 받는 생성자를 자동으로 생성할 수 있다.

@AllArgsConstructor

package com.devkuma.tutorial.lombok;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class AllArgsConstructorTutorial {
    private String string;
    private int number;

    public static void main(String[] args) {
        new AllArgsConstructorTutorial("devkuma", 999);
    }
}

@AllArgsConstructor 어노테이션을 선언하여 모든 필드를 인수받는 생성자를 자동으로 생성 할 수 있다.

static 팩토리 메소드 정의

package com.devkuma.tutorial.lombok;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor(staticName="of")
public class StaticFactoryOfTututorial {

    private final String required;
    private int optional;

    public static void main(String[] args) {
        StaticFactoryOfTututorial tututorial = StaticFactoryOfTututorial.of("devkuma");
    }
}
  • 각 어노테이션 staticName 속성을 지정하는 것으로, static 팩토리 메소드를 자동 생성 할 수 있다.

@Data

package com.devkuma.tutorial.lombok;

import lombok.Data;

@Data
public class DataTutorial {
    public static void main(String[] args) {
        DataTutorial a = new DataTutorial("devkuma");
        a.setNumber(300);

        DataTutorial b = new DataTutorial("devkuma");
        b.setNumber(300);

        System.out.println("a = " + a);
        System.out.println(a.equals(b));
    }

    private final String required;
    private int number;
}

실행 결과

a = DataTutorial(required=devkuma, number=300)
true
  • 클래스에 @Data 어노테이션이 선어되면, 다음의 어노테이션들을 모두 선언이 들어간다.
    • @ToString
    • @Getter
    • @Setter
    • @RequiredArgsConstructor
    • @EqualsAndHashCode

@Value

Main.java

package com.devkuma.tutorial.lombok;

import lombok.Value;

@Value
public class ValueTutorial {

    String string;
    int number;
}
  • 클래스에 @Value 어노테이션이 선언되면, 다음의 어노테이션들을 모두 선언이 들어간다.
    • @Getter
    • @ToString
    • @EqualsAndHashCode
    • @AllArgsConstructor
  • 그리고, 클래스 및 각 필드 final이 된다.
  • 그리고, 각 필드는 자동으로 접근제어자가 private이 된다.
  • 이는 DDD 값 객체가 된다.

@Builder

Main.java

package com.devkuma.tutorial.lombok;

import lombok.Builder;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;

@Builder
@ToString
public class BuilderTutorial {

    private String string;
    private int number;
    private List<String> list;

    public static void main(String[] args) {
        BuilderTutorialBuilder builder = BuilderTutorial.builder()
                .string("builder")
                .number(100)
                .list(Arrays.asList("devkuma", "araikuma"))
                .list(Arrays.asList("kimkc", "happykuma"));

        BuilderTutorial tutorial = builder.build();

        System.out.println(tutorial);
    }
}

실행 결과

BuilderTutorial(string=builder, number=100, list=[devkuma, araikuma])
  • @Builder 어노테이션이 선언되면, 그 클래스 빌더 클래스를 자동 생성 할 수 있다.

@Singular

디폴드로 그래로 놓으면 컬렉션 형식의 필드도 보통으로 덮어 쓰여져서 setter 메소드로 자동 생성된다. 추가 메소드로 자동 생성하고 싶다면 필드 @Singular 어노테이션을 선언한다.

package com.devkuma.tutorial.lombok;

import lombok.Builder;
import lombok.Singular;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;
@Builder
@ToString
public class SingularTutorial {

    private String string;
    private int number;
    @Singular("list")
    private List<String> list;

    public static void main(String[] args) {
        SingularTutorialBuilder builder = SingularTutorial.builder()
                .string("test")
                .number(100)
                .list("kimkc")
                .list("happykuma")
                .list(Arrays.asList("devkuma", "araikuma"));

        SingularTutorial tutorial = builder.build();

        System.out.println(tutorial);
    }

}

실행결과

SingularTutorial(string=test, number=100, list=[kimkc, happykuma, devkuma, araikuma])

@SneakyThrows

Main.java

package com.devkuma.tutorial.lombok;

import lombok.SneakyThrows;

public class SneakyThrowsTutorial {
    public static void main(String[] args) {
        method();
    }

    @SneakyThrows
    private static void method() {
        throw new Exception("test");
    }
}

실행 결과

Exception in thread "main" java.lang.Exception: test
	at com.devkuma.tutorial.lombok.SneakyThrowsTutorial.method(SneakyThrowsTutorial.java:12)
	at com.devkuma.tutorial.lombok.SneakyThrowsTutorial.main(SneakyThrowsTutorial.java:7)
  • @SneakyThrows 어노테이션을 메소드에서 선언하면, 체크 예외가 내부에서 발생되어도 throws를 넣지 않아도 된다.
    • sneaky 는 “몰래"라는 뜻이다.

지정한 예외만 무시

package com.devkuma.tutorial.lombok;

import lombok.SneakyThrows;

import java.io.IOException;

public class SneakyThrowsTutorial2 {
    public static void main(String[] args) {
        method();
    }

    @SneakyThrows(IOException.class)
    private static void method() {
        try {
            throw new Exception("test");
        } catch (Exception e) {
            // catch가 없으면 에러가 발생한다.
        }

        throw new IOException();
    }
}

실행 결과

Exception in thread "main" java.io.IOException
	at com.devkuma.tutorial.lombok.SneakyThrowsTutorial2.method(SneakyThrowsTutorial2.java:20)
	at com.devkuma.tutorial.lombok.SneakyThrowsTutorial2.main(SneakyThrowsTutorial2.java:9)
  • value로 예외 Class 객체를 전달하여, 지정한 예외만 무시할 수 있게 된다.

Logger 사용

build.gradle에 Logger 라이브러리를 추가한다.

dependencies {

    .. 생략 ...
		
 +   compile 'org.slf4j:slf4j-simple:1.7.30'
 }

LoggerTutorial.java 파일을 생성하여 코드를 아래과 같이 작성한다.

package com.devkuma.tutorial.lombok;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LoggerTutorial {

    public static void main(String[] args) {
        log.info("Hello Lombok Logger!!");
    }
}

실행 결과

[main] INFO com.devkuma.tutorial.lombok.LoggerTutorial - Hello Lombok Logger!!
  • 클래스에 @Slf4j 어노테이션을 선언하여 static final가 선언된 log 변수의 Logger를 사용할 수 있게 된다.

Slf4j 이외에도 다음 Logger를 제공하고 있다.

어노테이션 Logger 클래스
@CommonsLog org.apache.commons.logging.Log
@Log org.apache.commons.logging.Log
@Log4j org.apache.log4j.Logger
@Log4j2 org.apache.logging.log4j.Logger
@Slf4j org.slf4j.Logger
@XSlf4j org.slf4j.ext.XLogger