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?
prints false or true depending on
the implementation
does not compile
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:
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.getMethodmust 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:
It is not abstract, even though there is no way to obtain an
instance of it and nothing that such an instance might
represent.
It has a protected constructor even though its
only meaningful subclasses are in the same package.
It is the common superclass of 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?
And finally, it contains a single method
setAccessible that breaks the immutability of its
subclasses.
How can we fix this?
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.
Conclusions
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.