lombok

Lombok

Lombok 이란?

Lombok 은 에디터와 빌드 도구에 자동적으로 연결되어, 소스에 양념을 쳐주는 자바 라이브러리이다.
이 라이브러리를 사용하면 getter, setter 등 자바 코드를 짤 때 항상 반복적으로 생성하는 소스 작성을 줄일 수 있다.

Lombok 준비하기

  • Lombok 라이브러리를 프로젝트에 추가한다.

  • IDE 에 Lombok 플러그인을 추가한다.

    1. (IntelliJ) Settings > Plugins > Browse Repositories : Lombok Plugin 설치
      IDEA lombok 설치하기

    2. Settings > Build, Execution, Deployment > Compiler > Annotation Processors
      Enable annotation processing 활성화
      Annotation Processors 활성화 시키기

Lombok Annotations 맛보기

@Getter, @Setter

  • getter / setter 메서드 생성
  • 생성되는 메서드명 - getFieldName / setFieldName
  • @Getter 적용된 필드의 타입이 boolean일 경우, 메서드명은 isFieldName
  • Class 에 @Getter/@Setter를 적용하면 static을 제외한 모든 필드에 적용시킨 것과 동일
  • AccessLevel 을 명시하지 않으면 public
    • AccessLevel: PUBLIC PROTECTED PACKAGE PRIVATE / NONE
    • 특정 필드만 적용에서 제외시킬때 NONE
/* With Lombok */

@Getter @Setter
public class UserProfile {
    @Setter(AccessLevel.PROTECTED) private int userSeq;
    private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;

    public int getUserSeq() {
        return this.userSeq;
    }

    public String getUserName() {
        return this.userName;
    }

    public String getEmail() {
        return this.email;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    protected void setUserSeq(int userSeq) {
        this.userSeq = userSeq;
    }
}

@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor

@NoArgsConstructor

  • 파라미터가 없는 생성자를 만든다.
  • 초기 값이 필요한 final 필드가 있을 경우, 컴파일 에러
    • @NoArgsConstructor(force=true) 를 하면, 컴파일 에러를 내지 않고 0 / false / null 로 초기화
/* With Lombok */

@NoArgsConstructor(force=true)
public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;

    public UserProfile() {
        this.userSeq = 0;
        this.userName = null;
    }
}

@RequiredArgsConstructor

  • 아래 해당하는 필드에 대한 파라미터를 갖는 생성자를 만든다.
    • 초기화가 필요한 final 필드
    • @NonNull이 지정된 필드
/* With Lombok */

@RequiredArgsConstructor
public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;

    @java.beans.ConstructorProperties({"userSeq", "userName"})
    public UserProfile(int userSeq, String userName) {
        this.userSeq = userSeq;
        this.userName = userName;
    }
}

@AllArgsConstructor

  • 모든 필드를 파라미터로 갖는 생성자
/* With Lombok */

@AllArgsConstructor
public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private final int userSeq;
    @NonNull private String userName;
    private String email;

    @java.beans.ConstructorProperties({"userSeq", "userName", "email"})
    public UserProfile(int userSeq, String userName, String email) {
        this.userSeq = userSeq;
        this.userName = userName;
        this.email = email;
    }
}

staticName

  • e.g. @RequiredArgsConstructor(staticName="of")는 of라는 이름의 static 팩토리 메서드 생성
/* With Lombok */

@RequiredArgsConstructor(staticName = "of")
public class TypeProfile<T> {
    private T type;
}
/* Vanila Java */

public class TypeProfile<T> {
    @NonNull  private T type;

    @java.beans.ConstructorProperties({"type"})
    private TypeProfile(T type) {
        this.type = type;
    }

    public static <T> TypeProfile<T> of(T type) {
        return new TypeProfile<T>(type);
    }
}
TypeProfile tHello = TypeProfile.of("HELLO");
TypeProfile tFalse = TypeProfile.of(false);
TypeProfile t1 = TypeProfile.of(1);

