본문 바로가기
JAVA

Lombok 알아보기

by 봄석 2019. 9. 15.

Lombok 알아보기

 

Lombok이란??

- Java의 전형적인 Boilerplate code들을 없애주는 Annotation기반 라이브러리입니다.
- @Getter와 @Setter, @ToString 만으로도 강력합니다.

 

 

Lombok프로젝트에 추가하기

1. build.gradle(app)의 dependencies에 아래와 같이 추가

    compileOnly 'org.projectlombok:lombok:1.18.10'
    annotationProcessor "org.projectlombok:lombok:1.18.10" //require for anotation
    compileOnly 'org.glassfish:javax.annotation:10.0-b28' // package javax.annotation does not exist solved

 

2. Android Studio IDE에 플러그인을 설치해야 하기
- Settings > Plugins > Browse Repositories > lombok 검색 > Lombok Plugin 설치 > Android Studio ReStart

 

3. Annotation Processing 활성화 문제

플러그인 설치 후 재시작 시 아래의 에러가 발생할 수 있습니다.

 

 

일부 버전은 안내에 따라  Settings > Build > Compiler > Annotation Processors에서 해당 설정을 활성화할 수 있지만,

최신 버전의 Android Studio에서는 이 설정의 위치가 살짝 변경되어 그대로 따라가면 찾을 수 없습니다.

 

 

아래와 같은 설정이 필요합니다.

1.  Android Studio - File - Close Project
2. Configure - Settings - Build, Execution, Deployment - Compiler - Annotation Processors - Enable annotation processing. check

 


 


* Lombok 간단하게 사용해보기
1. @Data
- Lombok 적용 전, Boilerplate Code

전형적인 VO(Value Object )클래스 - 생성자와 Getter, Setter가 있습니다.

VO에 변수가 추가될 때 마다 Getter, Setter도 추가해줘야합니다

public class SimpleVO {
    private final String name;
    private int age;
    
    public SimpleVO(String name) {
    	this.name = name; 
    }
    public String getName() { 
    	return name; 
    }
    public int getAge() { 
    	return age; 
    }
    public void setAge(int age) { 
    	this.age = age; 
    }
    @Override public String toString() { 
    	return "SimpleVO) name=" + name + ", age=" + age; 
    } 
}

 

- @Data적용 후

public @Data class SimpleVO { private final String name; private int age; }

적용 전 코드에 비해 매우 깔끔해진걸 볼수있습니다.

 

위와 같이 적용 후, Structure를 확인해보면 다음과 같이 나타납니다.

코드의 양은 훨씬 적은데도 @Data 적용 전과 동일한 Structure를 가지고 있다.

코드의 양을 획기적으로 줄이고 가독성도 높은 코드를 만들 수 있는것입니다.

 

하지만 @Data 어노테이션은 

@ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor

을 모두 생성하기 때문에 사용에 주의가 필요합니다.

강력한 어노테이션인 만큼 그에 따른 부작용도 많습니다.

 

 

2. @Getter, @Setter
- @Data가 생성자 + getter + setter 라면,

@Getter, @Setter는 이름에서도 알 수 있 듯이 getter, setter를 만들어주는 Annotation입니다.

클래스레벨과 필드레벨 모두 사용가능합니다.

public class SimpleVO { 
  @Getter private final String name; 
  @Getter @Setter private int age; 
  
  public SimpleVO(String name) { 
  	this.name = name; 
  } 
}

 

Lombok으로 인해 생겨난 메소드들은 m자가 더 진하게 표시되어 있음을 알 수 있습니다

 

- 취향이 조금 갈리긴 하지만, 멤버변수의 경우 "m"을 prefix로 사용하는 프로그래머들도 있죠,

이 경우에, 아무런 처리 없이 @Getter, @Setter를 쓰게되면 getMName(), getMAge(), setMAge()가 생겨나는 참사가 발생합니다.

이런 경우를 대비하여 또 다른 Annotation이 있으니..... 그것은 바로....!

@Accessors의 prefix옵션을 사용하면 깔끔하게 해결 가능합니다. :-) 

