java compiler oddity: field declared in same class, yet "not visible"
The eclipse compiler refuses to compile the following code, stating that the field s is not visible. (IBM's Aspect J compiler also refuses, 开发者_如何学Pythonstating 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 ...
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.
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).
精彩评论