Why is an anonymous inner class containing nothing generated from this code?
Asked Answered
B

5

33
package com.test;

public class OuterClass {
    public class InnerClass {
        public class InnerInnerClass {

        }
    }

    public class InnerClass2 {

    }

    //this class should not exist in OuterClass after dummifying
    private class PrivateInnerClass {
        private String getString() {
            return "hello PrivateInnerClass";
        }
    }

    public String getStringFromPrivateInner() {
        return new PrivateInnerClass().getString();
    }
}

When run through javac on the command line with Sun JVM 1.6.0_20, this code produces 6 .class files:

OuterClass.class
OuterClass$1.class
OuterClass$InnerClass.class
OuterClass$InnerClass2.class
OuterClass$InnerClass$InnerInnerClass.class
OuterClass$PrivateInnerClass.class

When run through JDT in eclipse, it produces only 5 classes.

OuterClass.class
OuterClass$1.class
OuterClass$InnerClass.class
OuterClass$InnerClass2.class
OuterClass$InnerClass$InnerInnerClass.class
OuterClass$PrivateInnerClass.class

When decompiled, OuterClass$1.class contains nothing. Where is this extra class coming from and why is it created?

Bacolod answered 21/5, 2010 at 15:0 Comment(4)
I was able to confirm this. class OuterClass$1 extends java.lang.Object{ }. Weird.Tangerine
does it go away if you make PrivateInnerClass public? Maybe it has something to do with "faking" private access to a class.Enounce
@Alex: no, the bug report from 1999 mentions of dummy class too. See my "answer".Tangerine
Thanks all. I think I'm able to get around my issues based on the various fact-finding missions I sent you all on. :)Bacolod
T
12

I don't have the answer, but I'm able to confirm that, and reduce the snippet to the following:

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}

This creates OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}

And here's javap -c for OuterClass.class:

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

public void instantiate();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
                          //(LOuterClass;LOuterClass$1;)V
   9:   pop
   10:  return

}

And for OuterClass$PrivateInnerClass:

Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;

OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokespecial   #1; //Method "<init>":(LOuterClass;)V
   5:   return

}

As you can see, the synthesized constructor takes an OuterClass$1 argument.

So javac creates the default constructor to take an extra argument, of type $1, and the value of that default argument is 5: aconst_null.


I've found that $1 doesn't get created if either of the following is true:

  • You make public class PrivateInnerClass
  • You declare a nullary constructor for PrivateInnerClass
  • Or you don't call the new on it
  • Probably other things (e.g. static nested, etc).

Possibly related

  • Bug ID:4295934: Compiling a private inner class creates an anonymous class file in the wrong dir

Create the following source in a directory called test:

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}

Compile the file from the parent directory javac test/testClass.java

Notice that the file testClass$1.class is created in the current directory. Not sure why this file is even created since there is also a test/testClass$Inner.class created as well.

EVALUATION

The testClass$1.class file is for a dummy class needed by an "access constructor" for the private constructor of the private inner class testClass$Inner. Dissassembly shows that the fully-qualified name of this class is correctly noted, so it is unclear why the class file ends up in the wrong directory.

Tangerine answered 21/5, 2010 at 15:15 Comment(1)
When the same class is run via Eclipse, the empty class(OuterClass$1.class) is not getting created. Any ideaRugg
P
26

I'm using polygenelubricants's smaller snippet.

Remember there's no concept of nested classes in the bytecode; the bytecode is, however, aware of access modifiers. The problem the compiler is trying to circumvent here is that the method instantiate() needs to create a new instance of PrivateInnerClass. However, OuterClass does not have access to PrivateInnerClass's constructor (OuterClass$PrivateInnerClass will be generated as a package-protected class without a public constructor).

So what can the compiler do? The obvious solution is to change PrivateInnerClass to have a package-protected constructor. The problem here is that this will allow any other code which interfaces with the class to create a new instance of PrivateInnerClass, even though it's explicitly declared as private!

To try and prevent that, the javac compiler is doing a little trick: instead of making PrivateInnerClass's regular constructor visible from other classes, it leaves it as hidden (actually it does not define it at all, but that's the same thing from outside). Instead, it creates a new constructor which receives an additional parameter of the special type OuterClass$1.

Now, if you look at instantiate(), it calls that new constructor. It actually sends null as the 2nd parameter (of the type OuterClass$1) - that parameter is only used for specifying that this constructor is the one that should be called.

So, why create a new type for the 2nd parameter? Why not use, say, Object? It's only used to differentiate it from the regular constructor and null is passed anyway! And the answer is that as OuterClass$1 is private to OuterClass, a legal compiler will never allow the user to invoke the special OuterClass$PrivateInnerClass constructor, as one of the required parameter types, OuterClass$1, is hidden.

I'm guessing JDT's compiler uses another technique to solve the same problem.

Pretend answered 21/5, 2010 at 15:48 Comment(1)
and OP: if my smaller snippet is an easier subject of discussion, feel free to hoist it into the question instead (since my "answer" is more of a fact finding mission appropriate for a question anyway).Tangerine
T
12

