15

This question is about interesting behavior of Java: it produces additional (not default) constructor for nested classes in some situations.

This question is also about strange anonymous class, which Java produces with that strange constructor.


Consider the following code:

package a;

import java.lang.reflect.Constructor;

public class TestNested {    
    class A {    
        A() {
        }   

        A(int a) {
        }
    }    

    public static void main(String[] args) {
        Class<A> aClass = A.class;
        for (Constructor c : aClass.getDeclaredConstructors()) {
            System.out.println(c);
        }

    }
}

This will prints:

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

Ok. Next, lets make constructor A(int a) private:

    private A(int a) {
    }

Run program again. Receive:

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)

It is also ok. But now, lets modify main() method in such way (addition of new instance of class A creation):

public static void main(String[] args) {
    Class<A> aClass = A.class;
    for (Constructor c : aClass.getDeclaredConstructors()) {
        System.out.println(c);
    }

    A a = new TestNested().new A(123);  // new line of code
}

Then input becomes:

a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1) 

What is it: a.TestNested$A(a.TestNested,int,a.TestNested$1) <<<---??

Ok, lets again make constructor A(int a) package local:

    A(int a) {
    }

Rerun program again (we don't remove line with instance of A creation!), output is as in the first time:

a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)

Questions:

1) How this could be explained?

2) What is this third strange constructor?


UPDATE: Investigation shown following.

1) Lets try to call this strange constructor using reflection from other class. We will not able to do this, because there isn't any way to create instance of that strange TestNested$1 class.

2) Ok. Lets do the trick. Lets add to the class TestNested such static field:

public static Object object = new Object() {
    public void print() {
        System.out.println("sss");
    }
};

Well? Ok, now we could call this third strange constructor from another class:

    TestNested tn = new TestNested();
    TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);

Sorry, but I absolutely don't understand it.


UPDATE-2: Further questions are:

3) Why Java use special anonymous inner class for an argument type for this third synthetic constructor? Why not just Object type, of constructor with special name?