@ToString

toString() 메서드 구현

  • 기본 대상은 non-static 필드 모두
    • ClassName(FieldName1=값1, FieldName2=값2…)
    • includeFieldNames = false 프린트 시 FieldName 제외 :: ClassName(값, 값…)
  • exclude="FieldName" or exclude={"FieldName1", "FieldName2"} - 원하지 않는 필드를 제외
/* With Lombok */

@ToString
public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;

    public String toString() {
        return "UserProfile(userSeq=" + this.userSeq + ", userName=" + this.userName
                            + ", email=" + this.email + ")";
    }
}

@EqualsAndHashCode

hashCodeequals를 생성한다.

  • @EqualsAndHashCode(of={"FieldName1", "FieldName2"}) 필요한 필드를 명시하거나,
  • @EqualsAndHashCode(exclude={"FieldName1", "FieldName2"}) 제외할 필드를 명시
  • 수퍼클래스가 있다면, callSuper=true 명시 필요
/* With Lombok */

@EqualsAndHashCode(of={"userSeq", "userName"})
public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;
}
/* Vanila Java */

public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof UserProfile)) return false;
        final UserProfile other = (UserProfile) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.userSeq != other.userSeq) return false;
        final Object this$userName = this.userName;
        final Object other$userName = other.userName;
        if (this$userName==null? other$userName != null: !this$userName.equals(other$userName)) 
            return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        result = result * PRIME + this.userSeq;
        final Object $userName = this.userName;
        result = result * PRIME + ($userName == null ? 43 : $userName.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof UserProfile;
    }
}

@Data

여러 Annotation을 한 번에!

  • @ToString
  • @EqualsAndHashCode
  • @RequiredArgsConstructor
  • 모든 필드에 @Getter
  • final 이 아닌 필드에 @Setter

@Cleanup

현재 변수의 스코프가 종료되기 전에 리소스가 clean up 되도록 보장해준다.

/* With Lombok */

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}
/* Vanila Java */

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

@Builder, @Builder.Default, @Singular

@Builder 빌더 패턴을 적용한 객체 생성 메서드를 만든다.
@Builder.Default 객체 생성시 field 기본값 설정
@Singular @Builder가 적용된 클래스의 필드가 Collection 타입 일 때, 해당 필드의 값을 추가하는 메서드 생성

/* 빌더 호출 */
UserProfile user00 = UserProfile.builder().userSeq(0)
                                          .userName("John Doe")
                                          .email("user00@lombok.com")
                                          .value("one")
                                          .value("two").build();
// UserProfile(userSeq=0, userName=John Doe, email=user00@lombok.com, age=20, values=[One, Two])
/* With Lombok */

@Builder
public class UserProfile {
    private final int userSeq;
    private String userName;
    private String email;
    @Builder.Default private int age = 20;
    @Singular private List<String> values;
}
/* Vanila Java */

public class UserProfile {
    private final int userSeq;
    private String userName;
    private String email;
    @Builder.Default private int age = 20;
    private List<String> values;

    @java.beans.ConstructorProperties({"userSeq", "userName", "email", "age", "values"})
    UserProfile(int userSeq, String userName, String email, int age, List<String> values) {
        this.userSeq = userSeq;
        this.userName = userName;
        this.email = email;
        this.age = age;
        this.values = values;
    }

    public static UserProfileBuilder builder() {
        return new UserProfileBuilder();
    }

    public static class UserProfileBuilder {
        private int userSeq;
        private String userName;
        private String email;
        private int age;
        private ArrayList<String> values;

        UserProfileBuilder() {
        }

        public UserProfileBuilder userSeq(int userSeq) {
            this.userSeq = userSeq;
            return this;
        }

        public UserProfileBuilder userName(String userName) {
            this.userName = userName;
            return this;
        }

        public UserProfileBuilder email(String email) {
            this.email = email;
            return this;
        }

