Lombok이란?
롬복(Lombok)은 자바 어노테이션 라이브러리로, 여러 코드를 자동 생성해주는 역할을 한다. 공식적인 설명에는 다음과 같이 되어 있다.
Java annotation library which helps to reduce boilerplate code.
여기서 보일러플레이트(boilerplate)란 변경이 거의 없으며 여러 곳에서 재사용될 수 있는 코드를 뜻한다. 개발을 하다보면 자바 생성자처럼, 꼭 만들어야 하고 여러 곳에서 사용되지만 정형화된 코드이기 때문에 직접 일일히 짜기에는 번거로운 코드들이 있다. Lombok은 이런 코드들을 간단하게 어노테이션으로 사용할 수 있게 만들어준다.
@Data 어노테이션의 구성 요소
이 중 @Data
어노테이션은 @Getter
, @Setter
, @ToString
, @EqualsAndHashCoded
, @RequiredArgsConstructor
와 @Value
를 전부 합쳐놓은 종합적인 어노테이션이라고 할 수 있다.
@Getter와 @Setter
@Getter
와 @Setter
는 이름 그대로 모든 필드의 값을 외부로 꺼내는 메서드(getter), non-final인 필드에 값을 주입하는 메서드(setter)를 만드는 역할을 한다. 클래스 위에 해당 어노테이션들을 직접 명시할 수도 있다.
@Getter @Setter
public class ExampleClass {
private String example;
}
그러면 lombok은 서버가 실행되는 순간에 해당 메서드들을 만들어준다. 결과적으로 아래와 같이 메서드들이 만들어지고, 이 메서드들을 사용하는 것이라고 보면 된다.
public class ExampleClass {
private String example;
// Getter
public String getExample() {
return example;
}
// Setter
public void setExample(String example) {
this.example = example;
}
}
@ToString
객체가 가지고 있는 정보나 필드 값들을 문자열로 만들어 리턴하는 역할을 한다. 여기서 중요한 점은, 단순히 정수형의 필드 하나를 String으로 바꾸어서 반환하는게 아니다. 클래스 이름, 필드명, 필드 값 등을 모두 문자열로 만들어서 반환한다. 즉 클래스 위에 선언된 @ToString
어노테이션은 아래와 같은 toString()
메서드를 직접 만들어주는 역할을 한다. 객체의 정보를 모두 출력해주는 메서드인 만큼, 디버깅 및 테스팅에 사용된다.
public class ExampleClass {
private int example;
@Override
public String toString() {
return "ExampleClass{" +
"example=" + example +
'}';
}
}
@EqualsAndHashCoded
equals()
메소드와 hashCode()
메소드를 생성한다. 그럼 equals() 메소드와 hashCode() 메소드는 무엇일까? 인터넷에는 다음과 같은 정보가 돌아다니고 있다.
- equals: 두 객체의 내용이 같은지, 동등성(equality) 를 비교하는 연산자
- hashCode: 두 객체가 같은 객체인지, 동일성(identity) 를 비교하는 연산자
과연 이게 맞는 말일까? lombok 공식 웹페이지에서 제공하는 예제는 아래와 같다.
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
if (!other.canEqual((Object)this)) return false;
if (!super.equals(o)) return false;
if (this.width != other.width) return false;
if (this.height != other.height) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + super.hashCode();
result = (result*PRIME) + this.width;
result = (result*PRIME) + this.height;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Square;
}
}
위의 코드를 기반으로 알 수 있는 equals()
메소드의 정의는, "두 객체가 같은 클래스의 인스턴스인지를 확인하고, 같은 클래스라면 각각의 필드 값을 비교한다"이다. 즉, 같은 클래스이고 같은 객체 내부 상태를 가지고 있어야 true를 반환한다.
또한 hashCode()
메소드의 역할은 "두 객체의 객체의 내부 필드 상태를 비교한다"이다. 즉, 같은 객체라면 반드시 hashCode 값이 같겠지만, 서로 다른 객체라고 해도 hashCode 값이 다르다는 보장은 없다. 객체의 내부 상태만 같으면 되기 때문이다. 따라서 인터넷에 돌아다니는 두 메소드에 대한 설명은 실제와는 정반대라는 것을 알 수 있다. 더 자세한 내용은 아래 공식 홈페이지에서 확인 가능하다.
@RequiredArgsConstructor
초기화되지 않은 final
필드나 @NonNull
어노테이션이 붙은 필드에 대해 하나의 파라미터만 받는 생성자를 생성해준다. 이때 두 필드에 대해 하는 일이 조금 다르다.
- 모든
final
필드는 매개변수를 받는다. @NonNull
으로 표시되어 있고 선언된 위치에서 초기화되지 않은 필드의 경우도 매개변수를 받는다. 이때 명시적인 null 체크도 동시에 생성된다.@NonNull
으로 표시된 필드의 매개변수 중 어느 하나라도 null이 포함되어 있으면NullPointerException
을 throw한다.