@Accessors(prefix = "m")
public class SimpleVO { 
	@Getter private final String mName;
    @Getter @Setter private int mAge; 
    
    public SimpleVO(String name) {
    	this.mName = name; 
    } 
}

 

Getter  Setter의 공통 속성

    1. value 

         - 접근 제한을 설정할 수 있습니다.


    2. onMethod 

         - 메서드의 어노테이션을 작성할 수 있습니다.   


 

예)

public class GetSetObject {
  @Getter(value = AccessLevel.PACKAGE, onMethod = @__({@NonNull, @Id}))
  private Long id;
}

위의 코드를  lombok을 사용하지 않았다면 아래와 같을 것입니다.

class GetSetObjectOnMethod {
  private Long id;

  @Id
  @NonNull
  Long getId() {
    return id;
  }
}

 

그리고 getter와  setter 는 각각 다른 속성을 하나씩 가지고 있습니다.

 

 

Getter

    -  lazy

    -  필드의 값은 지연시킵니다.

@Getter(value = AccessLevel.PUBLIC, lazy = true)
private final String name = expensive();

private String expensive() {
  return "wonwoo";
}

lazy가 true일때는 무조건 final 필드어야만 합니다.

lazy 속성이 false 일 경우에는 객체를 생성할 때 expensive() 메서드를 호출하지만

속성이 true일 경우에는 getName() 메서드를 호출할 때 expensive() 메서드를 호출 합니다.

 

Setter 

    - onParam

@Setter(onParam = @__(@NotNull))
private Long id;

위의 코드를 lombok을 사용하지 않았다면 아래와 같을 것입니다.

class GetSetObjectOnParam {
  private Long id;

  public void setId(@NotNull Long id) {
    this.id = id;
  }
}

 

 

 

 

3. @ToString ,@EqualsAndHashCode

- toString()메소드를 간편하게 만들 수 있는 Annotation.

hashcode와 equals를 생성해주는 Annotation

 

 

 

@ToString()
public class SimpleVO {
    @Getter private final String name;
    @Getter @Setter private int age;

    public SimpleVO(String name) {
        this.name = name;
    }
}


//use case
SimpleVO simpleVO = new SimpleVO("yenarue");
simpleVO.setAge(27);
Log.v("Test", simpleVO.toString()); // "SimpleVO(name=yenarue, age=27)"

 

Getter  Setter의 공통 속성

    1. exclude 

         - 제외시킬 변수명을 작성합니다


    2. of 

         - 포함시킬 변수명을 작성합니다.

 

    3. callSuper 

         - 상위 클래스의 호출 여부를 묻는 속성입니다.


    4. doNotUseGetters 

         - getter 사용여부 인듯 하나 제대로 동작하지는 모르겠습니다.

@EqualsAndHashCode(of = "id")
@ToString(exclude = "name")
public class HashCodeAndEqualsObject {
  private Long id;
  private String name;
}

만일 위와 같이 작성하였다면 hasCode, equals, toString 모두 id만 존재하게  됩니다.

 

각각만이 가지고 있는 속성으로는

 @EqualsAndHashCode 는 onParam, @ToString 는 includeFieldNames 속성이 존재합니다

onParam 은 equals에 작성되며 위의 onParam 속성과 동일하므로 생략하겠습니다.

includeFieldNames는 toString의 필드 명을 출력할지 하지 않을지의 여부입니다.

만일 위의 코드로 includeFieldNames을 false로 한다면 다음과 같이 출력 됩니다.

HashCodeAndEqualsObject(null)

 

 

4. @Builder

다수의 필드를 가지는 복잡한 클래스의 경우, 생성자 대신에 빌더를 사용하는 경우가 많습니다.

빌더 패턴을 직접 작성해보면 코딩량이 의외로 상당함을 깨닫게 되는데 이 때, 

@Builder 어노테이션을 사용하면 자동으로 해당 클래스에 빌더를 추가해주기 때문에 매우 편리합니다.

@Builder
public class User {
    private Long id;
    private String username;
    @Singular
    private List score;
}

컬렉션으로 된 필드에는

@Singular 어노테이션을 선언해주면 모든 원소를 한 번에 넘기지 않고 원소를 하나씩 추가할 수 있습니다.