        public UserProfileBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserProfileBuilder value(String value) {
            if (this.values == null) this.values = new ArrayList<String>();
            this.values.add(value);
            return this;
        }

        public UserProfileBuilder values(Collection<? extends String> values) {
            if (this.values == null) this.values = new ArrayList<String>();
            this.values.addAll(values);
            return this;
        }

        public UserProfileBuilder clearValues() {
            if (this.values != null)
                this.values.clear();

            return this;
        }

        public UserProfile build() {
            List<String> values;
            switch (this.values == null ? 0 : this.values.size()) {
                case 0:
                    values = java.util.Collections.emptyList();
                    break;
                case 1:
                    values = java.util.Collections.singletonList(this.values.get(0));
                    break;
                default:
                    values = java.util.Collections.unmodifiableList(new ArrayList<String>(this.values));
            }

            return new UserProfile(userSeq, userName, email, age, values);
        }

        public String toString() {
            return "UserProfile.UserProfileBuilder(userSeq=" + this.userSeq + ", userName=" + this.userName + ", email=" + this.email + ", age=" + this.age + ", values=" + this.values + ")";
        }
    }
}

@Log / @Slf4j …

해당 Class 명으로 로거 객체를 생성한다.

Lombok Annotations 주의사항

@NoArgsConstructor

  • force=true는 필드에 @NonNull 같은 제약조건을 무시하므로 주의

@AllArgsConstructor, @RequiredArgsConstructor

  • 해당 어노테이션 사용은 지양하고, 필요한 Field로 Constructor 를 직접 만들자
    • 같은 타입을 필드 선언 순서를 나중에 바꿀 경우 문제가 발생 소지가 있기 때문
@AllArgsConstructor
public class UserProfile {
    private int userSeq;
    private String userName;
    private String email;
    private String job;
}
UserProfile u = new UserProfile(1, "John", "lombok@study.com", "developer");
  • Field 갯수가 많아서 Constructor 를 한땀 한땀 만드는게 고통이라면, @Builder 를 사용해보자

@EqualsAndHashCode

  • 항상 @EqualsAndHashCode(of={“필드명”}) 형태로 동등성 비교에 필요한 필드를 명시하자

@Data

  • @RequiredArgsConstructor @EqualsAndHashCode 를 조합한 애너테이션이기 때문에 각 애너테이션의 주의사항이 포함된다.

  • @AllArgsConstructor 를 같이 적용시키면 @RequiredArgsConstructor 는 적용되지 않는다.

@Builder

  • private 생성자를 구현하여 @Builder 를 지정하자
    @Builder 를 Class에 적용시키면 생성자의 접근 레벨이 default이기 때문에, 동일 패키지 내에서 해당 생성자를 호출할 수 있는 문제가 있다.
/* With Lombok */

public class UserProfile {
    private final int userSeq;
    private String userName;
    private String email;

    @Builder
    private UserProfile(int userSeq, String userName, String email) {
        this.userSeq = userSeq;
        this.userName = userName;
        this.email = email;
    }
}
/* Vanila Java */

public class UserProfile {
    private final int userSeq;
    private String userName;
    private String email;

    private UserProfile(int userSeq, String userName, String email) {
        this.userSeq = userSeq;
        this.userName = userName;
        this.email = email;
    }

    public static UserProfileBuilder builder() {
        return new UserProfileBuilder();
    }

    public static class UserProfileBuilder {
        private int userSeq;
        /* ...생략... */
    }
}

lombok.config 로 사용하지 않을 Annotation 관리하기

# lombok.config
lombok.allArgsConstructor.flagUsage=error
# experimental 전체 금지
lombok.experimental.flagUsage=error

Reference

Lombok

Lombok javadoc
Lombok features
Lombok 사용상 주의점 ⭐️

Builder Pattern

builder pattern ⭐️

Java Immutable

Oracle Java docs > immutable ⭐️
Oracle Java docs > access control