Question

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 ...

Was it helpful?

Solution

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.

OTHER TIPS

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.

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

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.

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top