@Singular을 자세히 설명하면 ,

어노테이션은 컬렉션에 사용할 수 있는데 하나의 어떤 Object을 컬렉션에 추가 할 수도 있고  컬렉션 모두를 추가할 수 도 있습니다.

User user = User.builder()
  .id(1004)
  .username("god")
  .score(70)
  .score(80)
  .build();
// User(id = 1, username = god, scores=[70,80])

@Builder.Default

@Builder.Default 어노테이션은 @Builder 어노테이션을 사용할 경우 미리 선언된 프로퍼티의 값을 사용할 수 없다. 

@Builder
public class SingularExample {

    @Builder.Default
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

위 처럼 사용할 경우에는 name에 기본적으로 wonwoo 라는 값을 넣어두었습니다.

만약 @Builder.Default 어노테이션이 존재 하지 않는다면 해당 값을 초기화 되지 않습니다.

 

SingularExample singularExample  = SingularExample
    .builder()
    .build();

SingularExample(name=wonwoo, phones=[])

위와 같이 아무 값을 넣지 않았지만 name에는 wonwoo라는 값이 존재합니다.

만약 @Builder.Default 를 제거한다면 아래와 같은 값이 출력 될 것입니다.

 

 

@Builder.Default를 사용하지 않았을때는  아래와 같습니다.

@Builder
public class SingularExample {
    private String name = "wonwoo";

    @Singular
    private List<String> phones;
}

SingularExample(name=null, phones=[])

5. @XXXXArgsConstructor

위의 어노테이션은 생성자를 생성해주는 어노테이션입니다.

생성자를 생성해주는 어노테이션은 3가지가 있습니다.

 

  • @NoArgsConstructor
    • 디폴트 생성자를 생성합니다.
  •  @AllArgsConstructor 
    • 모든 필드의 생성자를 생성합니다.
  •  @RequiredArgsConstructor
    • 필수 생성자를 생성합니다.

 

속성

    1. staticName 

         - 위에서 @Data 어노테이션의 staticConstructor 와 동일합니다. static한 생성자를 만들어 줍니다.


    2. access 

         - 접근제한을 할 수 있습니다.

         - PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE 등으로 설정가능 합니다.   

 

    3. onConstructor 

         - 생성자에 어노테이션을 작성할 수 있습니다.
 

 

예)

@RequiredArgsConstructor(staticName = "of", onConstructor = @__(@Inject))
public class ConstructorObject {
  private final Long id;
  private final String name;
}

 

위의 클래스를   Lombok  어노테이션을 사용하지 않았다면  아래와 같을 것입니다.

class ConstructorObjectNot {
  private final Long id;
  private final String name;

  @Inject
  private ConstructorObjectNot(Long id, String name) {
    this.id = id;
    this.name = name;
  }
  public static ConstructorObjectNot of(Long id, String name) {
    return new ConstructorObjectNot(id, name);
  }
}

 

 

 

6. @NonNull

@NonNull 어노테이션을 변수에 붙이면 자동으로 null 체크를 해줍니다.

해당 변수가 null로 넘어온 경우, NullPointerException 예외가 발생함.

 

7.@Getter(lazy=true) 

동기화를 이용하여 최초 1회만 getter가 호출

 

 

8.@Synchronized

메소드에서 동기화 Lock을 설정

 

9.@var @val

스칼라, 코틀린 이외에 다른 언어들의 키워드와 동일하게 타입추론을 합니다.

public class ValAndVarTests {
  @Test
  public void valVarTest() {
    val arrVal = Arrays.asList(1, 2, 3, 4, 5);
    arrVal = new ArrayList<>(); // compile error

    var arrVar = Arrays.asList(1, 2, 3, 4, 5);
    arrVar = new ArrayList<>();
  }
}

val 경우에는 final 키워드가 생성됩니다.

그래서 다시 어사인을 할 경우에 컴파일 에러가 발생합니다.

마찬가지로 var는 final이 존재 하지 않으므로 다시 어사인이 가능합니다.

 

위의 코드를  lombok을 사용하지 않으면 아래와 같습니다.

