Why does having a static nested class cause a second constructor to be added when it's not in the source?

后端 未结 1 1220
無奈伤痛
無奈伤痛 2021-01-13 17:26

I was trying to break BillPaugh Singleton solution using reflection, well I am able to do it but I can see two constructors while accessing the BillPaughSingleTon solution.

相关标签:
1条回答
  • 2021-01-13 18:11

    Here's my take:

    Because the SingleTonBillPaugh constructor in the source is private, SingleTonBillPaugh$SingleTonHelper can't access it, so the compiler generates a synthetic constructor SingleTonBillPaugh$SingleTonHelper can access. This is what synthetic methods and constructors are for: To provide a means of accessing the containing class's private data.

    The bigger question to my mind is why does the synthetic constructor accept an argument, and why is the type of the argument SingleTonBillPaugh$1? (Your screenshot makes it look like it's a SingleTonBillPaugh instance, but in my tests it's actually a SingleTonBillPaugh$1 instance — that is, there's a third class being generated here in addition to SingleTonBillPaugh and SingleTonBillPaugh$SingleTonHelper).

    My answer to that question is: Because otherwise, there would be two constructors differentiated only by the fact that one is synthetic and accessible by SingleTonBillPaugh$SingleTonHelper and the other isn't. Java requires that signatures be more different than that, so it generates a class for the sole reason of differentiating the synthetic constructor from the non-synthetic one.

    We can see that we do indeed have a SingleTonBillPaugh$1 class, and if we javap -p -c SingleTonBillPaugh\$1 it, we get:

    class SingleTonBillPaugh$1 {
    }
    

    Doesn't get a lot more minimal than that, which suggests it is indeed purely to act as an argument type for the synthetic constructor. We can further confirm that by looking at the bytecode for SingleTonBillPaugh$SingleTonHelper using javap -p -c SingleTonBillPaugh\$SingleTonHelper:

    class SingleTonBillPaugh$SingleTonHelper {
      private static final SingleTonBillPaugh instance;
    
      SingleTonBillPaugh$SingleTonHelper();
        Code:
           0: aload_0
           1: invokespecial #2                  // Method java/lang/Object."<init>":()V
           4: return
    
      static SingleTonBillPaugh access$000();
        Code:
           0: getstatic     #1                  // Field instance:LSingleTonBillPaugh;
           3: areturn
    
      static {};
        Code:
           0: new           #3                  // class SingleTonBillPaugh
           3: dup
           4: aconst_null
           5: invokespecial #4                  // Method SingleTonBillPaugh."<init>":(LSingleTonBillPaugh$1;)V
           8: putstatic     #1                  // Field instance:LSingleTonBillPaugh;
          11: return
    }
    

    Note how (right near the end) it's calling the one-argument version of the constructor (passing in null).

    And in fact, it seems to always do this — add one new argument to the end of the constructor's argument list. If I change the private constructor to accept a String and update SingleTonHelper to pass it "", the synthetic constructor ends up being SingleTonBillPaugh(String, SingleTonBillPaugh$1).


    Re your question below:

    I kept one sysout in no argument constructor and when inner class called no argument constructor of outer class (which supposedly be synthetic constructor) the same sysout is printed. Why so? is it because internally synthetic constructor calls my provided pvt constructor?

    Exactly, the synthetic constructor calls the private one. Times like this, it's nice to dip into the bytecode:

    Here's my copy of SingleTonBillPaugh.java:

    public class SingleTonBillPaugh
    {
        public static SingleTonBillPaugh getInstance()
        {
            return SingleTonHelper.instance;
        }
    
        private SingleTonBillPaugh()
        {
            System.out.println(Thread.currentThread().getName() + " instance is going to be created");
        }
    
        static class SingleTonHelper
        {
            private static final SingleTonBillPaugh instance = new SingleTonBillPaugh();
        }
    }
    

    If we compile that, then use javap -p -c SingleTonBillPaugh, we get:

    public class SingleTonBillPaugh {
      public static SingleTonBillPaugh getInstance();
        Code:
           0: invokestatic  #2                  // Method SingleTonBillPaugh$SingleTonHelper.access$000:()LSingleTonBillPaugh;
           3: areturn
    
      private SingleTonBillPaugh();
        Code:
           0: aload_0
           1: invokespecial #3                  // Method java/lang/Object."<init>":()V
           4: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
           7: new           #5                  // class java/lang/StringBuilder
          10: dup
          11: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
          14: invokestatic  #7                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
          17: invokevirtual #8                  // Method java/lang/Thread.getName:()Ljava/lang/String;
          20: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          23: ldc           #10                 // String  instance is going to be created
          25: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          28: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          31: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          34: return
    
      SingleTonBillPaugh(SingleTonBillPaugh$1);
        Code:
           0: aload_0
           1: invokespecial #1                  // Method "<init>":()V
           4: return
    }
    

    As we can see, the SingleTonBillPaugh(SingleTonBillPaugh$1) constructor, written in source code form, woudl basically be:

    SingleTonBillPaugh(SingleTonBillPaugh$1 unused) {
        this();
    }
    
    0 讨论(0)
提交回复
热议问题