views:

332

answers:

5

The eclipse compiler refuses to compile the following code, stating that the field s is not visible. (IBM's Aspect J compiler also refuses, stating that "s could not be resolved") Why is that?

public class Test {

    String s;

    void foo(Object o) {
        String os = getClass().cast(o).s;
    }
}

The Java Language Specification states:

Otherwise, we say there is default access, which is permitted only when the access occurs from within the package in which the type is declared.

The way I understand it, the field is declared and accessed in the same compilation unit, thus within the same package, and should therefore be accessible.

Even more strangely, adding a downcast from ? extends Test to Test makes the field visible, i.e. the following code compiles:

public class Test {

    String s;

    void foo(Object o) {
        Test t = getClass().cast(o);
        String os = t.s;
    }
}

Have I stumbled across a compiler bug, or misunderstood the Java Spec?

Edit: I am on another computer now. Here, javac accepts the code, but eclipse still doesn't. Versions on this machine:

Eclipse Platform

Version: 3.4.2 Build id: M20090211-1700

JDK 1.6.0

Edit 2 Indeed, javac accepts the code. I had tested by running the ant build, which uses IBM's Ascpect J compiler ...

+2  A: 

I can't reproduce what you are saying. These both compile fine for me without warning, error or anything with javac directly.

WinXP, javac 1.6.0_16


No I tried with eclipse (v3.4.1, Build id: M20080911-1700) and for the first one it says:

The field Test.s is not visible

At least for Compiler Compliance level 1.6 and 1.5. The funny thing being, if you look at the Quick-fix options it lists a Change to 's' resolution. Which of course doesn't solve the problem. So the eclipse compiler and the Quick-fix "generator" seem to have different views on this too ;-)


For Compiler Compliance level 1.4 (as was to be expected) in eclipse for the first one I get

s cannot be resolved or is not a field

and for the second one I get

Type mismatch: cannot convert from Object to Test


If I specify -source 1.4 and target -1.4 in the command line directly javac says for the first one

cannot find symbol

and for the second one I get

incompatible types
jitter
+1. Compiles on 1.5 as well.
ChssPly76
I'm now on another computer, where javac accepts the code, but eclipse still doesn't. What does your eclipse say? I'll repeat my test on the original machine tomorrow to make sure I tested correctly.
meriton
+2  A: 

Well, let's see. I'd say the compiler can't properly guarantee that foo() will be called by some entity within the package, and therefore can't guarantee that s is visible. For example, add

protected void bar() {
    foo();
}

and then in some subclass Banana in another package

public void quux() { bar(); }

and oops! getClass() yields Banana, which cannot see s.

Edit: In a sense, other.package.Banana doesn't have a field s. If Banana were in the same package, it could still have its own s property, and would have to refer to Test's s via super.

Jonathan Feinberg
My understanding is: that Banana cannot see s means that code in the compilation unit banana can not access s. But my code is in the compilation unit Test ...
meriton
Yes, your code is in compilation unit Test, but a putative subclass might not be, and that class will be the result of getClass(), and that class cannot see the package-protected field. The compiler error is correct, I think.
Jonathan Feinberg
+3  A: 

Try this:

void foo(Object o) {
    Test foo = getClass().cast(o);
    String so = foo.s;
}

[Edit to clarify]:

getClass().cast(o) returns an object of type 'capture#1-of? extends Test' and not Test. So the issue is related to generics and how the compiler treats it. I don't know the details of the spec on generics but given that some compilers (per comments here) do accept your code, then this is either a loop hole in the spec or some of these compilers are not entirely according to spec.

[Last thoughts]: I believe the eclipse compiler is actually (carefully) correct here. The object o may in fact be an extension of Test (and defined in another package) and the compiler has no way of knowing if that is indeed the case or not. So it is treating it as the worst case of an instance of an extension defined in another package. It would have been super correct if adding a final qualifier to class Test would have allowed access to field s, but it does not.

Note the above was compiled on Mac OS X/Eclipse 3.4.
Erm ... I already did? Or what does renaming the local variable from my 2nd code snippet accomplish? I know that works. My question is why the first snippet doesn't.
meriton
It doesn't because you are assuming the generic capture is also defined in the same package by the compiler. If it is not, then package level visibility no longer applies.
(And sorry, I skimmed your post and missed the end.)
+1  A: 

Actually in almost all cases, except when required by Generics, it's better (and safer) to use Java cast operator. I discussed it here. Java cast operator does look over verbose, but it's the right tool here.

Replacing cast method with the operator compiles just fine in Eclipse.

public class Test {

    String s;

    void foo(Object o) {
        String os = ((Test) o).s;
    }
}

I think that alphazero is correct here, that Eclipse is just over cautious.

Alexander Pogrebnyak
A: 

Very weird. For an unknown reason (to me), the eclipse compiler requires an explicit cast:

void foo(Object o) {
    String os = ((Test)getClass().cast(o)).s;
}

While the code perfectly compiles without the cast with Sun's JDK (I'm running version 1.6.0_16 on GNU/Linux).

Pascal Thivent