java编译器奇怪:在同一个类中声明的字段,但“不可见”

时间:2009-10-21 23:07:59

标签: java generics compiler-construction compiler-errors

eclipse编译器拒绝编译以下代码,声明字段s不可见。 (IBM的Aspect J编译器也拒绝,说“无法解决”)为什么会这样?

public class Test {

    String s;

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

Java语言规范声明:

  

否则,我们说有默认值   访问权限,仅在以下时允许   访问发生在   声明类型的包。

我理解它的方式,该字段在同一个编译单元中声明和访问,因此在同一个包中,因此应该可以访问。

更奇怪的是,从? extends TestTest添加向下转换会使该字段可见,即以下代码编译:

public class Test {

    String s;

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

我是偶然发现了编译器错误,还是误解了Java规范?

修改 我现在在另一台电脑上。在这里,javac接受代码,但eclipse仍然没有。这台机器上的版本:

  

Eclipse平台

     

版本:3.4.2构建ID:   M20090211-1700

JDK 1.6.0

编辑2 实际上,javac接受了代码。我通过运行ant build来测试,它使用IBM的Ascpect J编译器......

5 个答案:

答案 0 :(得分:6)

试试这个:

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

[编辑澄清]:

getClass().cast(o)会返回“capture#1-of? extends Test”类型的对象,而不是Test。所以这个问题与泛型以及编译器如何处理它有关。我不知道泛型规范的细节,但考虑到一些编译器(这里的评论)确实接受了你的代码,那么这可能是规范中的一个循环漏洞,或者这些编译器中的一些并不完全符合规范。 / p>

[最后的想法]: 我相信eclipse编译器实际上(小心)正确。对象o实际上可能是Test的扩展(并在另一个包中定义),编译器无法知道是否确实如此。所以它将它视为另一个包中定义的扩展实例的最坏情况。如果向类final添加Test限定符可以允许访问字段s,那么这将是非常正确的,但事实并非如此。

答案 1 :(得分:4)

嗯,让我们看看。我说编译器无法正确保证包中的某个实体将调用foo(),因此无法保证s可见。例如,添加

protected void bar() {
    foo();
}

然后在另一个包

中的某个子类Banana
public void quux() { bar(); }

和哎呀! getClass()产生Banana,无法看到s

编辑:从某种意义上说,other.package.Banana 没有字段s。如果Banana 在同一个包中,它仍然可以拥有自己的s属性,并且必须通过{{1}引用Test的{​​{1}} }}

答案 2 :(得分:2)

我无法重现你所说的话。这些都可以在没有任何警告,错误或任何直接使用javac的情况下为我编译。

WinXP,javac 1.6.0_16


不,我尝试使用eclipse(v3.4.1,Build id:M20080911-1700),第一个使用eclipse:

The field Test.s is not visible

至少对于编译器合规性级别1.6和1.5。 有趣的是,如果您查看快速修复选项,它会列出Change to 's'分辨率。这当然不能解决问题。因此,eclipse编译器和Quick-fix“生成器”似乎对此也有不同的看法; - )


对于第一个我得到的eclipse中的编译器合规性级别1.4(正如预期的那样)

s cannot be resolved or is not a field

我得到的第二个

Type mismatch: cannot convert from Object to Test

如果我直接在命令行中指定-source 1.4target -1.4 javac说明第一个

cannot find symbol

我得到的第二个

incompatible types

答案 3 :(得分:1)

实际上几乎在所有情况下,除非Generics要求,否则使用Java强制转换操作符会更好(也更安全)。我讨论了它here。 Java强制转换操作符看起来很冗长,但它是正确的工具。

使用运算符替换cast方法在Eclipse中编译得很好。

public class Test {

    String s;

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

我认为alphazero是正确的here,Eclipse过于谨慎。

答案 4 :(得分:0)

非常奇怪。由于未知原因(对我而言),eclipse编译器需要显式转换:

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

虽然没有使用Sun的JDK(我在GNU / Linux上运行的是1.6.0_16版本),代码完美编译。