Sponsored Link •
|
Summary
Looking at the Java Reflection API, we can learn some lessons about immutability.
Advertisement
|
Java Reflection Quiz: what's the value of
Object.class.getMethod("toString") ==
Object.class.getMethod("toString")
? In other words,
what does the following code fragment do?
boolean identical = (Object.class.getMethod("toString") == Object.class.getMethod("toString")); System.out.println(identical);
false
true
false
or true
depending on
the implementation
Go ahead, think about it. I'll wait.
If you answered "does not compile", you probably remembered
that Class.getMethod
has a second parameter that specifies the parameter types of the
method in question. So you can distinguish between Object.wait()
and Object.wait(long timeout)
,
for example. The code fragment doesn't supply this second
parameter, so it doesn't compile, right?
Well, that's true in versions of the J2SE platform prior to 5.0
(Tiger). Tiger introduced varargs,
and changed the signature of several standard methods to exploit
them. In particular, the signature of
Class.getMethod
changed from
Class.getMethod(String name, Class[]
parameterTypes)
to
Class.getMethod(String name,
Class... parameterTypes)
So instead of writing
String.class.getMethod("indexOf", new Class[]
{String.class, int.class})
you can write
String.class.getMethod("indexOf", String.class,
int.class)
And in particular, when there are no parameters you can just
write Object.class.getMethod("toString")
.
If you actually went and studied the API documentation for
Class.getMethod
, you probably concluded that the right answer was
c: prints false
or true
depending on the implementation. The specification doesn't
explicitly say that calling Class.getMethod
twice
with the same parameters will return the same Method
object, but it doesn't rule it out either. After all, the
reflected method isn't going to change by suddenly acquiring a
different return
type or migrating to a different class.
In other words, the Method
is logically
immutable, so it seems that implementation would be
within its rights to return the same Method
object
always for a given reflected method. That's one of the many advantages of
immutability, after all.
(Let's leave aside the question of JVMTI
and in particular its ability to redefine
classes dynamically. If JVMTI suddenly changed a method
from final
to non-final
, then we might
expect an existing Method
object reflecting that
method to start returning a different value from its getModifiers()
method. I don't think JVMTI can currently do that sort of
thing, but even if it could we would say that
Method
is unmodifiable rather than
immutable, i.e. its behaviour can't be changed in any
way by calling its methods. Unmodifiability would still be
enough to allow Class.getMethod
to return the same
Method
object every time it was called with the
same parameters.)
So is Method
actually immutable? If you scan
through the list of methods in java.lang.reflect.Method
,
you'll just see innocuous methods like isVarargs()
and getExceptionTypes()
. It all looks immutable-ish
enough.
Then you look at the box just after the method summary:
Methods inherited from class java.lang.reflect.AccessibleObject |
---|
getAnnotations, isAccessible, isAnnotationPresent, setAccessible, setAccessible |
So what on earth is setAccessible
about?
If you don't know, then the answer may shock you (if you are
easily shocked). The setAccessible
method allows you
to bypass the access control semantics of the Java language. By
calling setAccessible
on the Method
object for a private
method, you can call that method
from outside the class it is defined in, using Method.invoke
.
By calling setAccessible
on the Field
object for a private
field, you can read or write
that field from any other class. As of Tiger, you can even modify
a final
field in this way.
You might have comfortably assumed that if you want to know who
the callers of a private
method are, you don't have
to look further than the class it appears in; or that if a class
contains only final
fields it is immutable. In the
presence of reflection, these assumptions are unreliable.
You can reasonably hope that there are few cases where other
code uses reflection to confound your expectations deliberately.
Untrusted code can't call setAccessible
, for example.
Java
serialization uses reflection to get at private fields so it
can serialize or deserialize them, and to call private methods
such as readObject
,
but that works in well-understood ways.
Anyway, the subject here is not the putatively shocking
semantics of setAccessible
, but the question of
whether Method
might be immutable. Clearly, since it
has a setter method, the answer is no. This single method is all
that stands between Method
and the glory of
immutability. In other words, Class.getMethod
must return a new object every time it is called, just
for the 1% of callers who then call setAccessible
on
it.
Actually, if we look closer, we can see that
AccessibleObject
is probably one of those classes
whose designers bitterly regret the inability to make incompatible
changes in the Java API. (Not that I'm throwing stones here. API
design is a human enterprise and therefore fallible.) Here are
some of the things wrong with it:
protected
constructor even though its
only meaningful subclasses are in the same package.
Method
,
Field
, and Constructor
, so we might
expect it to represent some important thing that those classes
all are. Being an "accessible object" (whatever that means)
does not seem to be such a thing. And this superclass is
apparently an invitation to dump other unrelated things that
these three classes have in common there. What does getAnnotations()
have to do with being an "accessible object", for example?
setAccessible
that breaks the immutability of its
subclasses.
The short answer is, we can't. The Reflection API is there, people use it, and we can't change it for fear of breaking millions of lines of code.
But let's imagine we don't have such a constraint. How would
we achieve the functionality of setAccessible
without
breaking immutability?
Here's one suggestion. Let's start by imagining that we remove
the setAccessible
method from the Method
class, and replace it with this method:
public Method getAccessibleVersion() throws SecurityException;
This hypothetical method returns a Method
object
that is able to ignore access controls, just as a
Method
that you've called
setAccessible(true)
on does today. If you call
m.getAccessibleVersion()
, then the result can be
m
if the method was already accessible, because it
is public or because m
was the result of a previous
getAccessibleVersion()
. Otherwise it will be a
different Method
object m1
with the
desired property.
Both m
and m1
can be immutable. This
implies that if you call m.getAccessibleVersion()
a
second time, it can reasonably return m1
again.
Likewise, Class.getMethod
can now return the same
object every time it is called with the same parameters.
This is a useful pattern in general to avoid losing immutability. Where you might want to change the state of an immutable object, arrange to return another immutable object that has the changed state instead.
Well, we haven't really provided the same functionality as
before, because before we had a common superclass
AccessibleObject
that contained the
setAccessible
method. Some code might take
an AccessibleObject
as a parameter and call
setAccessible
on it without knowing whether it is a
Method
or a Field
. I'm having trouble
imagining what such code might look like, but still. We could
achieve what we want by changing the interface (and the name) of
AccessibleObject
like this:
public abstract class ReflectionObject { ReflectionObject() {} // no subclasses outside this package public abstract ReflectionObject getAccessibleVersion(); public abstract boolean isAccessible(); ... } public final class Method extends ReflectionObject { ... public Method getAccessibleVersion(); ... }
We use covariant return types to allow each subclass to declare
itself as the return type of getAccessibleVersion
.
(To be fair, this feature wasn't available when
setAccessible
was added in 1.2, so an interface based
on getAccessibleVersion()
would not have been as
clean there.)
If we were feeling extravagant, we could even express this
covariance explicitly in ReflectionObject
, using the
same generics pattern as Enum
:
public abstract class ReflectionObject<T extends ReflectionObject<T>> { public abstract T getAccessibleVersion(); public abstract boolean isAccessible(); ... } public final class Method extends ReflectionObject<Method> { ... public Method getAccessibleVersion(); ... }
However, that rhetorical flourish doesn't really gain us
anything, especially since Method
could extend
ReflectionObject<Field>
if it was feeling
perverse.
What can we conclude from all of this?
Reflection is full of surprises.
Be careful what you call your superclasses.
Try to preserve immutability. Where you might want to change the state of an immutable object, arrange to return another immutable object that has the changed state instead.
The answer to the quiz is a, it prints false
.
Have an opinion? Be the first to post a comment about this weblog entry.
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
|