4) What Java could use already defined anonymous inner class for those purposes? Isn't this some kind of violation of security?

Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • That's an anonymous inner class of A that's being passed to the third constructor. The question is why it's there or what's inside. Care to decompile? – John Dvorak Jan 10 '13 at 19:57
  • Perhaps it's just a trick to make the private constructor accessible to the inner classes? – John Dvorak Jan 10 '13 at 19:59
  • @JanDvorak, yes, and where this constructor is actually invoked? I suspect, then when creating instance of `A` passing int value in it. But, really, why? – Andremoniy Jan 10 '13 at 20:00
  • If an inner class can call an outer class' methods, then this is a way to make this possible on the JVM level (JVM has no concept of inner classes). Then it would be used when trying to call `new A(int)` from within the inner class. – John Dvorak Jan 10 '13 at 20:02
  • 1
    @JanDvorak what is the reason for this? Java could doing it easier, just modifying accessible state of the constructor. – Andremoniy Jan 10 '13 at 20:05
  • 1
    but then it would be accessible form the outside... – John Dvorak Jan 10 '13 at 20:16
  • @JanDvorak, yes agree. Ok. And what is this third inner class `a.TestNested$1`? Due to reflection investigation, this class even has not any constructors at all, and can not be instantiated using reflection. – Andremoniy Jan 10 '13 at 20:19
  • It could be a marker class to indicate the access rights to the constructor. I, however, am not sure how does that class work as a token. – John Dvorak Jan 10 '13 at 20:20
  • When you compile this java file, which .class files do you have? – partlov Jan 10 '13 at 20:27
  • @partlov it is a really good question! I have: `TestNested.class`, `TestNested$1.class` and `TestNested$A.class`. This `TestNested$1.class` is a puzzle for me... – Andremoniy Jan 10 '13 at 20:32
  • Furthermore! If we will create some static anonymous class in class `TestNested` (just static field `static Object o = new Object {...}`, then the number and names of generated `.class` files will not change. But if we will create second anonymous class we will receive new `TestNested$2.class`. – Andremoniy Jan 10 '13 at 20:37
  • @partlov, please see my update about anonymous classes. – Andremoniy Jan 10 '13 at 20:44
  • I decompiled generated classes and `TestNested$1` is just one static class. Inner classes can exists just inside a context of outer class. In this example we are instantiating inner class inside a static method, not somewhere in outer class. So, my opinion here (but I'm not shore) that java creates one more class, that is same like our inner class, except it is static. – partlov Jan 10 '13 at 21:02
  • @partlov, Sorry, but nope (as I think). You see, I create just anonymous class `new Object {...}` and Java uses this anonymous class like that `TestNested$1` class. I can even pass its instance to that strange constructor. – Andremoniy Jan 10 '13 at 21:06
  • 1
    @Andremoniy In response to your second update: Java can't just use `Object` as the argument type because you would be able to have your own constructor with an `Object` argument and invoke it without reflection (by compiling `A` with a non-private `A(int, Object)` constructor, saving the `TestNested$A.class` file somewhere else, removing the new constructor, recompiling `TestNested`, and replacing the new `TestNested$A.class` with the version with the `A(int, Object)` constructor.) Using an anonymous type (`TestNested$1`) as the argument type of the synthetic constructor prevents that. – matts Jan 10 '13 at 21:46
  • 1
    @Andremoniy and about violating security: not possible since the argument types for the constructor are bound at compile-time (and when you recompile the outer class, it will also recompile the inner one). Even if you have an object that is the same type as the anonymous type, you can't have a constructor with that type as an argument, so the compiler will pick a different constructor to invoke. – matts Jan 10 '13 at 21:49

2 Answers2

9

The third constructor is a synthetic constructor generated by the compiler, in order to allow access to the private constructor from the outer class. This is because inner classes (and their enclosing classes' access to their private members) only exist for the Java language and not the JVM, so the compiler has to bridge the gap behind the scenes.

Reflection will tell you if a member is synthetic:

for (Constructor c : aClass.getDeclaredConstructors()) {
    System.out.println(c + " " + c.isSynthetic());
}

This prints:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true

See this post for further discussion: Eclipse warning about synthetic accessor for private static nested classes in Java?

EDIT: interestingly, the eclipse compiler does it differently than javac. When using eclipse, it adds an argument of the type of the inner class itself:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true

I tried to trip it up by exposing that constructor ahead of time:

class A {    
    A() {
    }   

    private A(int a) {
    }

    A(int a, A another) { }
}

It dealt with this by simply adding another argument to the synthetic constructor:

a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) false
a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true
Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Thank you for your reply! It is clear, that this method is synthetic. All my question is about this fact, that Java produces such method. The questions are: why it needs special InnerClass for this purposes, why it could use existing inner class for this purposes, and where in specification this things are described? – Andremoniy Jan 10 '13 at 21:18
  • 1
    Very good answer. Compiler add some synthetically created class and uses it. In decompiled code it just write null to that parameter: `new A(123, null)`. In that way compiler is shore that you will not have any methods with that signature. I just don't know why they didn't throw compiler error here. We are calling private method of inner class here!? – partlov Jan 10 '13 at 21:20
  • 1
    @partlov, great! It is really just `null`. Ok! But may be there is some way for invoke this constructor direct from source code, not through reflection? – Andremoniy Jan 10 '13 at 21:23
  • 1
    @Andremoniy I think it is not possibly. We don't have that "class" while we write our code. That class exists only after compile. So, I think it is only possible with reflection. – partlov Jan 10 '13 at 21:26
  • We actually have that class, it could be public static anonymous instance of hand-made class in `TestNested`. The problem is, how we can program invoke this constructor (not through Reflection). – Andremoniy Jan 10 '13 at 21:28
  • 1
    @Andremoniy I don't think it's possible to invoke the synthetic constructor from Java source code. In the bytecode, the constructor invocation is (I think) an `invokespecial` bytecode instruction, which invokes a specific constructor. The compiler is not going to generate bytecode that invokes the synthetic constructor. – matts Jan 10 '13 at 21:30
  • 1
    Yeah, but don't you think it is just artificially created situation. Maybe compiler has this kind of logic: "Ok, I will add constructor with some anonymous class if it exists somewhere here (if for example Andremoniy created it :)), but if such doesn't exist I will create one". :) – partlov Jan 10 '13 at 21:33
  • @partlov, :)))) lol, yes, I also thought so. It could be true :) – Andremoniy Jan 10 '13 at 21:39
  • @PaulBellora, how it could be so? Eclipse and my IDEA are using same JDK compiler. Aren't them? Eclipse has own Java compiler? O_o Why there is this difference? – Andremoniy Jan 10 '13 at 21:44
  • 1
    @Andremoniy See [this post](http://stackoverflow.com/questions/3061654/what-is-the-difference-between-javac-and-the-eclipse-compiler). – Paul Bellora Jan 10 '13 at 21:47
8

First of all, thank you for this interesting question. I was so intrigued that I could not resist taking a look at the bytecode. This is the bytecode of TestNested:

Compiled from "TestNested.java"
  public class a.TestNested {
    public a.TestNested();
      Code:
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return        

    public static void main(java.lang.String[]);
      Code:
         0: ldc_w         #2                  // class a/TestNested$A
         3: astore_1      
         4: aload_1       
         5: invokevirtual #3                  // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
         8: astore_2      
         9: aload_2       
        10: arraylength   
        11: istore_3      
        12: iconst_0      
        13: istore        4
        15: iload         4
        17: iload_3       
        18: if_icmpge     41
        21: aload_2       
        22: iload         4
        24: aaload        
        25: astore        5
        27: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload         5
        32: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        35: iinc          4, 1
        38: goto          15
        41: new           #2                  // class a/TestNested$A
        44: dup           
        45: new           #6                  // class a/TestNested
        48: dup           
        49: invokespecial #7                  // Method "<init>":()V
        52: dup           
        53: invokevirtual #8                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
        56: pop           
        57: bipush        123
        59: aconst_null   
        60: invokespecial #9                  // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
        63: astore_2      
        64: return        
  }

As you can see, the constructor a.TestNested$A(a.TestNested,int,a.TestNested$1) is invoked from your main method. Furthermore, null is passed as the value of the a.TestNested$1 parameter.

So let's take a look at the mysterious anonymous class a.TestNested$1:

Compiled from "TestNested.java"
class a.TestNested$1 {
}

Strange - I would have expected this class to actually do something. To understand it, let's take a look at the constructors in a.TestNested$A: class a.TestNested$A { final a.TestNested this$0;

  a.TestNested$A(a.TestNested);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  private a.TestNested$A(a.TestNested, int);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #2                  // Field this$0:La/TestNested;
       5: aload_0       
       6: invokespecial #3                  // Method java/lang/Object."<init>":()V
       9: return        

  a.TestNested$A(a.TestNested, int, a.TestNested$1);
    Code:
       0: aload_0       
       1: aload_1       
       2: iload_2       
       3: invokespecial #1                  // Method "<init>":(La/TestNested;I)V
       6: return        
}

Looking at the package-visible constructor a.TestNested$A(a.TestNested, int, a.TestNested$1), we can see that the third argument is ignored.

Now we can explain the constructor and the anonymous inner class. The additional constructor is required in order to circumvent the visibility restriction on the private constructor. This additional constructor simply delegates to the private constructor. However, it cannot have the exact same signature as the private constructor. Because of this, the anonymous inner class is added to provide a unique signature without colliding with other possible overloaded constructors, such as a constructor with signature (int,int) or (int,Object). Since this anonymous inner class is only needed to create a unique signature, it does not need to be instantiated and does not need to have content.

Jannik Jochem
  • 1,516
  • 1
  • 14
  • 28