The Artima Developer Community
Sponsored Link

Dr. Dichotomy's Development Diary
Negatable Marker Annotations
by Eamonn McManus
March 9, 2005


A marker interface is an interface with no methods, which a class can implement to show that it has certain properties. Well-known examples are Cloneable and Serializable. A class can implement Serializable to indicate that it has been designed to be serialized, for example.

One problem with marker interfaces is that when a class implements an interface, all of its subclasses inherit that implementation. Since Number implements Serializable, any subclass such as Integer or AtomicInteger does too.

What can you do if you want to define a subclass of Number that is not Serializable? You are forced to resort to a hack: define a writeObject method that throws NotSerializableException. Instances of your class will still appear to be serializable (instanceof Serializable is still true) but will not actually be. Yuck.

Annotations, introduced with Tiger (J2SE 5), provide an alternative to marker interfaces. If serialization were being defined now, then instead of specifying serializability like this...

public class Number implements Serializable {...} might specify it like this...

public class Number {...}

Annotations can be defined such that they either are or are not inherited by subclasses. We could decide that the hypothetical @Serializable annotation is inheritable when we define it:

public @interface Serializable {}

In this case, subclasses of our @Serializable Number class like Integer are automatically @Serializable themselves. This is usually what you want, but just as with the marker interface there is no way to cancel serializability in a subclass. Once an inheritable marker annotation is in a superclass, a subclass can't turn it off.

Alternatively, we could omit the @Inherited annotation from the definition of @Serializable. Then Integer is not automatically @Serializable even if Number is. We can explicitly choose whether or not to make it so. But we no longer get the behaviour that we usually want, which is that a class is automatically @Serializable if its parent is.

We could define a @NotSerializable marker annotation, just as we could define a NotSerializable marker interface, so a subclass could override the inherited @Serializable by saying it was @NotSerializable after all. But how ugly.

Fortunately, there is a nicer solution using what I'll call negatable marker annotations. The idea is simple. We change the definition of @Serializable to this:

public @interface Serializable {
    boolean value() default true;

With this definition, we can still define Number as before:

public class Number {...}

Subclasses of Number are now automatically @Serializable too. But if we want to define a subclass that is not, we simply write:

public class UnserializableInteger extends Number {...}

It could hardly be simpler!

When we write @Serializable with this definition, it's equivalent to writing @Serializable(true). We can omit true since it's the default value, and then there's nothing left but @Serializable(). The annotation syntax allows us to omit the empty parentheses too, and that leaves us with plain @Serializable.

The code that uses reflection to know whether a class is @Serializable or not is different with this definition. With a plain marker annotation, we would use:

Class c = whatever;
Serializable ann = c.getAnnotation(Serializable.class);
boolean isSerializable = (ann != null);

With a negatable marker annotation, we must use:

Class c = whatever;
Serializable ann = c.getAnnotation(Serializable.class);
boolean isSerializable = (ann != null && ann.value());

The class is serializable if the @Serializable annotation is present (ann != null) and the value of the annotation is true (ann.value(), which we could also write as ann.value() == true).

Negatable marker annotations can have other applications than cancelling inherited annotations. For example, suppose you have a convention that an interface called SomethingMBean is an "MBean interface", whatever that might be. You can add a negatable @MBean annotation to supplement this convention. You can say that an interface is an MBean interface even if isn't called SomethingMBean, by using @MBean. And, you can say that an interface is not an MBean interface even if it is called SomethingMBean, by using @MBean(false).

Negatable marker annotations also allow you to be more explicit. With plain marker annotations, if a class is marked @Serializable then you know that its designers intended it to be serializable. But if it is not marked @Serializable, you only know that either its designers intended it not to be serializable or they didn't think about the question. A negatable marker annotation allows you to write @Serializable(false) to be explicit about it.

I haven't seen this idea elsewhere, but I would amazed if it has not been independently invented several times. Let me know!

Talk Back!


RSS Feed


About the Blogger


This weblog entry is Copyright © 2005 Eamonn McManus. All rights reserved.

Sponsored Links

