Rod Hilton's rants about software development, technology, and sometimes Star Wars

I’m going to show you a little trick that will add two methods to any Java class, without actually defining them. Furthermore, these methods will be given package visibility, accessible by any class in the same package.

First and foremost, credit for showing me this interesting javac tidbit goes to Ted Neward. Ted recently presented an introduction to java bytecode at a local JUG. The entire presentation was incredibly interesting, but one of the more interesting bits that came out was a little bit of trickery the java compiler performs in certain cases.

Let’s take a look at two classes. One we’ll call CompTest, which will be a simple class that contains a private String and a method to print it to the screen. The second will be CompExecutive, which will simply make a CompTest instance and call the one method it defines. It will also use Java reflection to count the number of methods on CompTest.

public class CompTest {
  private String myVariable="This is a private variable";
  
  public void printVar() {
    System.out.println(myVariable);
  }
}
import java.lang.reflect.*;

public class CompExecutive {
  public static void main(String[] args) {
    CompTest ct=new CompTest();
    ct.printVar();

    //Get all of the methods on the class.
    Method[] declaredMethods=ct.getClass().getDeclaredMethods();
    System.out.println("Number of Methods on CompTest: "+declaredMethods.length);
  }
}

When we run this program, we get what we would expect. The outputted string, as well as the number of methods defined in CompTest, one.

This is a private variable
Number of Methods on CompTest: 1

Now let’s change CompTest slightly. Let’s give CompTest an inner class. Inner classes are allowed to access private variables inside the containing class, so we’ll make an Inner class that changes the private variable, then prints it.

public class CompTest2 {
  private String myVariable="This is a private variable";
  
  public class InnerClass {
    public void alsoPrintVar() {
      myVariable = "Is it still private?";
      System.out.println("From Inner Class: "+myVariable);
    }
  }

  public void printVar() {
    System.out.println(myVariable);
    InnerClass ic=new InnerClass();
    ic.alsoPrintVar();
  }
}
import java.lang.reflect.*;

public class CompExecutive2 {
  public static void main(String[] args) throws Exception {
    CompTest2 ct=new CompTest2();
    ct.printVar();

    //Get all of the methods on the class.
    Method[] declaredMethods=ct.getClass().getDeclaredMethods();
    System.out.println("Methods on CompTest2: "+declaredMethods.length);
  }
}

What do we expect the output to be? We haven’t added any methods to CompTest, just an inner class declaration. The output should still say that there is only one declared method, right? And yet, when we run it…

This is a private variable
From Inner Class: Is it still private?
Methods on CompTest2: 3

Three? Where did the other two methods come from?

The answer lies in the way that the java compiler deals with inner classes. If you’ve ever worked with inner classes and packaged them, say into a .jar file, you may have noticed that each inner class is actually compiled to its own class file. In the above example, compilation yields three files: CompExecutive2.class, CompTest2.class, and CompTest2$InnerClass.class. This third class file is an independent, compiled class. One might wonder, given the fact that this is a separate class, how it is able to access the private variables inside CompTest2. Answering this question also gives us the secret behind the extra two methods.

We can run CompTest2 through javap, the java disassembler included with the jdk. If we run javap -c CompTest2, we can see the disassembled code that makes up our class. If we do so, we find this:

public class CompTest2 extends java.lang.Object{
public CompTest2();
  Code:
   0:   aload_0
   1:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   ldc     #3; //String This is a private variable
   7:   putfield        #1; //Field myVariable:Ljava/lang/String;
   10:  return

public void printVar();
  Code:
   0:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   getfield        #1; //Field myVariable:Ljava/lang/String;
   7:   invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   10:  new     #6; //class CompTest2$InnerClass
   13:  dup
   14:  aload_0
   15:  invokespecial   #7; //Method CompTest2$InnerClass."<init>":(LCompTest2;)V
   18:  astore_1
   19:  aload_1
   20:  invokevirtual   #8; //Method CompTest2$InnerClass.alsoPrintVar:()V
   23:  return

static java.lang.String access$002(CompTest2, java.lang.String);
  Code:
   0:   aload_0
   1:   aload_1
   2:   dup_x1
   3:   putfield        #1; //Field myVariable:Ljava/lang/String;
   6:   areturn

static java.lang.String access$000(CompTest2);
  Code:
   0:   aload_0
   1:   getfield        #1; //Field myVariable:Ljava/lang/String;
   4:   areturn

}

We can ignore the specifics about how jvm assembler works, but we can notice which methods are defined on the class. First is a constructor - no surprises there. It doesn’t count as a method, so that’s not one of the three. Next is printVar, which looks just how we declared it. But after that, things get strange. There are two extra methods, access$000 and access$002. They both take a CompTest2 instance, and they both return a string. What’s going on here?

What’s going on is that, since the compiler has to put CompTest2$InnerClass inside its own class file, it has to make the private variable, myVariable, accessible to it. Inner Classes are secretly loaded up to be constructed with a reference to the container class, so when methods need access to private members, they call these access methods, passing in the instance of the Outer Class they were given. These access methods can’t be private (since the separate class can’t see them), and Java doesn’t support friend classes like C++, so the least permissive accessibility the access methods can have is package level visibility.

In other words, javac silently adds package-level methods to CompTest2, which allow CompTest2’s private variables to be modified and accessed. If CompTest2 didn’t have the line that changed myVariable, the compiler would have only added one method. As it is, two methods were added: one that simply returns the value, and the other that allows it to be changed.

And yes, you can use reflection to call these methods from a class in the same package as CompTest2. Case in point:

import java.lang.reflect.*;

public class Sneaky2 {
  public static void main(String[] args) throws Exception {
    CompTest2 ct=new CompTest2();
    
    Method secretSetMethod=ct.getClass().getDeclaredMethod("access$002",CompTest2.class, String.class);
    secretSetMethod.invoke(ct,ct," -- Not so private anymore, huh? -- ");

    ct.printVar();
  }
}

Since myVariable is private, you would hope that this code wouldn’t compile, or wouldn’t run, or something. Certainly that it wouldn’t actually change the value of myVariable inside ct. And yet, when you run this program, you get this:

 -- Not so private anymore, huh? -- 
From Inner Class: Is it still private?

If you are wondering why it still prints “Is it still private?”, it’s because the method printVar() tells an InnerClass instance to run its own alsoPrintVar() method, which sets the variable (overwriting what we set using reflection).

There you go. Every time you use inner classes or anonymous inner classes, javac makes a package-visibility method to access or mutate any private variable your inner class needs to access or mutate. If your inner class only reads the variable, it makes the method that only returns it, but if your inner class changes it as well, it makes a second method to change the value.

Thanks again to Ted Neward for his excellent presentation.

comments powered by Disqus