8

When I attempt to compile the following the code, I get the compilation error:

unexpected type System.out.println( new Test().C.i );
                                    ^
required: class,package 
found: value

class Test {

    class C {
        static final int i = 0;    
    }

    public static void main(String... z) {
        System.out.println( new Test().C.i  );
    }

}

But, if I change new Test().C.i to new Test().new C().i it compiles just fine.

Why? If i is static in C, then I shouldn't have to instantiate C. I should just be able to call it through the class C, not a C object.

What am I missing?

August
  • 12,410
  • 3
  • 35
  • 51
  • You can't access inner Type (static or not) via variable. If you create `Test t = new Test();` then `t.C` is inappropriate call to type `C`. What you can only do is either use outer type like `Test.C.i` or use `new` to actually create instance of `C` which will let you access `i` (I am sure that there is some Java Language Specification which explains it, hope someone will find it and post it). – Pshemo Dec 28 '14 at 02:06
  • You can access static nested class through variable. Test.C.i works just fine if both C and i are static. –  Dec 28 '14 at 02:08
  • In `Test.C` `Test` is not variable, but type. In my example `t` is variable. – Pshemo Dec 28 '14 at 02:09
  • And the winner for today's question with the most downvoted answers goes to... – user253751 Dec 28 '14 at 02:17
  • Ah, you're right. Sorry. –  Dec 28 '14 at 02:23
  • Great question - was just as surprised as anybody else that this didn't work. Nice change from the standard "why does my trivial homework code not work" fare :) And yes is there a gold badge for "asking question with 5 deleted answers"? ;) – Voo Dec 28 '14 at 02:28

2 Answers2

5

The problem is that "." Identifier in the Java syntax expects the identifier to refer to a variable and not a type.

This is specified in 6.5.6.2. Qualified Expression Names of the JLS (among other places):

If Q is a type name that names a class type (§8 (Classes)), then:

If there is not exactly one accessible (§6.6) member of the class type that is a field named Id, then a compile-time error occurs.

Otherwise, if the single accessible member field is not a class variable (that is, it is not declared static), then a compile-time error occurs.

Otherwise, if the class variable is declared final, then Q.Id denotes the value of the class variable.

The type of the expression Q.Id is the declared type of the class variable after capture conversion (§5.1.10).

If Q.Id appears in a context that requires a variable and not a value, then a compile-time error occurs.

Otherwise, Q.Id denotes the class variable.

The type of the expression Q.Id is the declared type of the class variable after capture conversion (§5.1.10).

Note that this clause covers the use of enum constants (§8.9), since these always have a corresponding final class variable.

While I can definitely appreciate the logic of why you'd think that it'd work like that - I actually expected it to work as well - it's not a big deal: Since there is always exactly only one i you can refer to it by Test.C.i. If i is non-static new Test().new C().i would be the correct way to access it.

Another way to look at it is to read 15.8. Primary Expressions which has the actual syntax for primary expressions (which is what we deal with here): It allows ClassInstanceCreationExpression (which is why new Test().new C().i works) as well as FieldAccess (which works for Test.C.i because the "class" is resolved recursively - only the last identifier has to refer to a field then).

Voo
  • 29,040
  • 11
  • 82
  • 156
  • 1
    I believe you have the wrong JLS entry. You want the one where `Q` is an expression name, not a type name. – Sotirios Delimanolis Dec 28 '14 at 08:20
  • @Sotirios I'd reasonably need both and probably quite a few other places to cover all combinations. I think 15.8 is very much clearer on the whole - a question about the syntax of a language is in the end always best answered by just looking at the grammar. I might expand on the later point more and remove the first part though when I get to it. – Voo Dec 28 '14 at 15:07
  • I would rather stop focusing o syntactical issues (that most of modern IDEs will fix for yourself) and explain why it is **really** not working - see answer below – rgasiore Dec 29 '14 at 01:42
  • @rgasiore The OP knows what static/non static classes do and has repeatedly expressed her academic interest in *why* the snippet doesn't work (it would be reasonable after all to expect to be only able to refer to a non static inner class through an instance of the outer one) – Voo Dec 29 '14 at 07:49
