객체지향 언어에서 '인스턴스화를 막으려거든' 이라는 말은 상당히 의아하다.
간혹 그런 경우가 있는데, 보통 유틸리티 클래스
가 그렇다.
유틸리티 클래스 : 보통 static한 메서드만 갖고 있는 클래스를 칭한다.
아래는 유틸리티 클래스를 임의로 작성해보았다.
package item04;
public class UtilityClass {
// 이런 static한 메서드들은 클래스를 통해서 바로 호출이 가능하다.
public static String hello() {
return "hello";
}
public static void main(String[] args) {
UtilityClass.hello();
// 물론, 문법적으로는 인스턴스를 통해서 호출할 수도 있지만 너무 불필요한 코드이다.
// 오히려 지금 호출한 메서드가 static 메서드인지, 인스턴스 메서드인지 헷갈리게만 한다.
UtilityClass utilityClass = new UtilityClass();
utilityClass.hello();
}
}
그래서 '애초에 인스턴스를 만드는 것을 방지하자' 라는 게아이템4
의 핵심목표이다.
그럼 어떻게 인스턴스를 만드는 것을 방지할 수 있을까?
package item04;
public abstract class UtilityClass {
// 자바에서는 생성자를 작성하지 않으면 생성자를 기본으로 제공해주지만,
// 이 예제에서는 명시적으로 보이게끔 작성해주었다.
public UtilityClass() {
System.out.println("UtilityClass - Constructor");
}
public static String hello() {
return "hello";
}
public static void main(String[] args) {
UtilityClass utilityClass = new UtilityClass(); // 컴파일 에러
// ~~빨간 밑줄~~
}
}
이와 같은 방법으로 방지할 수 있지만, 사실 이 방법은 완벽히 인스턴스화를 방지하지는 못한다.
아래 코드는 추상클래스
로 정의된 UtilityClass
를 상속받은 DefaultUtilityClass
이다.
package item04;
public class DefaultUtilityClass extends UtilityClass {
public static void main(String[] args) {
// 자식 클래스의 생성자를 호출하면 UtilityClass의 생성자도 호출되기 때문에,
// 인스턴스가 방지되지 못한다.
DefaultUtilityClass defaultUtilityClass = new DefaultUtilityClass();
// 콘솔 출력 : UtilityClass - Constructor
}
}
위 에시처럼 자식클래스의 인스턴스를 만들게되면, 부모클래스의 생성자도 호출되기 때문에 인스턴스화를 방지하지 못한다.
그럼 어떻게 해야할까?
생성자를 private으로 설정하게 되면 상속을 할 수 없게된다.
위 예시의 DefaultUtilityClass
와 같은 자식클래스가 애초에 생길 수가 없다.
그러나 추상클래스가 아닌 채, 단순 private 생성자만 추가했다고 한다면,
아래 코드와 같이,
package item04;
public class UtilityClass {
private UtilityClass() {
System.out.println("UtilityClass - Constructor");
}
public static String hello() {
return "hello";
}
public static void main(String[] args) {
UtilityClass utilityClass = new UtilityClass();
}
}
클래스 내부에서는 생성자를 호출할 수가 있다.
우리는 이것을 방지해야한다.
그래서 private 생성자가 호출되면 에러
를 던지는 방식을 사용할 것이다.
private UtilityClass() {
// System.out.println("UtilityClass - Constructor");
throw new AssertionError("잘못됐어. 생성자를 호출하면 안돼");
}
* 이 에러는 예외를 처리(try-catch)하기 위한 것이 아니다. 이 에시에서는 "생성자를 만나선 안돼!" 라고 알려주는 것이다.
아래 사진은 실행 결과다.
⚠️ 그러나, 생성자를 못쓰게하기 위해 직접 생성자를 작성하는 것은 상당히 아이러니하다.
또한 생성자가 너무 눈에 잘보이기 때문에 혼란을 야기할 수 있다.
그래서 저자는 아래 코드와 같이 주석을 통해 문서화
해주는 것을 추천하고 있다.
package item04;
public class UtilityClass {
/**
* 이 클래스는 인스턴스를 만들 수 없습니다.
*/
private UtilityClass() {
throw new AssertionError();
}
public static String hello() {
return "hello";
}
}
정적 메서드만 담은 유틸리티 클래스는 애초에 인스턴스로 만들어 쓰려고 설계한 클래스가 아니다.
추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.
private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.
생성자에 주석으로 인스턴스화 불가한 이유를 설명하는 것이 좋다.
상속을 방지할 때도 같은 방법을 사용할 수 있다.(private)