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

问题描述 投票:0回答:5

eclipse编译器拒绝编译以下代码,提示字段s不可见。 (IBM的Aspect J编译器也拒绝,指出“s无法解析”)这是为什么?

public class Test {

    String s;

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

Java 语言规范指出:

否则我们说有默认 访问,仅在以下情况下才允许 访问发生在 声明类型的包。

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

更奇怪的是,添加从

? extends Test
Test
的向下转换会使该字段可见,即可以编译以下代码:

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 版本号: M20090211-1700

JDK 1.6.0

编辑2 事实上,javac 接受该代码。我通过运行 ant 构建进行了测试,它使用 IBM 的 Ascpect J 编译器......

java generics compiler-errors
5个回答
6
投票

试试这个:

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

[编辑以澄清]:

getClass().cast(o)
返回类型为 '
capture#1-of? extends Test
' 的对象,而不是
Test
。 所以这个问题与泛型以及编译器如何处理它有关。 我不知道泛型规范的详细信息,但鉴于某些编译器(根据此处的评论)确实接受您的代码,那么这要么是规范中的漏洞,要么其中一些编译器不完全符合规范。

[最后的想法]: 我相信 eclipse 编译器在这里实际上(仔细地)是正确的。 对象

o
实际上可能是 Test 的扩展(并在另一个包中定义),并且编译器无法知道情况是否确实如此。 因此,它将其视为另一个包中定义的扩展实例的最坏情况。 如果向类
final
添加
Test
限定符将允许访问字段
s
,那将是非常正确的,但事实并非如此。


4
投票

好吧,让我们看看。我想说编译器无法正确保证

foo()
将被包内的某些实体调用,因此无法保证
s
可见。例如,添加

protected void bar() {
    foo();
}

然后在某个子类中

Banana
在另一个包中

public void quux() { bar(); }

哎呀!

getClass()
产生
Banana
,它看不到
s

编辑:从某种意义上说,other.package.Banana 没有 have 字段

s
。如果 Banana 在同一个包中,它仍然可以拥有自己的
s
属性,并且必须通过
Test
引用
s
super


2
投票

我无法重现你所说的内容。这些都对我来说编译得很好,没有警告、错误或直接使用 javac 的任何东西。

WinXP、javac 1.6.0_16


不,我尝试使用 eclipse(v3.4.1,构建 ID:M20080911-1700),第一个它说:

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.4
target -1.4
javac
表示第一个

cannot find symbol

对于第二个我得到的

incompatible types

1
投票

实际上在几乎所有情况下,除了泛型需要时,最好(也更安全)使用 Java 强制转换运算符。 我在here讨论过它。 Java 强制转换运算符看起来确实很冗长,但它是正确的工具。

用运算符替换

cast
方法在 Eclipse 中编译得很好。

public class Test {

    String s;

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

我认为alphazero是正确的这里,Eclipse 过于谨慎了。


0
投票

很奇怪。由于未知原因(对我来说),Eclipse 编译器需要显式强制转换:

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

虽然代码可以完美编译,无需使用 Sun 的 JDK 进行强制转换(我在 GNU/Linux 上运行版本 1.6.0_16)。

© www.soinside.com 2019 - 2024. All rights reserved.