Java

[Java]마커 인터페이스와 Serializable 동작 방식

Hyung1 2021. 2. 18. 01:25
728x90
반응형

Serializable의 동작방식이 궁금하여 내부를 보았는데, 어라? 필드와 메소드가 한 개도 없었다. 

 

이것을 마커 인터페이스라고 한다.

마커 인터페이스(marker interface)

아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 것

 

자바의 대표 마커 인터페이스로는 위에서 언급한 Serializable, Cloneable와 흔히 알지는 못하지만 Spring 에서 리스너를 사용한다면 보이는 EnentListener라는 인터페이스도 있다.

 

아무것도 담고 있지 않으면, 왜 쓰는 것일까?

 

아무것도 담지 않고 있지만, Serilaizable을 구현한 클래스의 인스턴스는 objectOutputStream을 통해 쓸 수 있다. 즉 직렬화 할 수 있다고 알려준다. 이 인터페이스를 구현하지 않은 클래스의 경우에는 직렬화를 할 수 없다.

 

더 간단히 말하면, 단순하게 타입을 체크한다고 할 수있다.

 

코드로 예를 보자.

import java.io.*;

public class serializableTest {

    public void serializableTest() throws IOException, ClassNotFoundException {
        File f= new File("a.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
        objectOutputStream.writeObject(new Example("hyungil", "hyung1jung@tistory.com"));
    }

    class Example {
        private String name;
        private String email;

        public Example(String name, String email) {
            this.name = name;
            this.email = email;
        }

        // 생성자 및 기타 메서드 생략
    }

    public static void main(String[] args) {
        serializableTest serializableTest = new serializableTest();
        try {
            serializableTest.serializableTest();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

 

위 코드를 실행하면, 

 

이런 에러가 발생한다. 

 

이는 직렬화를 할 수 있는 Serializable를 구현하지 않았기 때문이다. 위 에러는 

    class Example implements Serializable {
        private String name;
        private String email;
}

Serializable 인터페이스를 구현함으로써 해결 할 수 있다. 어떻게 Serializable 인터페이스를 쓴다고 해결이 될까?

 

이제 더 깊게 writeObject() 메서드의 내부를 보자. 

객체는 ObjectOitputStream의 writeObject() 메서드에 자신을 넘김으로써 직렬화 된다. writeObject() 메서드는 객체의 모든 것을 기록하게 된다.

 

여기서 추가로 말하자면, 직렬화의 해제는 역직렬화의 과정을 거치게 되는데 ObjectInputStream의 readObjcet() 메서드를 호출함으로써 스트림으로부터 읽어 들이고 이를 직렬화 되기전의 객체로 다시 만들게 된다.

 

간단하게 말하자면, ObjectOutputStream을 생성해서 writeObject() 메서드를 이용해서 객체를 직렬화하고, ObjectInputStream을 생성해서 readObject() 메서드를 통해서 객체를 복원한다.

 

다시 writeObject() 메서드의 내부로 돌아가자. 중요한 키워드는 writeObject0이다. writreObject() 메서드의 중간을 보면 writeObject0() 메서드가 있다. 이 메서드의 내부를 보면,

 

이렇게 생겼다. 코드가 너무 길어 중간은 생략하였다.

    private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
			... 중간 생략

            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

 

여기서 "// remaining cases"의 부분을 보면, if문이 꽤 있다. String은 Serializable을 구현 했으니 직렬화가 가능하고, 배열도 마찬가지고, Enum도 마찬가지고... 그 다음에 보면 Serializable가 없다면 예외로 처리한다. 

 

이처럼 간단하게 Serializable가 선언되어 있는지 안되어 있는지 체크 정도만 한다. 실질적으로 크게 무엇을 하는건 아니다. 그래서 이것을 마커 인터페이스라고 부른다.

 

마커 인터페이스는 마커 어노테이션으로 활용 가능하다. @ExampleAnnotation이라는 어노테이션이 있다면 아래와 같이 가져와서 체크하면 된다.

final ExampleAnnotation exampleAnnotation = someObject.getClass().getAnnotation(ExampleAnnotation.class);

그럼 마커 인터페이스와 마커 어노테이션의 차이는 뭘까?

 

마커 인터퍼에스는 컴파일 시점에 발견할 수 있다는 장점이 있다. 그리고 적용 범위를좀더 세밀하게 지정할 수 있다.

 

마커 어노테이션은, 어노테이션 자료형을 선언할 때, target를 선언하여 ElementType.TYPE라고 지정해서 사용하면 되는데, ElementType.TYPE는 클래스 뿐만 아니라 인터페이스에도 적용이 가능하다. 

 

그럼 특정한 인터페이스를 구현한 클래스에만 적용할 수 있어야 하는 마커가 필요하다고 가정해보자.

 

마커 인터페이스를 사용하면 그 특정 인터페이스를 상속하도록 하면 된다. 그럼 마커를 상속한 모든 자료형은 자동으로 그 특정 인터페이스의 하위 자료형이 된다.

 

즉, 마커 어노테이션의 장점은 유연하게 확장이 가능하다고 할 수 있다. 어노테이션을 만들면 사용하고 나서도 계속 더 많은 정보를 추가 할 수있다.

 

하지만 인터페이스는 메소드를 만드는 순간 호환성이 깨지므로 마커 어노테이션처럼 지속적으로 유연하게 확장하는 것이 힘들다. 

 

728x90
반응형