Tuesday, December 22, 2009

Java inner class peculiarity

I recently ran across an obscure Java factoid that will probably never come in handy. I found it when researching PMD rules.

Suppose I have an inner class as follows:

1 package com.examples;

3 public class Outer {

5   public void test() {
6     Inner ic = new Inner();
7   }

9   public class Inner {
10     private Inner() {
11     }
12   }
13 }


Even though the constructor for the inner class Inner is private, a package-access constructor is created by the compiler as a side-effect when you call the private constructor at line 6 in the test method.

There are a couple of ways to verify this. First, you can just look at the generated class file Outer$Inner.class. You'll find two lines, one for the private constructor that you have written, and another which is a "synthetic"* constructor:

..
private Outer$Inner(com.examples.Outer arg0);
..
synthetic Outer$Inner(com.examples.Outer arg0, com.examples.Outer.Inner arg1);


If you are not convinced, you can run through the constructors using reflection, as shown in this example class:

1 package com.examples;
2 import java.lang.reflect.*;

4 public class InnerTester {
5   public static void main(String[] args) throws ClassNotFoundException {
6     InnerTester.printConstructors("com.examples.Outer$Inner");
7   }
8   public static void printConstructors(String str) throws ClassNotFoundException {
9      Class c = Class.forName(str);
10     Constructor[] cs = c.getDeclaredConstructors();
11     for (int i = 0; i < cs.length; i++) {
12       System.out.println(cs[i].toString());
13     }
14   }
15}

Running this program produces two lines of output:

com.examples.Outer$Inner(com.examples.Outer,com.examples.Outer$Inner)
private com.examples.Outer$Inner(com.examples.Outer)


If you comment out line 6 in Outer.java and recompile it, the output of this program is just one line:

private com.examples.Outer$Inner(com.examples.Outer)


You can look at the class file, Outer$Inner.class, and check that the package access constructor is gone, there, too.

There's a rule warning against calling a private constructor of an inner class like this, because the constructor is effectively given package access. PMD suggests using a factory method instead of creating the instance directly in the outer class, or just making the constructor public, to solve the problem.

I can't imagine any situation in which this would be a problem unless you're using reflection to call constructors.


* The Sun tutorial on Java reflection has a brief discussion of synthetic (compiler-generated) constructors.

No comments:

Post a Comment