Book/Effective Java

[Book] ITEM 4) private 생성자로 noninstantiability를 강제할 것

kkkkkkkkkkkk 2022. 3. 1. 20:21

모든 클래스들이 인스턴스화가 필요한 것이 아닙니다.

static 메서드와 static 필드들을 담은 클래스들을 인스턴스화를 시키지 않아도 기능들을 사용 할 수 있는데 굳이 인스턴스화를 시킬 필요는 없다고 봅니다.

역직렬화 기능을 담은 클래스가 있다고 가정해봅시다.

public class ObjectDeSerialization{

    private static ByteArrayInputStream byteArrayInputStream;
    private static ObjectInputStream objectInputStream;

    public static byte[]makeByteDecodeArray(String encodeObject) {
        return Base64.getDecoder().decode(encodeObject);
    }

    public static Object makeDecodeObject(byte[]bytes)throws 예외생략 {
        byteArrayInputStream= new ByteArrayInputStream(bytes);
        objectInputStream= new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }
}

이러한 클래스는 굳이 인스턴스를 만들지 않아도 됩니다. static 메서드와 static 필드들을 담고 있는데 인스턴스를 만들 필요는 없습니다.

그러면 인스턴스화를 막는 방법은 무엇이 있을까요??

👻  private 생성자

첫번째 방법으로는 private 생성자 를 사용하는 방법이 있습니다.

하지만 reflection 기능으로 인스턴스를 생성하는 방법이 있었죠??

@Test
@DisplayName("역직렬화 인스턴스 생성 테스트")
void ObjectDeSerializationTest()throws 예외 생략{
    // given & when
    // reflection 사용
    Class<ObjectDeSerialization> objectDeSerializationClass = ObjectDeSerialization.class;
    Constructor<ObjectDeSerialization> declaredConstructor = objectDeSerializationClass.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    ObjectDeSerialization objectDeSerialization = declaredConstructor.newInstance();

    // then
    assertThat(objectDeSerialization.getClass().getName()).isEqualTo(ObjectDeSerialization.class.getName());
}

그러면 예외를 던져 막아볼까요??

// 인스턴스 생성시에 AssertionError 예외 발생
private ObjectDeSerialization() {
    throw new AssertionError();
}

인스턴스 생성시에 private 생성자에 AssertionError 예외를 던졌으니 예외가 발생 될 것입니다.

@Test
@DisplayName("역직렬화 인스턴스 생성 테스트")
void ObjectDeSerializationTest()throws 예외 생략{
    // given & when
    // reflection 사용
    Class<ObjectDeSerialization> objectDeSerializationClass = ObjectDeSerialization.class;
    Constructor<ObjectDeSerialization> declaredConstructor = objectDeSerializationClass.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);
    ObjectDeSerialization objectDeSerialization = declaredConstructor.newInstance();

    // then
    assertThat(objectDeSerialization.getClass().getName()).isEqualTo(ObjectDeSerialization.class.getName());
}

private 생성자를 사용한 경우 주석으로 왜 private 를 사용했는지 설명을 해야합니다.

// 인스턴스화를 막기위한 생성자입니다.
private ObjectDeSerialization() {
    throw new AssertionError();
}

👻  abstract Class

두번째 방법으로는 abstract 를 사용하여 인스턴스를 생성하는 것을 막을 수 있습니다.

public abstract class ObjectDeSerialization{

    private static ByteArrayInputStream byteArrayInputStream;
    private static ObjectInputStream objectInputStream;

    public static byte[]makeByteDecodeArray(String encodeObject) {
        return Base64.getDecoder().decode(encodeObject);
    }

    public static Object makeDecodeObject(byte[]bytes)throws IOException, ClassNotFoundException{
        byteArrayInputStream= new ByteArrayInputStream(bytes);
        objectInputStream= new ObjectInputStream(byteArrayInputStream);
        returnobjectInputStream.readObject();
    }
}

인스턴스를 생성할려고하면 컴파일 에러가 나옵니다.

@Test
@DisplayName("역직렬화 인스턴스 생성 테스트")
void ObjectDeSerializationTest() {
    // given & when & then
    ObjectDeSerialization objectDeSerialization = new ObjectDeSerialization(); // 컴파일 에러!!!

}

또한 reflection 기능으로 생성을 하면 InstantiationException 예외가 발생합니다.

@Test
@DisplayName("역직렬화 인스턴스 생성 테스트")
void ObjectDeSerializationTest()throws 예외 생략 {
    // given & when
    // reflection 사용
    Class<ObjectDeSerialization> objectDeSerializationClass = ObjectDeSerialization.class;
    Constructor<ObjectDeSerialization> declaredConstructor = objectDeSerializationClass.getDeclaredConstructor();
    declaredConstructor.setAccessible(true);

    // then
    assertThrows(InstantiationException.class,()-> declaredConstructor.newInstance());
}

private 생성자를 사용해서 인스턴스 생성을 막아도 좋지만 abstract 를 사용하여 인스턴스를 막는 것만으로 충분할 것 같다는 생각이듭니다.