74

Take a look at below code:

class Foo{
    public static int x = 1;
}

class Bar{    
    public static void main(String[] args) {
        Foo foo;
        System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
    }
}

As you see while trying to access static field x via an uninitialized local variable Foo foo; code foo.x generates compilation error: Variable 'foo' might not have been initialized.

It could seem like this error makes sense, but only until we realize that to access a static member the JVM doesn't actually use the value of a variable, but only its type.

For instance I can initialize foo with value null and this will let us access x without any problems:

Foo foo = null;
System.out.println(foo.x); //compiles and at runtime prints 1!!! 

Such scenario works because compiler realizes that x is static and treats foo.x as if it was written like Foo.x (at least that is what I thought until now).

So why compiler suddenly insists on foo having a value which it will NOT use at all?


Disclaimer: This is not code which would be used in real application, but interesting phenomenon which I couldn't find answer to on Stack Overflow, so I decided to ask about it.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • 7
    I'd say a limitation in the compiler that isn't really worth fixing, given that the code raises a warning anyway. – M A Sep 16 '19 at 20:33
  • 1
    @manouti That is also my guess, but I am still interested in why compiler behaves that way. Which part of specification forces it? – Pshemo Sep 16 '19 at 20:35
  • I think the compiler is less concerned with initialization (even though that is the warning) as it is with the null pointer exception you are creating. The reference to the initialization is the compiler trying to give reason to why you may have a potential null pointer exception. – portfoliobuilder Sep 16 '19 at 20:37
  • 9
    @portfoliobuilder There is no risk of NPE here since as mentioned in question, in case of accessing `static` member compiler doesn't use *value* of variable but its *type*. We can even write `((Foo)null).x` and this *will compile and work* because compiler will recognize that `x` is static (unless I misunderstood your comment). – Pshemo Sep 16 '19 at 20:39
  • I think the compiler is complaining for the right reason, as static keyword indicates that the particular member belongs to a "type" itself, rather than to an instance of that type. The proper way or Java way for accessing a static variable is Foo.a, not foo.a. Copied the static definition from https://www.baeldung.com/java-static – N0000B Sep 16 '19 at 20:45
  • @Pshemo Depending on what you are doing with staticX and what the value of staticX is there absolutely is risk. You are showing a reference to static methods and fields via class instance rather than a class itself. The correct implementation is Foo.x, not foo.x. – portfoliobuilder Sep 16 '19 at 20:47
  • 29
    Accessing a static variable from a non-static context (such as `foo.x`) should have been a compiler error when Java was first created. Sadly, that ship sailed over 25 years ago and would be a breaking change if they changed it now. – Powerlord Sep 16 '19 at 20:49
  • 2
    @portfoliobuilder "..there absolutely is risk" what risk do you have in mind? Also one nitpick: both ways are technically *correct* (unfortunately), but `Foo.x` is *preferred* (which is why we usually get compilation *warning* when we attempt to use variant `foo.x`). – Pshemo Sep 16 '19 at 21:12
  • Sort of related: Kotlin will say a static member is unresolved if you try to access it through an instance variable. – TheWanderer Sep 18 '19 at 12:05

4 Answers4

73

§15.11. Field Access Expressions:

If the field is static:

The Primary expression is evaluated, and the result is discarded. If evaluation of the Primary expression completes abruptly, the field access expression completes abruptly for the same reason.

Where earlier it states that field access is identified by Primary.Identifier.

This shows that even though it seems to not use the Primary, it is still evaluated and the result is then discarded which is why it will need to be initialized. This can make a difference when the evaluation halts the access as stated in the quote.

EDIT:

Here is a short example just to demonstrate visually that the Primary is evaluated even though the result is discarded:

class Foo {
    public static int x = 1;
    
    public static Foo dummyFoo() throws InterruptedException {
        Thread.sleep(5000);
        return null;
    }
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(dummyFoo().x);
        System.out.println(Foo.x);
    }
}

Here you can see that dummyFoo() is still evaluated because the print is delayed by the 5 second Thread.sleep() even though it always returns a null value which is discarded.

If the expression was not evaluated the print would appear instantly, which can be seen when the class Foo is used directly to access x with Foo.x.

Note: Method invocation is also considered a Primary shown in §15.8 Primary Expressions.

Community
  • 1
  • 1
Nexevis
  • 4,647
  • 3
  • 13
  • 22
  • 4
    Interestingly, `javac` does this literally, generating a load and pop instruction whereas `ecj` enforces the formal rule, i.e. does not allow access through an unintialized variable, but does not generate code for the side-effect free operation. – Holger Sep 17 '19 at 13:32
21

Chapter 16. Definite Assignment

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

It doesn't really matter what you try to access via a local variable. The rule is that it should be definitely assigned before that.

To evaluate a field access expression foo.x, the primary part of it (foo) must be evaluated first. It means that access to foo will occur, which will result in a compile-time error.

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
14

There is value in keeping the rules as simple as possible, and “don’t use a variable that might not have been initialised” is as simple as it gets.

More to the point, there is an established way of calling static methods - always use the class name, not a variable.

System.out.println(Foo.x);

The variable “foo” is unwanted overhead that should be removed, and the compiler errors and warnings could be seen as helping leading towards that.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
racraman
  • 4,988
  • 1
  • 16
  • 16
3

Other answers perfectly explain the mechanism behind what is happening. Maybe you also wanted the rationale behind Java's specification. Not being a Java expert, I cannot give you the original reasons, but let me point this out:

  • Every piece of code either has a meaning or it triggers a compilation error.
  • (For statics, because an instance is unnecessary, Foo.x is natural.)
  • Now, what shall we do with foo.x (access through instance variable)?
    • It could be a compilation error, as in C#, or
    • It has a meaning. Because Foo.x already means "simply access x", it is reasonable that the expression foo.x has a different meaning; that is, every part of the expression is valid and access x.

Let's hope someone knowledgeable can tell the real reason. :-)

Pablo H
  • 609
  • 4
  • 22