-2

I think the reason why new Test().new C().i works is because class Test is a top-level class and is treated as static. If you were to change your inner class C to be static then new C().i would work.

However, you should NOT access static members in a non-static way.

To access your static field do:

System.out.println(C.i);

Edit:

For those saying that class Test is not static please refer to this stackoverflow answer.

All top-level classes are, by definition, static.

What the static boils down to is that an instance of the class can stand on its own. Or, the other way around: a non-static inner class (= instance inner class) cannot exist without an instance of the outer class. Since a top-level class does not have an outer class, it can't be anything but static.

Because all top-level classes are static, having the static keyword in a top-level class definition is pointless.


Just to show you how dumb of an idea it is to access a static field this way I created the following project:

class Test {

    class C {
        static final int i = 0;
    }

    public static void main(String[] args) {
        // BAD:
        System.out.println(new Test().new C().i);
        // Correct:
        System.out.println(C.i);
    }

}

If you compile the class and view it in jd-gui you can see how it was compiled:

class Test {

  public static void main(String[] args) {
    void tmp13_10 = new Test(); tmp13_10.getClass(); new C(); System.out.println(0);
    System.out.println(0);
  }

  class C {
    static final int i = 0;

    C() {
    }
  }
}
Community
  • 1
  • 1
Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
  • There is no need to change `C` to `static` to be able to access `i` via `Test.C.i`. Also this is not what OP is asking for. – Pshemo Dec 28 '14 at 02:13
  • 3
    That's not what I'm asking. I already know that. I'm asking for academic reasons. –  Dec 28 '14 at 02:14
  • @Pshemo My bad, you are correct. Was answering from my phone. – Jared Rummler Dec 28 '14 at 02:18
  • 1
    @frankie added an explanation as to why it works for `new Test().new C().i` – Jared Rummler Dec 28 '14 at 02:29
  • "*`new Test().new C().i` works is because class `Test` is static*" doesn't look good since `Test` is not static. "* If you were to change your inner class `C` to be static then `new C().i` would work*" true, but only inside static method of Test class because compiler would change it into `new Test.C().i` but question is why `new Test().C.i` doesn't work so it looks like your answer still doesn't answer the question. – Pshemo Dec 28 '14 at 16:28
  • @Pshemo "`Test` is not static". Yes it is. See my edit. – Jared Rummler Dec 28 '14 at 19:42
  • Top-level classes acts like static ones, but it doesn't mean that they are static. We can think of them as static ones, but they have no `static` modifier in them (even added implicit by compiler). You can test it easily with `System.out.println(Modifier.isStatic(Test.class.getModifiers()));` which prints `false`. IMO it is similar to final and effective-final: just because in Java8 we can use variables which do not change in anonymous classes doesn't mean that they are `final`, because they are not, they just act like ones. – Pshemo Dec 28 '14 at 19:51
  • From linked answer: "*What the static boils down to is that an instance of the class can stand on its own*" seems like oversimplification. I would say that `static` means more that static element doesn't need *instance* to be accessible, but it *requires* class (or maybe even better *type*, sine interfaces are not classes). – Pshemo Dec 28 '14 at 20:01
  • @Pshemo All top-level classes act the same as all classes with the `static` modifier so I don't really know what your point is. If you have a better answer, then add one. I think we can both agree that the way the OP accessed the static field is wrong. – Jared Rummler Dec 28 '14 at 20:08
  • This is only my opinion so we can agree or not and it's fine, but I want you to see what is my point: IMO `static` means that to access static "item" it is ***required** type, not variable* (in case of using variables to access static members compiler will still change them to type). In case of top-level classes there is no class from which we could assess it so it can't be static. So top-level class is top-level class, static class is static class, they are similar but we can't say that one is type another. Anyway this discussion has very little with OP so just lets agree to disagree. – Pshemo Dec 28 '14 at 20:35