오랜만에 자바 포스팅을 해보자.
자바의 마커 인터페이스의 대해 살펴보려고 한다. 책도 조금 참고 하였다.
자바의 마커 인터페이스란 일반적인 인터페이스와 동일하지만 사실상 아무 메서드도 선언하지 않은 그런 인터페이스를 말한다. 예를들어 다음과 같다.
public interface SomeObject {
}
얼핏 보기엔 조금 난해한 코드이다. 인터페이스만 있고 메서드가 없으니 어디에다 쓸지도 난해하다. 자바로 코딩을 하다보면 저런 인터페이스가 종종 있긴하다. 자바의 대표적인 마커 인터페이스로는 우리가 흔히 아는
Serializable
,
Cloneable
와 흔히 알지는 못하지만 Spring에서 event리스너를 사용한다면 종종 보이는
EventListener
라는 인터페이스도 있다. 참고로 Spring의
ApplicationListener
인터페이스가 상속받고 있다. 아무튼 이런 인터페이스는 어떻게 무엇을 위해 만들어 졌나..
뭔가 대단한거 처럼 보일 수 도 있지만 실질적으로는 아주 간단하다. 대부분의 경우에는 단순한 타입체크라고 할 수 있다. (물론 아닌 인터페이스도 있을 수도 있다. 확실하지 않아서... 그래서 대부분이라고 표기를 했다. )
자바의 대표적인 마커 인터페이스인 Serializable 를 살펴볼까 한다. Serializable 인터페이스는 다음과 같다.
public interface Serializable {
}
아까 필자가 위에서 말했듯이 메서드가 한개도 선언되지 않았다. Serializable 인터페이스 같은 경우에는 직렬화를 할 수 있다는 뜻이다. 만약 이 인터페이스를 구현(?) 하지 않은 클래스 경우에는 직렬화를 하지 못한다.
아주 간단하게 예제를 만들어 보자.
@Test
public void serializableTest() throws IOException, ClassNotFoundException {
File f= new File("a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
objectOutputStream.writeObject(new SomeObject("wonwoo", "test@test.com"));
}
class SomeObject {
private String name;
private String email;
//생성자 및 기타 메서드 생략
}
위의 코드를 실행해보면 당연히 에러가 발생한다.
java.io.NotSerializableException: me.wonwoo.SomeObject
왜냐하면 직렬화를 할 수 있다는 Serializable 를 구현하지 않았기 때문이다. 그럼 위의 코드를 초록불이 들어오게 하고 싶다면 Serializable만 구현해 주면 된다.
@AllArgsConstructor
class SomeObject implements Serializable {
private String name;
private String email;
}
간단하다 인터페이스의 메서드도 없으니 구현할 메서드도 필요 없다. 그냥 선언만 해주면된다. 그럼 위에서 사용했던
writeObject()
메서드 안을 들여다보자.
writeObject()
메서드 안에는
writeObject0()
가 존재 한다.
writeObject0()
메스드 맨 아래에 보면 다음과 같은 코드가 있다.
//...
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() + "\
" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
//...
if문이 꽤 있다. 보면 String은 Serializable구현 했으니 되고 배열도 Serializable 할 수 있고 Enum도 Serializable를 구현 했으니 되고 다음으로는 Serializable가 되어있는지 체크하는 부분이다. 만약 Serializable가 없다면 에러로 처리 한다. 위에서 보았듯이 간단하게 Serializable가 선언 되었는지 안되어 있는지 체크정도만 한다. 실질적으로 뭘 하는 건 아니다. 그래서 마커 인터페이스로 부른다.
근데 조금 의아한게 왜 writeObject() 메서드의 파라미터를 Object으로 했을까? Serializable로 했다면 컴파일 타임에 에러를 발견하고 잡을 수 있을 텐데 말이다. 무슨 이유가 있나? 메서드 명이 Object이라?
마커 인터페이스는 어노테이션으로도 대체 가능하다. 만약 @SomeAnnotation 이라는 어노테이션이 있다면 아래와 같이 가져 와서 체크하면 된다.
final SomeAnnotation someAnnotation = someObject.getClass().getAnnotation(SomeAnnotation.class);
마커 인터페이스와 마커 어노테이션의 차이를 살펴보자.
마커 인터페이스 같은 경우에는 컴파일 시점에 발견할 수 있다는 큰 장점이 있다. 그리고 또한 적용범위를 좀 더 세밀하게 지정 할 수 있다.
만약 어노테이션 자료형을 선언할 때 target 에
ElementType.TYPE
이라고 지정해서 사용한다고 하면
ElementType.TYPE
은 클래스 뿐만 아니라 인터페이스에도 적용 가능하다. 그런데 특정한 인터페이스를 구현한 클래스에만 적용할 수 있어야 하는 마커가 필요하다가 해보자. 마커 인터페이스를 쓴다면 그 특정 인터페이스를 상속 하도록 선언만 하면 된다. 그럼 마커를 상속한 모든 장료형은 자동으로 그 특정 인터페이스의 하휘 자료형이 된다.
그렇다면 마커 어노테이션의 장점은 뭘까? 마커 아노테이션은 유연하게 확장이 가능하다. 어노테이션을 만들어 사용한 뒤에도 계속적으로 더 많은 정보를 추가 할 수 있는 것이 큰 장점이다. 예를들어 어떤 어노테이션을 만들고 배포를 한 뒤에 뭔가 더 정보를 추가 하고 싶다면 새로 추가 된 요소들에 대해
default
값을 갖게 하면 하위 호환성도 지킬 수 있으며 처음에는 마커 어노테이션으로 시작했다가 나중에는 기능이 많은 어노테이션으로 진화 가능하다.
하지만 인터페이스 경우에는 메서드를 만드는 순간 하위 호환성이 깨지므로 마커 어노테이션처럼 지속적인 진화는 불가능하다.
마커 어노테이션과 마커 인터페이스 중 둘 중 어느게 낫다고 할 수 없을 거 같다. 각각의 쓰임새가 다르기 때문이다. 위에서 말했듯이 새로운 메서드가 없는 자료형을 정의하고 싶다면 마커 인터페이스를 이용해야 하고 클래스나 인터페이스 이외의 마커를 달아야 하고 앞으로도 더 많은 추가 정보가 있다고 생각하면 마커 어노테이션을 사용하면 된다. 어떤 쓰임인지 잘 판단하고 만들어야 할 듯 싶다.
참고로 Spring의 대표적인 마커 인터페이스는
Aware
Spring data에 대표적인 마커 인터페이스는
Repository
등이 있다. 좀 더 찾아 볼라고 했는데 귀찮.. 개발하다가 나오겠지 뭐..
이렇게 오늘은 마커 인터페이스를 살펴봤다!