[Java] Lombok 소화하기
by 배부른코딩로그💡 귀찮은 반복 작업을 완벽하게 해소해주는 롬복(Lombok). 한 번 제대로 정리해보자!
목표
- Lombok 라이브러리가 무엇인지 왜 사용하는지 설명할 수 있다.
- Lombok 관련 애노테이션 종류와 각각의 쓰임을 설명할 수 있다.
- Lombok 관련 예제를 정리하는 공간으로 활용할 수 있다.
롬복은 자바 개발에 널리 사용되는 매우 편리한 라이브러리이다
생성자나 Getter, Setter 등 보일러 플레이트성 코드를 매번 수동으로 작성할 필요가 없기 때문일 것이다.
작성을 안했는데 언제 생성이 되는 것일까? 이를 컴파일 타임(Compile-time)에서 자동으로 생성해준다.
게다가, IDE 내에서도 모든 메서드에 적상적으로 접근할 수 있다.
롬복에서 일반적으로 사용하는 애노테이션은 다음과 같다.
- @Data
- @Builder and @SuperBuilder
- @NonNull
- @Cleanup
- @RequiredArgsConstructor(onConstructor = @__(@Inject))
var, val
Lombok
@Data
JPA에서 DTO를 만들 때, 주로 사용하는 애노테이션이다.
이에 대한 예제는 다음과 같다.
@Data public final class Person { private String firstName; private String LastName; private int age; private Company company; }
@Data 애노테이션에는 아래와 같은 애노테이션 집합이 하나에 묶여 있음을 알아야 한다.
- @Getter
- @Setter
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
Getter/Setter와 toString(), hashCode(), equals() 등 일반적인 메서드를 기본적으로 생성한다.
따라서, 사용자 지정으로 toString() 또는 equals()에 구현할 수 있는 유연성을 갖추고 싶다면
클래스 정의에 직접 추가하면 Lombok 기본 보일러 플레이트 내용을 덮어쓸 수 있다.
@Builder and @SuperBuilder
@Builder
Builder 패턴의 보일러 플레이트이며, 클래스에 필드가 여러 개 있어 초기화가 복잡할 때 유용하다.
사용 예제는 다음과 같다.
@Builder public final class Person { private String firstName; private String LastName; private int age; private Company company; }
Builder 패턴을 사용한 객체 생성 예제는 다음과 같다.
final Person codify = Person.builder .firstName('dify') .lastName('Co') .age(30) .build();
Builder 패턴의 장법은 각 파라미터에 따른 생성자 코드를 작성하지 않아도 됨에 있다.
객체 생성 초기화 시, lastName이 필요가 없다면 없는대로 생성이 가능하다.
그런데, @Builder는 상속에서 어떻게 작동될까?
@SuperBuilder를 사용하여 상속에 따른 Builder 패턴을 적용할 수 있다.
@SuperBuilder
@SuperBuilder public final class Person { private String firstName; private String LastName; private int age; private Company company; } @SuperBuilder public class Staff { private String position; private int yearOfExperience; private List<String> abilities; }
final Person codify = Staff.builder() .firstName('dify') .lastName('Co') .age(30) .position("developer") .yearOfExperience(3) .abilities(Collections.singletonList("Spring", "React")) .build();
위와 같이 @Data와 @Builder의 함께 사용하면, 컴파일 중 오류가 발생한다.
그 이유는 @Data와 @Builder 만 사용했기 때문에 기본 생성자가 누락되었기 때문이다.
이 경우, Java의 직렬화/역직렬화 라이브러리가 실행됨에 기본 생성자가 누락되어 역직렬화 단계에서 오류가 난다.
이를 위해 @NoArgsConstructor 혹은 @AllArgsConstructor를 추가한다.
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Company { private String name; public String changeA2B() { Company company = Company.builder().name("CODIFY").builder(); ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(); Company company2 = mapper.readValue(json, Company.class); System.out.println(company2); // Company [name=CODIFY] } }
@NoArgsConstructor 및 @AllArgsConstructor 애노테이션 이름에서 의미가 드러난다 : )
// @NoArgsConstructor public Company() {} // @AllArgsConstructor, 필드가 n개면, n개의 args public Company(String name) { this.name = name; }
@NonNull
롬복은 @NonNull 애노테이션을 통해 null-check도 손쉽게 도와준다.
if (param == null) throw new NullPointerException("Cannot invoke ~ because 'param' is null");
컴파일시, 메서드 스코프 맨 위에 삽입된다.
public void printPerson(@NonNull Person person) { System.out.println(person.toString()); // Here, no need to worry about NPE! } public String upperCase(@NonNull String str) { return str.toUpperCase(); // Here, no need to worry about NPE! }
단, @NonNull 애노테이션은 롬복이 생성한 생성자나 메서드에서만 작동한다는 점이다
다음과 같은 경우 @NonNull이 무시된다.
@Getter @Setter public class LombokTest { @NonNull private Integer id; @NonNull private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
@Getter와 @Setter를 사용하지 않기 위해 Override 처리한 getName과 setName의 경우가 이에 해당된다.
public class LombokTest { @NonNull private Integer id; @NonNull private String name; @NonNull public Integer getId() { return this.id; } public void setId(@NonNull Integer id) { if (id == null) { throw new NullPointerException("id is null"); } this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
컴파일 시 위와 같이 작성됨을 주의해야 한다.
@ Cleanup
@Cleanup 애노테이션은 스코프 내에서 리소스를 참조 완료 후 리소스를 릴리즈함을 간편하게 할 수 있다.
File I/O를 통해 간단한 예제를 가져와봤다.
public byte[] read(String infile, String outfile) { @Cleanup InputStream in = new FileInputStream(infile); @Cleanup OutputStream out = new FileOutputStream(outfile); byte[] b = new byte[10000]; while (true) { int r = in.read(b); if (r == -1) break; out.write(b, 0, r); } return b; }
롬복은 생성된 InputStream 및 OutputStream은 참조 후 자동으로 닫아준다.
// if else check, then in.close(); // if else check, then out.close();
try~catch~finally를 통해 닫을 때 보일러 플레이트성 코드를 매번 기입했는데,
이를 애노테이션 한 방에 처리를 할 수 있다는 점에서 아주 유용하다.
@RequiredArgsConstructor
여기서는 Singleton Pattern과의 "통합"을 위한 다른 용도를 추가적으로 정리한다.
- @RequiredArgsConstructor(onConstructor = @__(@Inject)): 해당 생성자에 @Inject 을 생성.
- @RequiredArgsConstructor(onConstructor = @_(@Autowired)): 해당 생성자에 @Autowired 를 생성.
출처
- Project Lombok, Reinier Zwitserloot & Roel Spilker, 2009-2022
- 빌더 패턴(Builder Pattern), 기계인간(John Grib), 2018.02.12~2021-10-16
- [Java] - Project Lombok(롬복), 주발2(in Tistory), 2021.06.20
단어
- *보일러플레이트 코드(Boilerplate code)
컴퓨터 프로그래밍에서 최소한의 변경으로 여러 곳에서 재사용되며, 반복적으로 비슷하게 작성되는 코드를 의미한다.
Last Updated. 2022. 06. 07.
블로그의 정보
배부른코딩로그
배부른코딩로그