The Artima Developer Community
Sponsored Link

Weblogs Forum
Negatable Marker Annotations

7 replies on 1 page. Most recent reply: Mar 23, 2005 10:50 AM by Eamonn McManus

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 7 replies on 1 page
Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

Negatable Marker Annotations (View in Weblogs)
Posted: Mar 9, 2005 1:36 PM
Reply to this message Reply
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!


Kevlin Henney

Posts: 22
Nickname: kevlin
Registered: Apr, 2003

Re: Negatable Marker Annotations Posted: Mar 10, 2005 2:31 AM
Reply to this message Reply
> One problem with marker interfaces is that when a class implements an interface, all of its subclasses inherit that implementation.

That is not a problem, it's a feature: when subclassing is viewed from the perspective of subtyping and substitutability, rather than jumbled ad hoc code reuse, it is inevitable that a supertype -- whether class or interface -- defines and constrains the interface and capabilities of its descendants. That viewpoint is pretty much embodied in most object type systems. There may be some well-defined cases where you want to work against it, but on the whole the assumption should be that such a workaround is exactly that: an intentional and designed departure from the norm and from well-reasoned best practice.

> 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.

Yes, it's a hack, but at least it looks like a hack. Trying to encourage support for non-substitutability through annotations to make the workaround more transparent seems to be answering the wrong design question, I'm afraid.

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

Generally this idea of inheritance with cancellation comes up quite often in one form or another. I have noticed people either stating a need for it or implementing it in different languages since at least the late 1980s, so it probably stretches back further. Most of the time the stated need masks a different design issue.

Kevlin

Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

Re: Negatable Marker Annotations Posted: Mar 10, 2005 8:25 AM
Reply to this message Reply
Kevlin, I'm not sure we're talking about the same things here. The "idea" I was referring to was that of having a marker that is usually positive but can be explicitly negative. I don't think that inherited interfaces should be cancellable in general, since as you say that would violate substitutability (Eiffel commits this sin because of both visibility reduction and covariant parameters, for example). However, it does make sense for some markers to be cancellable in subclasses.

@Serializable is perhaps not the best example because of the substitutability point you mention: if I accept a Number parameter, then I expect any value passed to that parameter to obey Number's contract and be serializable. A better example might be @Cloneable (if Object.clone() weren't so screwed up otherwise). This is not something you are saying to the users of your class but to Object.clone(): "I've done the necessary work to ensure I can be cloned safely by copying my fields." This is not necessarily true of subclasses so they probably shouldn't inherit this marker, or at least they should be able to cancel it.

In any case negatable markers have uses other than on classes, as I mentioned.

Michael Chermside

Posts: 17
Nickname: mcherm
Registered: Jan, 2004

Poor example Posted: Mar 11, 2005 5:47 AM
Reply to this message Reply
I agree with the usefullness of the feature (and thank you for writing it out so neatly with examples and everything). But I object to the specific example. It is true that once-upon-a-time (before annotations existed), marker interfaces were used as a way of simply "tagging" classes, and some of the things represented in this fashion were not things one would necessarily expect subclasses to implement. But some ARE, and you wouldn't want to cancel those in subclasses because it would break substitutability.

-- Michael Chermside

David Waddell

Posts: 1
Nickname: dwaddell
Registered: Mar, 2005

Re: Negatable Marker Annotations Posted: Mar 11, 2005 8:56 AM
Reply to this message Reply
Isn't the point here that using a marker interface to denote
something 'this is serializable' is only one way to express the concept ? That you might have a class model wherein 'serializability' was not naturally part of the type hierarchy ?

The use of annotations would be more natural here than using a marker interface.

Brendan Boesen

Posts: 2
Nickname: dugong
Registered: Feb, 2003

Re: Negatable Marker Annotations Posted: Mar 13, 2005 11:03 PM
Reply to this message Reply
I can't quite understand why I wouldn't ask the object itself if it is serializable?

