Investigate Private Inner Classes
From Software By Jeff
You have a good reason for doing it, I'm sure. Making a private inner class, that is. There are plenty of good reasons, I'm not going to dispute that. There's trouble with private inner classes--you can't see them from the outside. OK, so maybe that's not a problem where you're from, but when you're working on a test-driven-development project, you need to be able to investigate the objects for testing purposes.
Consider the simple class InnerClass, shown below. Of course, examples barely have reasons for existing; our inner class is a goofy Map like classs, except that it doesn't do anything to keep you from putting more than one object with the same key. OK, maybe some simple value. That's not the point. The point is that there are exposed simple methods to add things to the list and get a dumb dump of the contents of the list. There's no way to get something out of the list or make changes to the list. Further, you could try to get the entries List from the class, but it wouldn't do you any good because it's filled with instances of an unavailable inner class.
public class InnerClass {
private java.util.List entries = new java.util.ArrayList();
public void put(Object key, Object object) {
entries.add(new MapLike(key, object));
}
public void iterate() {
java.util.Iterator entriesIterator = entries.iterator();
while (entriesIterator.hasNext()) {
MapLike mapLike = (MapLike) entriesIterator.next();
System.out.println(mapLike.getKey().toString() + ": " + mapLike.getObject().toString());
}
}
private class MapLike {
private Object key;
private Object object;
public MapLike() {
}
public MapLike(Object key, Object object) {
this.key = key;
this.object = object;
}
public Object getKey() {
return key;
}
public Object getObject() {
return object;
}
}
}
Now, although I've said we're doing this for test-driven-development, I invalidate that argument by showing you the class we want to work with first. Let me defend myself by saying that I'm trying to show what kind of thing we want to test; conceptually it's hard to explain in a terse wiki entry, such as this. Now you've got an idea of what we really want to work with.
Oh, and yes, I know it's not really null-safe. This is an example, and I'm choosing to not burden the class with too many checks. Checks that I'd consider necessary in the real world, but not here in example world.
Consider the following test class. The class simply makes an instance of our InnerClass, sticks something (yes, useless) in there, and asks for it back. TDD justification for the class and its put() and iterate() methods.
public class InnerClassTest extends junit.framework.TestCase {
public void testInnerClass() {
InnerClass innerClass = new InnerClass();
innerClass.put("Key1", "Object1");
innerClass.iterate();
}
}
When we run the test we expect to see output like Key1: Object1 in wherever the standard output goes. All is good. Yes, this is bad testing because it requires you to read the output, but it's an example, sheesh.
Now, we still don't have a way to make sure that we've got the InnerClass$MapLike, or what it's doing. Since our class doesn't have any way for us to get to the List, or more specifically its contents, we've got to turn to our friend, java.lang.reflect. Through reflection we'll zip right past that private stuff, and get what we need.
Before we go on, I need to proclaim that fundamentally, I'm all for respecting the Java accessor. I wouldn't introduce this into production code unless I had very, very good reason.
Consider this test class. It instantiates a new InnerClass, puts an Object pair in its inner class. Then we use some Java reflection tools to get to the inner class's members. We utilize Field and Method to read variables and call methods that are otherwise unavailable to us because InnerClass is not sharing MapLike.
public class PrivateInnerClassTest extends junit.framework.TestCase {
public void testPrivateInnerClass() throws Exception{
InnerClass innerClass = new InnerClass();
innerClass.put("Key2", "Object2");
java.lang.reflect.Field entriesField = innerClass.getClass().getDeclaredField("entries");
entriesField.setAccessible(true);
java.util.List entries = new java.util.ArrayList((java.util.List) (entriesField.get(innerClass)));
entriesField.setAccessible(false);
assertFalse(entries.isEmpty());
boolean tested = false;
Class[] declaredClasses = innerClass.getClass().getDeclaredClasses();
for (int i = 0; i < declaredClasses.length; i++)
{
Class declaredClass = declaredClasses[i];
if (declaredClass.getName().indexOf("InnerClass$MapLike") >= 0)
{
java.lang.reflect.Method mapLikeMethodGetKey = declaredClass.getMethod("getKey", null);
java.lang.reflect.Method mapLikeMethodGetObject = declaredClass.getMethod("getObject", null);
java.util.Iterator entriesIterator = entries.iterator();
while (entriesIterator.hasNext())
{
Object entry = entriesIterator.next();
Object key = mapLikeMethodGetKey.invoke(entry, null);
Object object = mapLikeMethodGetObject.invoke(entry, null);
System.out.println("Stolen " + key.toString() + ": " + object.toString());
}
tested = true;
break;
}
assertTrue(tested);
}
}
}
Here we see three of our favorite bits of reflection: Fields, Methods and Classes. OK, so Class is just plain Java, but our use here is indicative of reflection.
Let's look at the test bit by bit, in hunks where we don't need discrete disection.
In the beginning, we do like we did in the last test. That is, we make and populate our InnerClass. We could at this point call iterate() to see the results, but I can assure you it'd print Key2: Object2 to the standard output. We don't need to do that because the other test does that for us.
Next we try to grab the List of entries from InnerClass. As this is a private member, we can't just ask for it, so we get it using the getDeclaredField() method, and the following few lines allow us to copy its contents into our own. We could, of course, keep using the reference to the class, but we politely return the field to inaccessible.
Then the trickier part. We try to find the private inner class. We do this using the getDeclaredClasses() method. Unfortunately there's not a simple getDeclaredClass(String) method like there is for fields and methods. We then iterate this collection (we could assume it's the first one, because it is, but this technique allows us to expand or change the InnerClass class without breaking the test. Of course, one of the reasons we unit test is to protect us from breakage, which is why we check the tested boolean when we're done--it lets us know that we've found and looked at the inner class.
Finally, iterating through the List of entries (one in this test) we use reflection to call our getters. Normally we wouldn't want to test getters, but since that's all this class gives us, that's what we have to do. The ouput will be similar to before, except this time it'll say Stolen Key2: Object2 in the standard output.
There you have the fundamentals for getting to the bits hidden in other classes, including items in classes inside classes. The rudimentary tests herein would get you admonished if you that that was enough for thorough testing. In reality we'd have checked the private fields of the private inner classes to make sure that they contained the values we wanted; again, it's testing setters, but it's also testing the use of the inner class MapLike by the outer class, InnerClass.
Note a small extension of reflection would allow us to instantiate our own copies of MapLike outside of InnerClass, perhaps to do other operations on it. That's another interesting techinque for another wiki entry.