I don't have the answer, but I'm able to confirm that, and reduce the snippet to the following:

public class OuterClass {
    private class PrivateInnerClass {
    }
    public void instantiate() {
        new PrivateInnerClass();
    }
}

This creates OuterClass$1.class

Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}

And here's javap -c for OuterClass.class:

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

public void instantiate();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
                          //(LOuterClass;LOuterClass$1;)V
   9:   pop
   10:  return

}

And for OuterClass$PrivateInnerClass:

Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;

OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokespecial   #1; //Method "<init>":(LOuterClass;)V
   5:   return

}

As you can see, the synthesized constructor takes an OuterClass$1 argument.

So javac creates the default constructor to take an extra argument, of type $1, and the value of that default argument is 5: aconst_null.


I've found that $1 doesn't get created if either of the following is true:

  • You make public class PrivateInnerClass
  • You declare a nullary constructor for PrivateInnerClass
  • Or you don't call the new on it
  • Probably other things (e.g. static nested, etc).

Possibly related

  • Bug ID:4295934: Compiling a private inner class creates an anonymous class file in the wrong dir

Create the following source in a directory called test:

package test;
public class testClass
{
    private class Inner
    {
    }
    public testClass()
    {
        Inner in = new Inner();
    }
}

Compile the file from the parent directory javac test/testClass.java

Notice that the file testClass$1.class is created in the current directory. Not sure why this file is even created since there is also a test/testClass$Inner.class created as well.

EVALUATION

The testClass$1.class file is for a dummy class needed by an "access constructor" for the private constructor of the private inner class testClass$Inner. Dissassembly shows that the fully-qualified name of this class is correctly noted, so it is unclear why the class file ends up in the wrong directory.

Tangerine answered 21/5, 2010 at 15:15 Comment(1)
When the same class is run via Eclipse, the empty class(OuterClass$1.class) is not getting created. Any ideaRugg
K
7

Based on the answer of polygenelubricants, I would guess that this mysterious class prevents anybody else (i.e., outside from OuterClass) from instantiating a OuterClass$PrivateInnerClass, because they don't have access to OuterClass$1.

Kiehl answered 21/5, 2010 at 15:32 Comment(1)
Man, it took me 22 lines to give the same answer. You rock :)Pretend
O
4

After searching I found this link. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717

Comment is referring to source code that is available in given link.

This is not a bug.

The compiler is trying to solve an access problem. Since the inner class Test.Request is private its constructor is private. This can be seen if you use -private to javap:

$ javap -private Test\$Request Compiled from "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; private Test$Request(Test); Test$Request(Test, Test$1); }

However, the JVM will not allow the anonymous subclass of Coucou (Test$1) access to this private constructor. This is a fundamental difference between the JVM and the Java programming language when it comes to nested classes. The language allows nested classes to access private members of the enclosing class.

Originally, when nested classes were added to the language, the solution to this problem was to make the constructor package private and would have looked like this:

$ javap -private Test\$Request Compiled from "Test.java" final class Test$Request extends java.lang.Object{ final Test this$0; Test$Request(Test); }

However, this can easily lead to problems where you can get access to the constructor when you shouldn't. To address this problem, the current solution was invented. The "real" constructor will remain private:

private Test$Request(Test);

However, other nested classes must be allowed to call this constructor. So an access constructor must be provided. However, this access constructor must be different from the "real" constructor. To solve this problem the compiler adds an extra parameter to the access constructor. The type of this extra parameter must be something unique that doesn't conflict with anything the user might have written. So an obvious solution is to add an anonymous class and use that as the type of the second parameter:

Test$Request(Test, Test$1);

However, the compiler is clever and reuses any anonymous class if one exists. If you change the example to not include an anonymous class, you will see that the compiler will create one:

public abstract class Test { private final class Request {} private final class OtherRequest { Request test() { return new Request(); } } }

If there is no access to the private constructor, the compiler doesn't need to generate any access constructor which explains the behavior of this example:

public abstract class Test { private final class Request {} }

Octoroon answered 9/6, 2016 at 19:22 Comment(0)
A
0

One more spot - if OuterClass$1 is already declared by user, OuterClass$PrivateInnerClass will have it as a constructor argument anyways:

public class OuterClass { 

    ... 

    public String getStringFromPrivateInner() { 
        PrivateInnerClass c = new PrivateInnerClass();
        Object o = new Object() {};
        return null;
    }
}

-

public java.lang.String getStringFromPrivateInner();
  Code:
   0:   new     #2; //class OuterClass$PrivateInnerClass
   3:   dup
   4:   aload_0
   5:   aconst_null
   6:   invokespecial   #3; //Method OuterClass$PrivateInnerClass."":
(LOuterClass;LOuterClass$1;)V
   9:   astore_1
   10:  new     #4; //class OuterClass$1
   13:  dup
   14:  aload_0
   15:  invokespecial   #5; //Method OuterClass$1."":(LOuterClass;)V
   18:  astore_2
   19:  aconst_null
   20:  areturn
Armanda answered 21/5, 2010 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.