Joonas Javanainen

Java pitfalls: Field access in inner classes

This is not a “pitfall” per se, but an implementation detail worth knowing. Let’s say I have a inner class with a field. Such a field is visible to the enclosing class, but which one of the following ways is the fastest way to access it? Note! I’m only looking here at the generated bytecode, and not considering any JIT optimizations, so this “performance analysis” is very naïve

public class Test {

  public void testAlternatives() {
    // Alternative 1
    System.out.println(new Inner().field);
    // Alternative 2
    System.out.println(new Inner().getField());
    // Alternative 3
    System.out.println(new Inner2().field);
    // Alternative 4
    System.out.println(new Inner2().getField());
  }

  class Inner {
    private int field;

    public int getField() {
      return field;
    }
  }

  class Inner2 {
    int field;

    public int getField() {
      return field;
    }
  }

}

An intuitive answer is that alternatives 1 and 3 are equally fast because the field is always visible to the enclosing class, and both use field access which is overall slightly faster than method access used in alternatives 2 and 4. However, there’s an implementation detail that causes this to be untrue. The JVM itself does not have a concept called “inner classes”. The whole concept is implemented by the Java compiler and in the bytecode level everything consists of normal classes.

The issue here is that if the inner class has a private field, and the compiler will eventually compile the inner class as a normal class. A private field in a normal class cannot be accessed by other classes, so the enclosing Test class cannot “see” the field without some tricks. Here’s the above code “desugared” to what the compiler actually compiles to bytecode:

public class Test {
  public void testAlternatives() {
    // Alternative 1
    System.out.println(Test$Inner.access$000(new Test$Inner(this)));
    // Alternative 2
    System.out.println(new Test$Inner(this).getField());
    // Alternative 3
    System.out.println(new Test$Inner2(this).field);
    // Alternative 4
    System.out.println(new Test$Inner2(this).getField());
  }
}

class Test$Inner {
  final Test this$0;

  private int field;

  Test$Inner(Test test) {
    this$0 = test;
  }

  public int getField() {
    return field;
  }

  static int access$000(Test$Inner inner) {
    return inner.field;
  }

}

class Test$Inner2 {
  final Test this$0;

  int field;

  Test$Inner2(Test test) {
    this$0 = test;
  }

  public int getField() {
    return field;
  }

}

As you can see, a package-level static accessor method called access$000 is generated in order to grant access to the private field. Now it’s easier to see that alternative 3 will most likely be the fastest one, because it is the only one that uses direct field access. Using package access in fields is a micro-optimization, but this whole thing is definitely a detail that should be known by Java developers. In performance-critical parts of code it might actually matter, and the Android performance guide actually mentions this implementation detail.

This implementation detail may also cause slight confusion when field access is attempted on a null reference of the inner class. Consider the following code:

public class NullTest {
  class Inner {
    private int field;
  }

  public void test() {
    Inner inner = null;
    System.out.println(inner.field);
  }

  public static void main(String[] args) {
    new NullTest().test();
  }
}

The variable “inner” is null, so a NullPointerException is obviously thrown. However, what is not apparent from the original code is that the exception is thrown inside the compiler-generated static accessor method!

$ java NullTest
Exception in thread "main" java.lang.NullPointerException
	at NullTest$Inner.access$000(NullTest.java:2)
	at NullTest.test(NullTest.java:8)
	at NullTest.main(NullTest.java:12)

The stack trace contains the intuitive exception source (line 8), but the real source will confuse developers who don’t know about compiler-generated accessor methods.