странность компилятора Java: поле объявлено в том же классе, но "не видно" - PullRequest
7 голосов
/ 22 октября 2009

Компилятор eclipse отказывается компилировать следующий код, заявляя, что поле s не видно. (Компилятор IBM Aspect J также отказывается, заявляя, что «не может быть разрешен») Почему это так?

public class Test {

    String s;

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

Спецификация языка Java гласит:

В противном случае мы говорим, что по умолчанию доступ, который разрешен только тогда, когда доступ происходит изнутри пакет, в котором объявлен тип.

Как я понимаю, поле объявляется и доступно в одном и том же модуле компиляции, то есть в одном пакете, и поэтому должно быть доступно.

Еще более странно, добавление downcast с ? extends Test до Test делает поле видимым, то есть компилируется следующий код:

public class Test {

    String s;

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

Я наткнулся на ошибку компилятора или неправильно понял спецификацию Java?

Edit: Я сейчас на другом компьютере. Здесь javac принимает код, но затмение все еще не делает. Версии на этой машине:

Eclipse Platform

Версия: 3.4.2 ID сборки: M20090211-1700

JDK 1.6.0

Редактировать 2 Действительно, Javac принимает код. Я проверил, запустив сборку ant, в которой используется компилятор IBM Ascpect J ...

Ответы [ 5 ]

6 голосов
/ 22 октября 2009

Попробуйте это:

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 голосов
/ 22 октября 2009

Ну, посмотрим. Я бы сказал, что компилятор не может должным образом гарантировать, что foo() будет вызываться какой-либо сущностью в пакете, и поэтому не может гарантировать, что s будет видимым. Например, добавьте

protected void bar() {
    foo();
}

и затем в каком-то подклассе Banana в другом пакете

public void quux() { bar(); }

и упс! getClass() дает Banana, который не видит s.

Редактировать: В некотором смысле, другие. Пакет. Банана не имеет поле s. Если бы Banana был в том же пакете, он все равно мог бы иметь свое собственное свойство s, и ему пришлось бы ссылаться на Test s через super.

2 голосов
/ 22 октября 2009

Я не могу воспроизвести то, что вы говорите. Они оба прекрасно скомпилированы для меня без предупреждения, ошибки или чего-то другого с использованием javac напрямую.

WinXP, javac 1.6.0_16


Нет, я пытался с помощью eclipse (v3.4.1, Build id: M20080911-1700), и для первого он говорит:

The field Test.s is not visible

Как минимум для Compiler Compliance уровня 1.6 и 1.5. Самое смешное, что если вы посмотрите на параметры быстрого исправления, в нем будет указано разрешение Change to 's'. Что, конечно, не решает проблему. Так что у компилятора eclipse и «генератора» Quick-fix тоже есть разные взгляды на это; -)


Для уровня соответствия Compiler 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 голос
/ 22 октября 2009

Фактически почти во всех случаях, кроме случаев, когда это требуется для Generics, лучше (и безопаснее) использовать оператор приведения Java. Я обсуждал это здесь . Оператор приведения Java действительно просматривает подробности, но здесь это правильный инструмент.

Замена метода cast на оператор прекрасно компилируется в Eclipse.

public class Test {

    String s;

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

Я думаю, что alphazero является правильным здесь , что Eclipse слишком осторожен.

0 голосов
/ 22 октября 2009

Очень странно. По неизвестной (мне) причине компилятор eclipse требует явного приведения:

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

Хотя код прекрасно компилируется без приведения с использованием Sun JDK (я использую версию 1.6.0_16 для GNU / Linux).

...