The Artima Developer Community
Sponsored Link

Weblogs Forum
AccessibleObject revisited: a study in immutability

0 replies on 1 page.

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 0 replies on 1 page
Eamonn McManus

Posts: 20
Nickname: dichotomy
Registered: Apr, 2003

AccessibleObject revisited: a study in immutability (View in Weblogs)
Posted: Jun 12, 2006 2:46 AM
Reply to this message Reply
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);
  1. prints false
  2. prints true
  3. prints false or true depending on the implementation
  4. 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:

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:

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

  • The answer to the quiz is a, it prints false.

Topic: AccessibleObject revisited: a study in immutability Previous Topic   Next Topic Topic: A Fedex Rate Requester using xmlnode

Sponsored Links



Google
  Web Artima.com   

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