#Static initializer block in inner class

1 messages · Page 1 of 1 (latest)

kind mantle
#

Please bear with me as I've only been learning programming/Java for a couple weeks now, so please excuse me if I use any term incorrectly (or if the question is stupid...)

I've been recently learning about anonymous classes and initializer blocks, which led me to learn that before JDK16 you could not have any static declarations inside inner classes except for constants when trying to do this:

  //Declare and instantiate anonymous class
  static ExtendedByAnonymous anonymousObject = new ExtendedByAnonymous() {
    //Static Initialization Block
    //Static declarations in inner classes are not supported at language level '15'
    static {
      System.out.println("anonymous object created");
    }```

Initially I thought this made sense, since how would you define the behaviour of a field that is static when the class that owns depends on the instance of an outer class? How would you initialize these fields? At the same time as every other static field? Whenever a new instance of the outer class was created? Seemed like it made sense to avoid these ambiguities, but then the Java developers obviously did decide on SOME way to handle this, since it is now allowed. 

So my questions are: 
1) Do the ambiguities I suggested actually exist? Or did I just misunderstand the core concepts of OOP?
2) If they do exist, how did the Java team solve them? Mainly, when are the static fields in inner classes initialized, and who "owns" those fields?

I'm willing to experiment more with this and maybe investigate the bytecode if nobody has a ready answer to the questions and anyone is willing to give me some guidance, as this seems to just a tad above my paygrade 😄
gritty yarrowBOT
#

<@&987246399047479336> please have a look, thanks.

gritty yarrowBOT
#

While you are waiting for getting help, here are some tips to improve your experience:

Code is much easier to read if posted with syntax highlighting and proper formatting.

If nobody is calling back, that usually means that your question was not well asked and hence nobody feels confident enough answering. Try to use your time to elaborate, provide details, context, more code, examples and maybe some screenshots. With enough info, someone knows the answer for sure.

Don't forget to close your thread using the command </help-thread close:1027500463647621170> when your question has been answered, thanks.

wintry mantle
#

lmao, says that they just started learning and their question might be stupid and asks an expert like question with lots of details and research

kind mantle
# wintry mantle lmao, says that they just started learning and their question might be stupid an...

I'm used to experimenting with every concept I try to learn instead of just memorizing the syntax so that I actually know what I'm doing and what's happening under the hood, specially since I don't (didn't?) have anyone to help me with the answers which kinda forced me to dig deeper (thank god stackoverflow seems unnecessarily hostile and bureaucratic otherwise I might not have bothered to research stuff myself lol)

#

That said, these ones do seem to be a bit more complex questions than I'm used to

#

I also found something that might be relevant, maybe someone with more expertise can extract more meaningful information from it than I did:
https://openjdk.org/jeps/395

gritty yarrowBOT
#

Static initializer block in inner class

#

Changed the title to Static initializer block in inner class.

wintry mantle
#

note that for this sort of questions, with that much of great detail and prior research, u would get a lot of positive feedback and upvotes on stackoverflow

#

the question is sorta niche, since nobody uses initializer blocks and inner classes are also not used often

#

so even for experienced people like me, chances are that people dont really know and have to check first as well

#

on SO, theres likely some java/jvm expert around who knows the answer immediately

kind mantle
wintry mantle
#

generally yes. but for this question i feel like a lot of people there would know the answer

kind mantle
#

It's either that or people saying "no don't ever do that this is so stupid, heres how you do it" lol

wintry mantle
#

😄

kind mantle
#

Anyway i'll give a try to investigating the bytecode and see if I can learn anything, I'll post updates if I do

pale folio
#

The relatively simple and boring answer is that there is only one type of (regular) class, at least after compilation.
This means that an inner class is just a regular class with an extra field that points to an instance of the outer class.
In particular, the class object itself is not bound to an instance of the outer class and can have static fields and methods just like any other class.
To prevent that the information is entirely lost, there are extra attributes for complied classes and fields that preserve the original meaning/semantic of the complied Java structures, for example:
https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.7.28

kind mantle
#

Ok so after some messing around with the bytecode here are some results I got:

Source:


    static {
        System.out.println("outer");
    }

     class InnerClass {

         static {
             System.out.println("inner");
         }

         static int a = 2;
     }

    public static void main(String[] args) {

        StaticInInner outer = new StaticInInner(); //as you would expect, the static initialization block of outer runs regardless of these lines of code
        StaticInInner.InnerClass inner = outer.new InnerClass(); //first reference to InnerClass triggers the initialization block (prints inner), this only happens once.
        StaticInInner.InnerClass inner2 = outer.new InnerClass();

        StaticInInner outer2 = new StaticInInner();
        StaticInInner.InnerClass inner3 = outer2.new InnerClass();
        StaticInInner.InnerClass inner4 = outer2.new InnerClass();

        inner.a = 4;
        System.out.println(inner.a); //prints 4
        System.out.println(inner2.a); //prints 4
        System.out.println(inner3.a); //prints 4
        System.out.println(inner4.a); //prints 4
    }
}```
gritty yarrowBOT
# kind mantle Ok so after some messing around with the bytecode here are some results I got: ...

Detected code, here are some useful tools:

Formatted code
public class StaticInInner {
  static {
    System.out.println("outer");
  }
  class InnerClass {
    static {
      System.out.println("inner");
    }
    static int a = 2;
  }
  public static void main(String[] args) {
    StaticInInner outer = new StaticInInner();
    //as you would expect, the static initialization block of outer runs regardless of these lines of code
    StaticInInner.InnerClass inner = outer.new InnerClass();
    //first reference to InnerClass triggers the initialization block (prints inner), this only happens once.
    StaticInInner.InnerClass inner2 = outer.new InnerClass();
    StaticInInner outer2 = new StaticInInner();
    StaticInInner.InnerClass inner3 = outer2.new InnerClass();
    StaticInInner.InnerClass inner4 = outer2.new InnerClass();
    inner.a = 4;
    System.out.println(inner.a);
    //prints 4
    System.out.println(inner2.a);
    //prints 4
    System.out.println(inner3.a);
    //prints 4
    System.out.println(inner4.a);
    //prints 4
  }
}
kind mantle
#

But this is the part we'll focus on:

        78: pop
        79: iconst_4
        80: putstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
        83: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        86: aload_2
        87: pop
        88: getstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
        91: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
        94: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        97: aload_3
        98: pop
        99: getstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
       102: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
       105: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
       108: aload         5
       110: pop
       111: getstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
       114: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
       117: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
       120: aload         6
       122: pop
       123: getstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
       126: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
       129: return```
gritty yarrowBOT
# kind mantle But this is the part we'll focus on: ``` 77: aload_2 78: pop ...

Detected code, here are some useful tools:

Formatted code
77 : aload_278 : pop79 : iconst_480 : putstatic#21// Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
83 : getstatic#25// Field java/lang/System.out:Ljava/io/PrintStream;
86 : aload_287 : pop88 : getstatic#21// Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
91 : invokevirtual#31// Method java/io/PrintStream.println:(I)V
94 : getstatic#25// Field java/lang/System.out:Ljava/io/PrintStream;
97 : aload_398 : pop99 : getstatic#21// Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
102 : invokevirtual#31// Method java/io/PrintStream.println:(I)V
105 : getstatic#25// Field java/lang/System.out:Ljava/io/PrintStream;
108 : aload5110 : pop111 : getstatic#21// Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
114 : invokevirtual#31// Method java/io/PrintStream.println:(I)V
117 : getstatic#25// Field java/lang/System.out:Ljava/io/PrintStream;
120 : aload6122 : pop123 : getstatic#21// Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
126 : invokevirtual#31// Method java/io/PrintStream.println:(I)V
129 : return
kind mantle
#

Basically it shows that the compiler tries to start the process of either changing the variable value or reading it as if it were a non-static variable but then gives up(?), notice the aloads followed by pops, then either adding the value to the stack when changing the variable or just reading the value from the static field when printing the variable

#

So essentially all it does is change/read the static variable, none of the instance have their values read or changed directly

#

The compiler does give you a hint that that's what happening
"Static member 'urlhere.sitename.projectname.packagedenominator.StaticInInner.InnerClass.a' accessed via instance reference"

#

So then I tried to change the bytecode to make it so that it would try to read the variable as if it were an instance variable by changing pop to nop and getstatic to getfield:

        87: nop
        88: getfield      #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
        91: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
        94: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;```
#

Which gives the error:
"Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected non-static field urlhere.sitename.projectname.packagedenominator.StaticInInner$InnerClass.a
at urlhere.sitename.projectname.packagedenominator.StaticInInner.main(StaticInInner.java:29)"

Which suggests putfield/getfield doesn't accept static fields, which makes sense

#

So far this seems to suggest that these variables aren't much different from any other static variables. Whenever we modify or read it, it always points to the same static field, disregarding both what instance of the inner class its being called from, pretty normal, but ALSO disregarding what instance of the outer class the inner class instance refers to. So "inner" and "inner2", which point to "outer", share the same static field with "inner3" and "inner4" which point to "outer2". I feel like this should clear the doubt of whether the static variables in an inner class depend on the instance of the outer class.

#

Also, just for curiosity, I tried removing the superfluous aload and pop opcodes from before getstatic, and it did work fine. Now, I ASSUME that it's probably there for a reason that just isn't manifested in this specific situation, but I decided to add this bit of info in case anyone knows anything more about it

example:

       120: getstatic     #21                 // Field urlhere/sitename/projectname/packagedenominator/StaticInInner$InnerClass.a:I
       123: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
       126: return```
#

Now, to talk specifically about initialization, a quick test seems to suggest the inner class is only initialized when it's actually needed.


 static {
     System.out.println("outer static initialization");
 }

 {
     System.out.println("outer instance initialization");
 }

  class InnerClass {

      static {
          System.out.println("inner static initialization");
      }

      {
          System.out.println("inner instance initialization");
      }

      static int a = 2;
  }

 public static void main(String[] args) {

     StaticInInner outer = new StaticInInner(); //as you would expect, the static initialization block of outer runs regardless of these lines of code
     StaticInInner.InnerClass inner = outer.new InnerClass(); //first reference to InnerClass triggers the static initialization block (prints inner), this only happens once.
     StaticInInner.InnerClass inner2 = outer.new InnerClass(); //doesn't run the static initialization block again, only the instance initialization block

     StaticInInner outer2 = new StaticInInner();
     StaticInInner.InnerClass inner3 = outer2.new InnerClass();
     StaticInInner.InnerClass inner4 = outer2.new InnerClass();

 }
}```


output:
```outer static initialization
outer instance initialization
inner static initialization
inner instance initialization
inner instance initialization
outer instance initialization
inner instance initialization
inner instance initialization```
gritty yarrowBOT
# kind mantle Now, to talk specifically about initialization, a quick test seems to suggest th...

Detected code, here are some useful tools:

Formatted code
public class StaticInInner {
  static {
    System.out.println("outer static initialization");
  }
    {
    System.out.println("outer instance initialization");
  }
  class InnerClass {
    static {
      System.out.println("inner static initialization");
    }
      {
      System.out.println("inner instance initialization");
    }
    static int a = 2;
  }
  public static void main(String[] args) {
    StaticInInner outer = new StaticInInner();
    //as you would expect, the static initialization block of outer runs regardless of these lines of code
    StaticInInner.InnerClass inner = outer.new InnerClass();
    //first reference to InnerClass triggers the static initialization block (prints inner), this only happens once.
    StaticInInner.InnerClass inner2 = outer.new InnerClass();
    //doesn't run the static initialization block again, only the instance initialization block
    StaticInInner outer2 = new StaticInInner();
    StaticInInner.InnerClass inner3 = outer2.new InnerClass();
    StaticInInner.InnerClass inner4 = outer2.new InnerClass();
  }
}
kind mantle
#

If we remove all new instance creations of InnerClass, it is not initialized, unlike the top-level class

#

Code:


    static {
        System.out.println("outer static initialization");
    }

    {
        System.out.println("outer instance initialization");
    }

     class InnerClass {

         static {
             System.out.println("inner static initialization");
         }

         {
             System.out.println("inner instance initialization");
         }

         static int a = 2;
     }

    public static void main(String[] args) {

        StaticInInner outer = new StaticInInner(); //as you would expect, the static initialization block of outer runs regardless of these lines of code
        StaticInInner outer2 = new StaticInInner();

    }
}```

