[ 유틸리티 클래스 ]
객체 생성이 목적이 아닌, 정적 메서드와 정적 필드만을 사용하기 위해 만들어진 클래스를 유틸리티 클래스라고 한다.
유틸리티 클래스의 예로 java.lang.Math가 있다.
public final class Math {
// Math 내부는 모두 static 변수와 static 메서드로 이루어져 있다.
public static final double E = 2.7182818284590452354;
...
public static double floor(double a) {
return StrictMath.floor(a);
}
...
}
java8 이전에는 인터페이스에 static 메서드 선언이 불가능했기때문에 이런 유틸리티성 작업은 모두 클래스에서 해야했지만, java8 이후로는 인터페이스에 static 메서드를 넣을 수 있게 되었다.
public interface UtilityInterface {
// interface에서는 접근 지시자를 붙이지 않으면 암묵적인 public으로 설정
default void sayHelloDefault()
{ System.out.println("hello"); } // ok
static void sayHelloStatic()
{ System.out.println("hello"); } // ok
// java9 버전부터는 private static 메서드도 가능
private static void sayHelloPrivateStatic()
{ System.out.println("hello"); } // ok
// 하지만 java9 버전 이후에도 private static 변수와 클래스는 선언 불가
}
인스턴스화 불가를 목적으로 만들어졌지만, 컴파일러는 자동으로 디폴트 생성자를 만들어준다. 따라서 외부에서 유틸리티 클래스를 생성해서 사용 가능하다. 예전에 만들어진 유틸리티 클래스를 보면 인스턴스 생성이 가능한 것들이 꽤 있다고 한다.
추상 클래스로 만드는 방법을 생각해 볼 수 있지만, 추상 클래스를 상속한 클래스를 구현하면 그만이다. 따라서 다른 해결방법이 필요하다.
해결방법은 디폴트 생성자를 private으로 만들어놓는 것이다.
public class UtilityClass {
private UtilityClass(){
throw new AssertionError(); // 혹시라도 생성되면 에러발생
}
}
이 방식을 사용하면 리플랙션을 통한 객체 생성도 불가능하다. private 생성자가 호출되면 에러가 발생하도록 만들어놨기때문이다.
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Constructor<UtilityClass> declaredConstructor = UtilityClass.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
UtilityClass utilityClass = declaredConstructor.newInstance(); // 에러 발생!!
System.out.println(utilityClass.getClass());
}
💡 핵심
인스턴스가 만들어지면 안되는 클래스가 있다면 디폴트 생성자를 private으로 만들어놓자.
조금 더 확실하게 막으려면 private 생성자 호출시 error 발생 시키기!