final List arrVal1 = Arrays.asList(1, 2, 3, 4, 5);
arrVal1 = new ArrayList<>();

List arrVar1 = Arrays.asList(1, 2, 3, 4, 5);
arrVar1 = new ArrayList<>();

 

10. @Cleanup

자동 리소스 관리 : close()번거 로움없이 안전하게 메서드를 호출하십시오 .

 

11. @Value

불변 클래스를 쉽게 생성할수 있습니다.

아주 간단하게 클래스 레벨에 @Value 어노테이션만 선언하면 사용할 수 있습니다.

@Value
public class ValueExample {
    String name;
    String email;
}

기본적으로 위와 같이 선언했을 경우 필드는 기본적으로 private 접근제어자와 final 이 붙은 상수가 됩니다.

final이 붙어 setter는 존재하지 않고 getter만 존재하게됩니다. 클래스 자체도 final class로 상속을 받을 수 없습니다

@Data 어노테이션과 비슷하게 equals, hashCode, toString을 함께 만들어 줍니다.

@Data 어노테이션이 비슷하지만 불변인 정도? 그 정도로만 생각해도 문제없을 듯 합니다.

기본생성자는 만들어 주지만 private 생성자로 만들어 줍니다.

 

 

lombok을 사용안하고 작성했다면 아래와 같을 것입니다.

public final class ValueExample {
    private final String name;
    private final String email;

    public ValueExample(String name, String email) {
        this.name = name;
        this.email = email;
    }

    private ValueExample() {
        this.name = null;
        this.email = null;
    }

    public String getName() {
        return this.name;
    }

    public String getEmail() {
        return this.email;
    }
    // equals, hashCode, toString
}

 @Value어노테이션의 속성으로는 staticConstructor 가 존재하는데 static한 생성자를 생성해주는 속성입니다

이 속성을 사용할 경우에는 모든 생성자가 private 으로 되고 정의해둔 해당 static 메서드만 사용할 수 있습니다.

@Value(staticConstructor = "of")
public class ValueExample {
    String name;
    String email;
}

ValueExample ve = ValueExample.of("wonwoo", "wonwoo@test.com");

 

12. @SneakyThrows

Exception 발생시 체크된 Throable로 감싸서 전달

 

13. @Accessors

해당 어노테이션은 클래스 레벨에 사용할 경우 setter를 체이닝하게 만들 수 있는 어노테이션입니다.

prefix 와 두개의 속성이 있는데 다른점은 메서드명이 달라진다는 것뿐이지 하는 역할을 같습니다.

 

chain 속성

@Accessors(chain = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

위와 같이 chain 옵션은 사용할 경우에는 setter가 만들어 질 때 해당 클래스를 다시 리턴하는 체이닝방식으로 만들어 집니다.

그렇다해서 불변은 아니고 해당 오브젝트를 다시  리턴하는  것 뿐입니다.

 

대략 아래와 같이 setter가 만들어 진다고 생각하면 됩니다.

public AccessorsExample name(String name) {
    this.name = name;
    return this;
}

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

fluent 속성

@Accessors(fluent = true)
@Data
public class AccessorsExample {

    private String name;
    private String email;
}

달라지는 것은 메소드명 뿐입니다.

getter와 setter 모두 달라집니다.

AccessorsExample accessorsExample = new AccessorsExample();
AccessorsExample emailAccessors = accessorsExample.email("wonwoo@test.com");
AccessorsExample nameAccessors = accessorsExample.name("wonwoo");
String name = accessorsExample.name();

* Lombok Annotation 참고자료
- 공식 홈페이지 : https://projectlombok.org/index.html
- 공식 홈페이지의 Reference Doc : https://projectlombok.org/features/
- 플러그인 안내 : https://plugins.jetbrains.com/androidstudio/plugin/6317-lombok-plugin

'JAVA' 카테고리의 다른 글

Java 8 - Interface바뀐점을 알아보기  (0) 2019.10.11
equals,hashCode 알아보기  (2) 2019.10.01
boilerplate  (2) 2019.09.15
Java - Atomic변수  (0) 2019.09.02
Java - Garbage Collection  (0) 2019.09.02

댓글