Output:
```outer static initialization
outer instance initialization
outer instance initialization```
#

If we call any static member of the inner class, it also runs the static initialization block.

Code:


    static {
        System.out.println("outer static initialization");
    }

    {
        System.out.println("outer instance initialization");
    }

     class InnerClass {

         static {
             System.out.println("inner static initialization");
         }

         {
             System.out.println("inner instance initialization");
         }

         static int a = 2;
     }

    public static void main(String[] args) {

        StaticInInner outer = new StaticInInner(); //as you would expect, the static initialization block of outer runs regardless of these lines of code
        StaticInInner outer2 = new StaticInInner();
        System.out.println(InnerClass.a);

    }
}```

Output: 
```outer static initialization
outer instance initialization
outer instance initialization
inner static initialization
2```
#

It does make sense that the inner class would only be initialized when necessary, instead of at the start of the program, since it's just a non-static member of the outer class anyway, so no real surprises there I guess

#

I think that's it, maybe someone has anything else to add or any suggestions

clever vortex
clever vortex
#

I write InnerClass like this and InnerClass.a is 2 when it's printed
class InnerClass {
static {
a = 2;
}

    {
        System.out.println("inner instance initialization");
    }
    static int a;
}
clever vortex
kind mantle
kind mantle
#

I have to sleep now but maybe we can continue this tomorrow if anyone has any new information

kind mantle
pale folio
#

Another thing you experienced is that in general, all static accesses don't require any instance of the class. The compiler only uses the type to determine the static member being used.
This is also why

((InnerClass) null).a

works as well. No inner class instance of needed to access a.
This is a deliberate feature of the language and it's compiler, although not a useful one. The compiler knows this is a static variable and treats it as such in the accesses.
I would have to search in the specifications why the instance is placed on the stack and then removed immediately. Could have a lot of reasons like compatibility.

kind mantle
# pale folio This guess is correct

would you say its fair to conclude that static members of inner classes/the inner class itself are only loaded by the jvm when it deems "necessary" to do so, as opposed to being loaded at startup like top-level classes/their static members are?

kind mantle
# pale folio Another thing you experienced is that in general, all static accesses don't requ...

regarding this, can i go as far as to say that not only are instances not NEEDED to access static variables, it is in fact IMPOSSIBLE to access static variables through an instance (at the bytecode level, not in source) since the getfield/putfield bytecodes seem to refuse to operate on static fields, meaning the only way to access those fields is through getstatic/putstatic which don't take objectref values (for obvious reasons)?

pale folio
#

(think about how many top level classes there are, loading all of them before the application starts would take long)

kind mantle
kind mantle
# pale folio This is true for all classes though, top level classes are loaded as needed, too...

maybe someone with a deep understanding of the jvm could make a plugin that for an ide that shows what lines of code could possibly be loading a class, i'd imagine you can't narrow it down to a single one at compile time since it'd depend on information about the state of the program but it should be able to show the ones that COULD initialize it and which ones would never be able to since the class would've been loaded before reaching it

#

prob wouldn't be all that useful tho so i doubt someone would bother lol

pale folio
#

The list of classes that could get loaded is very large, and the list of the ones that couldn't is even larger. In addition to that, there are classes/implementations defined at runtime and reflection, all making it pretty impossible to know what will happen.
But given that there are projects like GraalVM, which can compile Java to machine code, it is possible if you restrict the abilities of the JDK and reflective operations.

primal hollow
#

All static initialization happens at the first moment of use. If you never access that variable, it never gets initialized.