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 |
댓글