인스턴스화를 막으려거든 private 생성자를 사용하라
작성자: 프람
작성일시: 2024_04_17
내용: Effective Java 3/E 2장-아이템4
01. 인스턴화가 불필요한 경우
자바로 코드를 작성하다보면, 종종 Util class[1]를 만드는 경우가 있다.
그 예시를 JDK 표준 API에서 볼 수 있는데, 대표적으로 'java.lang.Math'가 있다.
public final class Math {
...생략
public static double sqrt(double a) {
return StrictMath.sqrt(a);
}
public static int abs(int a) {
return (a < 0) ? -a : a;
}
public static double floor(double a) {
return StrictMath.floor(a);
}
생략...
}
위 java.lang.Math의 예시 코드와 같이 별도의 인스턴스 변수를 가지지 않고 정적 메서드만을 가진다면 util class라 한다(일종의 helper class).
따라서, util class의 경우에는 인스턴스화가 불필요하다 볼 수 있다.
가령 우리가 String를 파싱하는 기능을 가지는 Util class가 있다고 가정해보자.
public class ParseUtil {
public static List<String> parseByDelimiter(final String plaintext, final String delimiter) {
return Arrays.stream(plaintext.split(delimiter))
.toList();
}
}
아래, 해당 Util class를 사용하는 코드를 사용하는 클라이언트를 보자
ParseUtil parseUtil = new ParseUtil();
List<String> parsedNameTextList = parseUtil.parseByDelimiter("프람,호티,배키,켬미,제우스,폰드,도비,초롱", ",");
System.out.println(parsedNameTextList);
컴파일러가
(코드 작성자가 생성자를 정의하지 않는 경우)
자동으로 기본 생성자를 만들어주기 때문에,
위 코드 처럼 기본 생성자로 ParseUtils을 인스터스화할 수 있다.
List<String> parsedNameTextList = ParseUtil.parseByDelimiter("프람,호티,배키,켬미,제우스,폰드,도비,초롱", ",");
System.out.println(parsedNameTextList);
이렇게 코드를 대체해도 기능적으로 똑같다.
따라서, 위 예시가 본 아이템에서 설명하는 불필요한 인스턴스화라고 할 수 있겠다.
02. 그래서 인스턴스화를 막으려거든?
02-01. 방법1: Util class를 abstract class
로 만든다.
오라클의 공식 튜토리얼을[2] 살펴보면 'abstract class는 인스턴스화를 할수 없다.' 설명하고 있다.
IDE에서도 컴파일 에러로 잡아주고 있는 모습이 아래와 같이 보인다.
- abstract class code
-
- client code
-
하지만, 이 방법에는 몇 가지 문제점이 존재한다.
- 상속해서 사용하면 그만이다.
- abstract class의 사용 용도를 생각하여, 다른 개발자가 상속을 해서 사용하라는 뜻으로 오해 할 여지가 생긴다.
02-02. 방법2: private 생성자를 사용하라.
아이템의 제목과 같이, 기본 생성자를 private으로 명시적으로 작성한다.
public class ParseUtil {
private ParseUtil() {}
public static List<String> parseByDelimiter(final String plaintext, final String delimiter) {
return Arrays.stream(plaintext.split(delimiter))
.toList();
}
}
이렇게 된다면 컴파일러가 묵시적으로 기본 생성자를 생성하는 것을 막을 수 있다.
또한, 해당 객체 외부에서는 생성자를 호출할 수 없기 때문에 인스턴스화 역시 막을 수 있다.
하지만! 이 방법에도 문제가 있다.
- 객체 내부에서는 기본 생성자를 통한 인스턴스화 가능하지 않다.
- '작성한 코드를 사용하지 않는다?' 논리적인 모순이 생긴다.
해당 문제를 해결하기 위한 방법을 2가지 제시해주겠다.
- private 생성자에 주석으로 인스턴스가 불가능한 클래스임을 주석으로 문서화하여 알린다.
- 아래 예시와 같이 Error를 통해 접근을 불가능함을 알린다.
private ParseUtil() { throw new AssertionError("인스턴스화를 할 수 없는 클래스입니다."); }
책의 저자는 1번 방법과 2번 방법을 같이 사용할 것을 권장하고 있다.
//기본 생성자가 만들어지는 것을 막는다(인스턴스 방지용).
private ParseUtil() {
throw new AssertionError("인스턴스화를 할 수 없는 클래스입니다.");
}
03. 결론
불필요한 인스턴스화를 막을려거든 private 생성자를 통해 생성자에 대한 접근을 막아줘라.
참고자료
[1] Java Helper vs. Utility Classes
[2] Abstract Methods and Classes
'독서 감상문 > Effective Java' 카테고리의 다른 글
로 타입은 사용하지 말라 (0) | 2024.05.06 |
---|---|
태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2024.05.03 |
상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (2) | 2024.05.02 |
Comparable을 구현할지 고려하라 (0) | 2024.04.25 |
자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (2) | 2024.04.18 |