In other words, if there is a choice as to whether the object is Serializable, put that choice in the contract:

interface Serializable {
	boolean isSerializable();
}


Usage is then something like:

public class Serializer {
 
	public void serialize(Serializable obj, OutputStream outStream) {
	
		if (! obj.isSerializable())
			return;
		else 
			// Go for it!!!
	}
}


Then you could have your standard serializable class, a derived but not serializable subclass and a derived serializable class that forces all its subclasses to be serializable too:

public class Number implements Serializable {
 
	public boolean isSerializable() {
		return true;
	}
}
 
public class NotSerializableNumber extends Number {
 
	public boolean isSerializable() {
		return false;
	}
}
 
public class DraconianWithChildrenSerializableNumber extends Number {
 
	public final boolean isSerializable() {
		return true;
	}
}


Interestingly, doing it this way puts the decision as to whether an actual object is serializable or not with that object. eg: a SensitiveDocument instance can decide whether or not its serializable depending on its content.

That would be the traditional way to do it. It's MUCH easier on the client than the annotation check and is relatively easy for the implementor. I do admit it's a little harder for a user of a class to work out whether instances of a class are, or are not, actually serializable - they have to trust the documentation or read the source code I guess. That might be a moot point however, because someone relying on this capability STILL has to check at runtime if the object 'implements' the protocol (meaning it answers 'yes') and handle the situation where it doesn't.

(Am I missing something really obvious here? Be gentle with me if I am please. :-)

I like David Waddell's comment about "you might have a class model wherein 'serializability' was not naturally part of the type hierarchy". This is starting to sound a little bit like aspects (though I haven't actually used aspects so I could be speaking out of my proverbial). Can anyone think of any other examples where this sort of capability would prove useful?

Eamonn, why did the JMX specification decide to go with a naming convention to determine whether an interface represents an MBean and not just have an MBean marker interface that other interfaces inherit from?

Todd Blanchard

Posts: 316
Nickname: tblanchard
Registered: May, 2003

Re: Negatable Marker Annotations Posted: Mar 16, 2005 9:53 AM
Reply to this message Reply
I have to agree with Brendan, this conditional annotation thing looks like an ungodly hack. Of course, annotations in general look like an unnecessary addition to the language for this kind of use.

Tagging interfaces that contain methods that determine if the interface is actually in force seems adequate and simple to use.

Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

Re: Negatable Marker Annotations Posted: Mar 23, 2005 10:50 AM
Reply to this message Reply
Brendan, I don't want to get too involved in the particular question of serialization. However, notice that what you are talking about is a way for an instance to say whether it is serializable, whereas marker interfaces and annotations are a way for a class to say whether it is serializable. (In this particular case there's a certain amount of redundancy with exceptions: what if you say isSerializable() but then throw NotSerializableException from writeObject?)

Now, maybe a lot of the time where marker interfaces are used, you really do want to say something about the instance rather than the class. But not always. One example where your technique clearly isn't applicable is when the marker interface or annotation is used by tools that run on source code or class files, rather than on runtime instances. The apt tool allows you to do all sorts of interesting processing on your source files based on annotations, for example.

And of course annotations can be applied to other things besides classes, as I mentioned. In these other cases it is useful to be able to say @Interesting(false) explicitly either because leaving it out is ambiguous (did I decide it was uninteresting, or did I not think about it?), or because the default interestingness is sometimes true and needs to be contradicted.

Concerning your question about why the JMX API uses a naming convention rather than a marker interface, I wasn't around when this stuff was invented and can't say. It was quite a long time ago, and might even precede e.g. java.rmi.Remote. If we were doing it over we probably would have an MBean interface like the Remote interface.

Flat View: This topic has 7 replies on 1 page
Topic: Hygenic versus Unhygenic macros in Heron Previous Topic   Next Topic Topic: When the Browser is an Inadequate Interface

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use