자바를 개발하다 보면 Math, Collections 같은 유틸리티 클래스를 만들 일이 많습니다. 일반적으로 유틸리티 클래스는 정적(static) 메서드만 포함하며, 객체로 생성할 필요가 없습니다.
Lombok에서는 이러한 유틸리티 클래스를 간편하게 정의할 수 있도록 @UtilityClass
어노테이션을 제공하며, 이를 통해 자동으로 인스턴스 생성을 막고 모든 멤버를 static으로 변환할 수 있습니다.
이번 포스팅에서는 @UtilityClass
의 개념과 사용법, 컴파일 후 코드 변환 과정, 그리고 유틸리티 클래스 설계 시 고려해야 할 점들을 정리해보겠습니다.
1. @UtilityClass
란?
@UtilityClass
는 Lombok v1.16.2에서 도입된 어노테이션으로, 객체 생성이 불필요한 유틸리티 클래스를 쉽게 정의할 수 있도록 도와줍니다.
유틸리티 클래스는 상태(State)가 없고, 행위(Behavior)만을 가지는 클래스를 의미합니다. 대표적으로 Math
클래스처럼 모든 메서드가 static
으로 정의되어 있으며, 인스턴스를 생성할 필요가 없습니다.
1.1 주요 기능
- 클래스를 자동으로
final
로 변환 → 상속 방지 - 모든 필드와 메서드를
static
으로 변환 → 객체 없이 사용 가능 private
생성자 자동 생성 → 객체 생성 불가능하도록 제한- 명시적인 생성자 작성 방지 → 생성자를 수동으로 만들 수 없음
- 내부 클래스도 자동
static
처리 → 유틸리티 성격 유지
2. @UtilityClass
사용법
Lombok의 @UtilityClass
를 사용하여 유틸리티 클래스를 작성하면, 클래스 내부의 모든 필드와 메서드가 자동으로 static
이 됩니다.
import lombok.experimental.UtilityClass;
@UtilityClass
public class UtilityClassExample {
private final int CONSTANT = 5; // static으로 자동 변환됨
public int addSomething(int in) {
return in + CONSTANT;
}
}
CONSTANT
필드는 자동으로static final
로 변환됩니다.addSomething(int in)
메서드도 자동으로static
이 됩니다.- 해당 클래스를 생성하려고 하면 컴파일 에러 발생 → 객체 생성을 원천적으로 차단
@UtilityClass
를 사용하면 객체를 생성할 필요 없이 클래스명.메서드명() 형식으로 접근 가능합니다.
int result = UtilityClassExample.addSomething(10); // 객체 없이 사용 가능
3. Lombok이 @UtilityClass
를 컴파일하면?
@UtilityClass
어노테이션을 사용하면, Lombok이 컴파일 과정에서 자동으로 필요한 코드들을 추가해줍니다.
3.1 컴파일 후 변환된 코드
아래는 위의 Lombok 코드를 컴파일했을 때 생성되는 순수 자바 코드입니다.
public final class UtilityClassExample { // final 키워드 추가 (상속 방지)
private static final int CONSTANT = 5; // 모든 멤버 자동 static 적용
private UtilityClassExample() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static int addSomething(int in) {
return in + CONSTANT;
}
}
- 클래스에
final
키워드 추가 → 상속 불가 - 모든 필드와 메서드를
static
으로 자동 변환 - private 생성자 추가 → 강제로 예외 발생 (
UnsupportedOperationException
) - 인스턴스화 방지 → 객체 생성이 불가능
3.2 자동으로 추가된 기능
1) final
키워드 추가
- 유틸리티 클래스는 상속할 필요가 없으므로
final
로 자동 변환됩니다. - 하위 클래스를 생성할 수 없도록 제한됩니다.
2) 모든 멤버를 static
으로 변환
- 필드(
CONSTANT
)와 메서드(addSomething()
)가 자동으로static
으로 설정됩니다. - 객체를 생성하지 않고도
클래스명.메서드명()
형태로 호출 가능합니다.
3) private
생성자 자동 추가
- 유틸리티 클래스는 객체를 생성할 필요가 없으므로, 인스턴스화를 방지해야 합니다.
- Lombok은 자동으로 private 생성자를 추가하고,객체가 생성될 경우 UnsupportedOperationException을 던지도록 구현합니다.
private UtilityClassExample() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
즉, 생성자가 private
로 설정되면서 객체 생성이 원천적으로 차단됩니다.
아래와 같이 객체를 생성하려고 하면 컴파일 오류가 발생합니다.
UtilityClassExample util = new UtilityClassExample(); // 컴파일 오류 발생!
4. 유틸리티 클래스 설계 시 고려해야 할 점
유틸리티 클래스는 상태(State)가 없고, 행위(Behavior)만 존재하는 클래스입니다. 무분별하게 사용하면 객체지향적 설계에서 벗어날 가능성이 크며, 유지보수성이 떨어질 수 있습니다.
4.1 유틸리티 클래스 장점
- 객체 없이 호출 가능 → 불필요한 인스턴스 생성 방지
- 일관성 유지 → 특정 로직을 한 곳에서 관리 가능
- 재사용성 증가 → 여러 곳에서 동일한 메서드 활용 가능
4.2 유틸리티 클래스의 단점
- 객체지향 프로그래밍 원칙에서 벗어남
- 모든 메서드가
static
이므로 객체지향적이지 않음 (절차지향적 설계)
- 모든 메서드가
- 단위 테스트가 어렵다
static
메서드는 Mocking이 어렵고, 변경이 힘듦- 테스트 가능한 코드로 만들려면 의존성 주입(DI)을 고려해야 함
- 강결합 발생 가능성
static
메서드를 남용하면 다른 클래스들과 강한 의존성을 가짐- 유지보수 시 코드 변경이 어려워질 수 있음
따라서, 유틸리티 클래스 사용이 적절한 경우인지 신중하게 판단해야 합니다.
5. 결론
@UtilityClass
는 Lombok을 활용하여 유틸리티 클래스를 간편하게 정의할 수 있도록 도와준다.- 자동으로
final
클래스,private
생성자,static
필드 및 메서드를 추가하여객체 생성이 불가능한 유틸리티 클래스를 만든다. - 유틸리티 클래스는 상태(State)가 없고, 행위(Behavior)만 존재해야 하므로필드에 상태값을 저장하는 것은 지양해야 한다.
- 무분별한 사용은 객체지향적 설계에서 벗어나며, 유지보수성과 테스트성을 저하시킬 수 있다.
- 꼭 필요한 경우에만 유틸리티 클래스를 사용하고,객체지향적인 설계를 우선 고려하는 것이 바람직하다.
6. 참고
'BackEnd > JAVA' 카테고리의 다른 글
[JAVA] equals()와 hashCode() 완벽 정리 (1) | 2025.02.24 |
---|---|
[JAVA] 익명 클래스 (Anonymous Class) 정리 (1) | 2025.02.20 |
[JAVA] 얕은 복사 (Shallow Copy) VS 깊은 복사 (Deep Copy) (2) | 2025.02.06 |
[JAVA] 와일드카드 (Generic Wildcards) 정리 (1) | 2025.02.05 |
[JAVA] 제네릭 (Generic) 정리 (1) | 2025.01.26 |