简体   繁体   中英

Why is a subclass' static initializer not invoked when a static method declared in its superclass is invoked on the subclass?

Given the following classes:

public abstract class Super {
    protected static Object staticVar;

    protected static void staticMethod() {
        System.out.println( staticVar );
    }
}

public class Sub extends Super {
    static {
        staticVar = new Object();
    }

    // Declaring a method with the same signature here, 
    // thus hiding Super.staticMethod(), avoids staticVar being null
    /*
    public static void staticMethod() {
        Super.staticMethod();
    }
    */
}

public class UserClass {
    public static void main( String[] args ) {
        new UserClass().method();
    }

    void method() {
        Sub.staticMethod(); // prints "null"
    }
}

I'm not targeting at answers like "Because it's specified like this in the JLS.". I know it is, since JLS, 12.4.1 When Initialization Occurs reads just:

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 a static method declared by T is invoked.

  • ...

I'm interested in whether there is a good reason why there is not a sentence like:

  • T is a subclass of S and a static method declared by S is invoked on T.

Be careful in your title, static fields and methods are NOT inherited. This means that when you comment staticMethod() in Sub , Sub.staticMethod() actually calls Super.staticMethod() then Sub static initializer is not executed.

However, the question is more interesting than I thought at the first sight : in my point of view, this shouldn't compile without a warning, just like when one calls a static method on an instance of the class.

EDIT: As @GeroldBroser pointed it, the first statement of this answer is wrong. Static methods are inherited as well but never overriden, simply hidden. I'm leaving the answer as is for history.

The JLS is specifically allowing the JVM to avoid loading the Sub class, it's in the section quoted in the question:

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.

The reason is to avoid having the JVM load classes unnecessarily. Initializing static variables is not an issue because they are not getting referenced anyway.

The reason is quite simple: for JVM not to do extra work prematurely (Java is lazy in its nature).

Whether you write Super.staticMethod() or Sub.staticMethod() , the same implementation is called. And this parent's implementation typically does not depend on subclasses. Static methods of Super are not supposed to access members of Sub , so what's the point in initializing Sub then?

Your example seems to be artificial and not well-designed.

Making subclass rewrite static fields of superclass does not sound like a good idea. In this case an outcome of Super's methods will depend on which class is touched first. This also makes hard to have multiple children of Super with their own behavior. To cut it short, static members are not for polymorphism - that's what OOP principles say.

I think it has to do with this part of the jvm spec:

Each frame (§2.6) contains a reference to the run-time constant pool (§2.5.5) for the type of the current method to support dynamic linking of the method code. The class file code for a method refers to methods to be invoked and variables to be accessed via symbolic references. Dynamic linking translates these symbolic method references into concrete method references, loading classes as necessary to resolve as-yet-undefined symbols, and translates variable accesses into appropriate offsets in storage structures associated with the run-time location of these variables.

This late binding of the methods and variables makes changes in other classes that a method uses less likely to break this code.

In chapter 5 in the jvm spec they also mention: A class or interface C may be initialized, among other things, as a result of:

The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.

...

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.

It seems to me the first bit of documentation states that any symbolic reference is simply resolved and invoked without regard as to where it came from. This documentation about method resolution has the following to say about that:

[M]ethod resolution attempts to locate the referenced method in C and its superclasses:

If C declares exactly one method with the name specified by the method reference, and the declaration is a signature polymorphic method (§2.9), then method lookup succeeds. All the class names mentioned in the descriptor are resolved (§5.4.3.1).

The resolved method is the signature polymorphic method declaration. It is not necessary for C to declare a method with the descriptor specified by the method reference.

Otherwise, if C declares a method with the name and descriptor specified by the method reference, method lookup succeeds.

Otherwise, if C has a superclass, step 2 of method resolution is recursively invoked on the direct superclass of C.

So the fact that it's called from a subclass seems to simply be ignored. Why do it this way? In the documentation you provided they say:

The intent is that a class or interface type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes.

In your example, you alter the state of Super when Sub is statically initialized. If initialization happened when you called Sub.staticMethod you would get different behavior for what the jvm considers the same method. This might be the inconsistency they were talking about avoiding.

Also, here's some of the decompiled class file code that executes staticMethod, showing use of invokestatic:

Constant pool:
    ...
    #2 = Methodref          #18.#19        // Sub.staticMethod:()V

... 

Code:
  stack=0, locals=1, args_size=1
     0: invokestatic  #2                  // Method Sub.staticMethod:()V
     3: return

for some reason jvm think that static block is no good, and its not executed

I believe, it is because you are not using any methods for subclass, so jvm sees no reason to "init" the class itself, the method call is statically bound to parent at compile time - there is late binding for static methods

http://ideone.com/pUyVj4

static {
    System.out.println("init");
    staticVar = new Object();
}

Add some other method, and call it before the sub

Sub.someOtherMethod();
new UsersClass().method();

or do explicit Class.forName("Sub");

Class.forName("Sub");
new UsersClass().method();

When static block is executed Static Initializers

A static initializer declared in a class is executed when the class is initialized

when you call Sub.staticMethod(); that means class in not initialized.Your are just refernce

When a class is initialized

When a Class is initialized in Java After class loading, initialization of class takes place which means initializing all static members of class. A Class is initialized in Java when :

1) an Instance of class is created using either new() keyword or using reflection using class.forName(), which may throw ClassNotFoundException in Java.

2) an static method of Class is invoked.

3) an static field of Class is assigned.

4) an static field of class is used which is not a constant variable.

5) if Class is a top level class and an assert statement lexically nested within class is executed.

When a class is loaded and initialized in JVM - Java

that's why your getting null(default value of instance variable).

    public class Sub extends Super {
    static {
        staticVar = new Object();
    }
    public static void staticMethod() {
        Super.staticMethod();
    }
}

in this case class is initialize and you get hashcode of new object() .If you do not override staticMethod() means your referring super class method and Sub class is not initialized.

According to this article , when you call static method or use static filed of a class, only that class will be initialized.

Here is the example screen shot. 在此输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM