Sponsored Link •
|
Summary
The Java language has always had the notion of marker interfaces. A problem with these is that if a parent class has the marker, all its children inevitably do too. On the other hand, marker annotations don't necessarily have this limitation. Here's how to define a marker that can be cancelled in a subclass.
Advertisement
|
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 {...}
...you might specify it like this...
@Serializable 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:
@Inherited 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:
@Inherited public @interface Serializable { boolean value() default true; }
With this definition, we can still define Number
as before:
@Serializable 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:
@Serializable(false) 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!
Have an opinion? Readers have already posted 7 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Eamonn McManus adds a new entry to his weblog, subscribe to his RSS feed.
Eamonn McManus is the technical lead of the JMX team at Sun Microsystems. As such he heads the technical work on JSR 3 (JMX API) and JSR 160 (JMX Remote API). In a previous life, he worked at the Open Software Foundation's Research Institute on the Mach microkernel and countless other things, including a TCP/IP stack written in Java. In an even previouser life, he worked on modem firmware in Z80 assembler. He is Irish, but lives and works in France and in French. His first name is pronounced Aymun (more or less) and is correctly written with an acute accent on the first letter, which however he long ago despaired of getting intact through computer systems. |
Sponsored Links
|