18

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.
Community
  • 1
  • 1
Gerold Broser
  • 14,080
  • 5
  • 48
  • 107
  • 1
    Have you checked the bytecode? I'm guessing that the exact same bytecode would be generated whether `method` calls `Sub.staticMethod()` or `Super.staticMethod()` [if it were public], which would make it hard to tell at runtime which class's static initializer is to be called. I'm not good at looking at bytecode myself, though. – ajb Sep 19 '14 at 20:26
  • @ajb : Neither am I, but it must be what occurs, otherwise this would not compile. – Dici Sep 19 '14 at 20:27
  • @ajb The bytecode reads `0 invokestatic igb.Sub.staticMethod() : void [22]` or `0 invokestatic igb.Super.staticMethod() : void [22]`. Depending on which class it is invoked. – Gerold Broser Sep 19 '14 at 20:41

7 Answers7

10

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.

Dici
  • 25,226
  • 7
  • 41
  • 82
  • Yes, that's what I am used to as well. But from a class user's point of view `Sub.staticMethod()` looks like `staticMethod()` being a method of `Sub`. And since a user isn't supposed to know implementation details the occurrence of `null` isn't very comprehensible, is it? – Gerold Broser Sep 19 '14 at 20:34
  • @GeroldBroser : From the user point of view, this code would be hidden and only the documentation would remain. With this, a user would be responsible of using the static method on the wron class. It is true that it can be confusing, that's why such a code should not compile at least with a warning in my opinion. – Dici Sep 19 '14 at 20:44
  • I'm actually curious as to whether there is a reason that the Java compiler allows `Sub.staticMethod()` to compile at all. I can see the reason for allowing methods *within* `Sub` to invoke static methods in `Super` with no class qualification, but, given that only the static initializer of `Super` is invoked, can you think of any reason for an external class (like `UsersClass`) to invoke `staticMethod()` in any way other than `Super.staticMethod()`? I can't, off the top of my head. – sumitsu Sep 19 '14 at 20:45
  • 2
    @Dici In fact, there's a warning in Eclipse: "The static method staticMethod() from the type Super should be accessed directly" and it can be deactivated under the "Indirect access to static member" preference there. – Gerold Broser Sep 19 '14 at 20:51
  • @GeroldBroser : Indeed, in my Eclipse it was set to `Ignore` and I changed it to `Error`. – Dici Sep 19 '14 at 20:56
  • 2
    The reason this works is that it's relatively common (although IMO probably not a good idea) for an abstract class to define static methods that its subclasses can use without importing them. For example, in JUnit 3.x all test classes need to inherit from `TestCase`, and `TestCase` extends `junit.framework.Assert` for the sole purpose of pulling all the `assertEquals` etc. methods into scope for subclasses. Now that we have `import static`, there's really no good reason to do this. – Daniel Pryden Sep 19 '14 at 20:59
  • @DanielPryden There's a real implementation behind this that lead me to this issue. (I'm usually not sitting around dreaming up rarely used use-cases ;-) There `Super` is called `ResourceBundleItems`, `Sub` is called `Messages`, `staticMethod()` is called `get(args)`. Now, `Messages.get(args)` is more descriptive than `ResourceBundleItems.get(args)`. And again, the user of `Messages` shouldn't be supposed to know anything about `Messages`' implementation details. – Gerold Broser Sep 19 '14 at 23:06
  • 1
    @Dici re your "shouldn't compile without a warning, just like when one calls a static method on an instance of the class": Compiling with `javac *.java` shows no warning at all. Compiling with `javac -Xlint *.java` shows just a warning for "static method should be qualified by type name, [...], instead of by an expression" if there is such. With `-Werror` you can make this an error. Indirect static access isn't criticized at all. – Gerold Broser Sep 19 '14 at 23:42
  • @GeroldBroser : Indeed ! Well I'm not an expert so I won't be too affirmative about but if it was me, it would be deprecated. – Dici Sep 20 '14 at 00:45
  • @GeroldBroser: Makes sense. In this particular case, since the issue is really the API you're exposing, you could just define a method `get()` on `Messages` that could call `ResourceBundleItems.get()`. That's probably a good idea anyway, because it also allows you to customize the Javadoc. – Daniel Pryden Sep 20 '14 at 02:09
  • @DanielPryden re "you could just define a method get() on Messages that could call ResourceBundleItems.get()": That's exactly what the commented part in `Sub` is meant to be. – Gerold Broser Sep 20 '14 at 08:29
  • See [this](http://stackoverflow.com/a/10292034/1744774) and [this](http://stackoverflow.com/a/29725529/1744774) answer to [Are static methods inherited in Java?](http://stackoverflow.com/q/10291949/1744774) regarding your "_static fields and methods are **NOT** inherited_". – Gerold Broser Apr 06 '16 at 00:26
  • @GeroldBroser Yeah that's true, the answer is inaccurate. Thanks for pointing this out. – Dici Apr 06 '16 at 10:15
3

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.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • Yes, but my question is not about static *fields* but about the background why referencing a superclass' static *method* via a subclass doesn't initialize the subclass. But, it seems likely that your answer "avoid having the JVM load classes unnecessarily" applies to methods as well. Though, in the case described in my original question I wouldn't call it "unnecessary". – Gerold Broser Sep 19 '14 at 22:33
3

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
Community
  • 1
  • 1
heisbrandon
  • 1,180
  • 7
  • 8
2

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.

apangin
  • 92,924
  • 10
  • 193
  • 247
1

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.enter image description here

Junbang Huang
  • 1,927
  • 19
  • 26
0

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();
Kalpesh Soni
  • 6,879
  • 2
  • 56
  • 59
  • Yes, i'm aware of this. That's what my mentioning of JLS 12.4.1 is meant to express. – Gerold Broser Sep 19 '14 at 21:01
  • because if T is subclass of S, and you dont need anything from T, jvm doesnt see the need to load it, you are just using it as a pointer to point to parent – Kalpesh Soni Sep 19 '14 at 21:09
  • Blasphemy! that vid is hilarious – Kalpesh Soni Sep 19 '14 at 21:28
  • "the method call is statically bound to parent at compile time" seems not to be true (in case of using `Sub`) if we look at the bytecode I posted in my first comment to my orginal question. – Gerold Broser Sep 20 '14 at 00:09
  • have you compared it to oo paradigm? create interface, assign object and call a method. In that case which implementation to call is decided by jvm at runtime - where as for static methods, compiler knows what to call at compile time – Kalpesh Soni Sep 20 '14 at 02:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/61591/discussion-between-kalpesh-soni-and-gerold-broser). – Kalpesh Soni Sep 20 '14 at 02:44
0

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.

Khan Abdulrehman
  • 816
  • 2
  • 10
  • 22