3

here is the code

public class ClassResolution {
static class Parent {
    public static String name;
    static {
        System.out.println("this is Parent");
        name = "Parent";
    }

}

static class Child extends Parent {
    static {
        System.out.println("this is Child");
        name = "Child";
    }

}

public static void main(String[] args) throws ClassNotFoundException {
    System.out.println(Child.name);
}}

what ouput i expect is:

this is Parent
this is Child
Child

but actually is:

this is Parent
Parent

it seems static block in Child class does not get executed, but why? it's anti-intuition, doesn't it?

supplement:
to make it more clear, i list the 2 1 points below:

  1. As @axtavt say, according to JLS 12.4.1, class Child is loaded, but not initialized.
  2. But @Alexei Kaigorodov pointed out, according to jvms-5.5, class Child should be initialized, because of the execution of instruction getstatic on Child class.

what do you think?

supplement2:
@Alexei Kaigorodov has renewed his mind, so it seems no disagreement left. But I think the point of Alexei Kaigorodov is enlightening, so I left it there.

Thank you, everyone.

turtledove
  • 25,464
  • 3
  • 23
  • 20

5 Answers5

4

From JLS 12.4.1:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

As you can see, nothing of these happens in your code (note that name is declared in Parent, not in Child), therefore Child doesn't get initialized and its static block doesn't get executed.

If you do something to trigger initialization of Child, you'll get an expected output:

new Child();
System.out.println(Child.name); 

Note, however, that static fields are not inherited, therefore Child.name and Parent.name actually refer to the same field. That's why it doesn't make much sense to use code similar to your example in practice.

Also note that despite the fact that Child.name actually refers to Parent.name, it's still referenced as Child.name in the bytecode, therefore your code triggers loading of Child, but not its initialization.

axtavt
  • 239,438
  • 41
  • 511
  • 482
3

As Child.name is really Parent.name, Child is not needed.

You might find this interesting.

public class ClassResolution {
    static class Parent {
        public static String name;

        static {
            System.out.println("this is Parent");
            name = "Parent";
        }

    }

    static class Child extends Parent {
        static {
            System.out.println("this is Child");
            name = "Child";
        }

        static String word ="hello";
    }

    public static void main(String[] args) {
        System.out.println(Child.name);
        System.out.println(Child.word);
        System.out.println(Child.name);
    }
}

prints

this is Parent
Parent
this is Child
hello
Child

The javap for this class prints that the references to Child are retained.

C:\>javap -c -classpath . ClassResolution
Compiled from "ClassResolution.java"
public class ClassResolution {
  public ClassResolution();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: getstatic     #3                  // Field ClassResolution$Child.name:Ljava/lang/String;
       6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: getstatic     #5                  // Field ClassResolution$Child.word:Ljava/lang/String;
      15: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: getstatic     #3                  // Field ClassResolution$Child.name:Ljava/lang/String;
      24: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
2

JLS#12.4.1. When Initialization Occurs

A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface.

I guess above says it all..

Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • 1
    this is because compiler reserves the right to refer the field directly. But in our case, reference to Child is retained, so we have to read JVM spec: http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5 "Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already." And in the code we see `getstatic so/ClassResolution$Child.name` instruction. – Alexei Kaigorodov Sep 21 '12 at 12:37
1

In short, Child.name is equal to Parent.name, and the compiler compiles it like so.

Because name is a static field of the Parent class, it is a class method in Parent, not Child. The Java compiler allows a shortcut where child classes can call static parent class methods/fields as if they come from their own class, but internally they are compiled with regards to the parent class.

Although your code refers to Child.name, it is internally Parent.name, as handled by the compiler. Then, because the Child class isn't initialized, the <clinit> static initializer block is never run, and name remains "Parent".

FThompson
  • 28,352
  • 13
  • 60
  • 93
0

Oops, I was wrong, the reference to Child was not removed and the class was really loaded, but not initialized. Congratulations, you have found a JVM bug. Go to Oracle site and file it. Let me know if you don't want to do so, I'll do it myself.

EDIT: I was wrong again, see comment below. This is not a bug, this is a "gotcha".

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • as axtavt point out, the JLS specified when a Class get initialized, and i thought it's initialized when it's loaded also. so it seems this is not a bug, but a trap! – turtledove Sep 21 '12 at 12:36
  • What exactly do you find to be a bug? The fact that a class is loaded without initializing? That would certainly not be a bug, a class can be loaded at any time. The fact that the error is produced even if no fields from the class are referenced? Before loading the class the runtime won't know whether it possesses the referenced field, so again no bug. – Marko Topolnik Sep 21 '12 at 12:37
  • @ Marko Topolnik see my comment to Amid's answer – Alexei Kaigorodov Sep 21 '12 at 12:41
  • Maybe you are right, it conflicts with JVM spec! actually, it's you found this bug, i just met it! – turtledove Sep 21 '12 at 13:01
  • 1
    After reading carefully all the relevant JVM spec parts, I have to say that was a false alarm. The assertion "Upon execution of a getstatic ... the class ... that declared the resolved field ... is initialized" requires to initialize the class that declared the resolved field (`Parent`), and resolved field is not referenced field in our case. The field resolution itself requires creating referenced class (`Child`), but not initializing it. – Alexei Kaigorodov Sep 21 '12 at 16:44