Чому ми не можемо отримати доступ до статичного вмісту через неініціалізовану локальну змінну?


77

Погляньте на код нижче:

class Foo{
    public static int x = 1;
}

class Bar{    
    public static void main(String[] args) {
        Foo foo;
        System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
    }
}

Як ви бачите , при спробі поля статичного доступу xчерез неініціалізованих локальну змінну Foo foo;коду foo.xгенерує помилку компіляції: Variable 'foo' might not have been initialized.

Це може здатися , як ця помилка має сенс, але тільки поки ми не усвідомлюємо , що для доступу до staticчлену віртуальна машина практично не використовувати в значення змінної, а тільки його тип .

Наприклад, я можу ініціалізувати fooзначенням, nullі це дозволить нам отримати доступ xбез будь-яких проблем:

Foo foo = null;
System.out.println(foo.x); //compiles and at runtime prints 1!!! 

Такий сценарій працює, тому що компілятор розуміє, що xце статично, і поводиться так, foo.xяк було написано Foo.x(принаймні, так я думав дотепер).

То чому компілятор раптом наполягає на fooзначенні, яке він взагалі НЕ використовуватиме ?


Застереження: Це не код, який буде використовуватися в реальному застосуванні, а цікаве явище, на яке я не зміг знайти відповіді у Stack Overflow, тому вирішив запитати про це.


7
Я б сказав обмеження в компіляторі, яке насправді не варто виправляти, враховуючи те, що код все одно викликає попередження.
M Anouti,

1
@manouti Це теж моє здогадування, але мене все одно цікавить, чому компілятор поводиться так. Яка частина специфікації це змушує?
Пшемо,

9
@portfoliobuilder Там немає ніякого ризику NPE тут з тих пір , як зазначено в питанні, в разі доступу до staticкомпілятору Членові значення змінної , але її тип . Ми навіть можемо писати, ((Foo)null).xі це буде компілювати та працювати, тому що компілятор розпізнає, що xце статично (якщо я не зрозумів ваш коментар).
Пшемо,

29
Доступ до статичної змінної з нестатичного контексту (наприклад foo.x) мав би бути помилкою компілятора, коли Java була створена вперше. На жаль, це судно відплило більше 25 років тому і було б надзвичайною зміною, якби вони змінили його зараз.
Powerlord

2
@portfoliobuilder "..існує абсолютно ризик", який ризик ти маєш на увазі? Також один нюанс: обидва способи є технічно правильними (на жаль), але Foo.xє кращим (саме тому ми зазвичай отримуємо попередження про компіляцію, коли намагаємось використовувати варіант foo.x).
Пшемо,

Відповіді:


74

§15.11. Полеві вирази доступу :

Якщо поле статичне :

Первинний вираз обчислюється, а результат відкидається . Якщо обчислення виразу Первинного завершується різко, вираз доступу до поля різко завершується з тієї ж причини.

Там, де раніше зазначалося, що доступ до полів ідентифікується Primary.Identifier.

Це показує, що, хоча воно і не використовує Primary, воно все одно обчислюється, а результат потім відкидається, саме тому його потрібно буде ініціалізувати. Це може змінити ситуацію, коли оцінка зупиняє доступ, як зазначено в пропозиції.

РЕДАГУВАТИ:

Ось короткий приклад, щоб наочно продемонструвати, що Primaryоцінюється, навіть якщо результат відкидається:

class Foo {
    public static int x = 1;
    
    public static Foo dummyFoo() throws InterruptedException {
        Thread.sleep(5000);
        return null;
    }
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(dummyFoo().x);
        System.out.println(Foo.x);
    }
}

Тут ви можете побачити, що dummyFoo()все ще обчислюється, оскільки значення printзатримується на 5 секунд, Thread.sleep()хоча воно завжди повертає nullзначення, яке відкидається.

Якщо вираз не обчислюється, printвін з'являється миттєво, що можна побачити, коли клас Fooвикористовується безпосередньо для доступу xдо Foo.x.

Примітка: Виклик методу також вважається Primaryпоказаним у §15.8 Первинні вирази .


4
Цікаво, javacщо це робиться буквально, генеруючи інструкцію навантаження та поп, тоді як ecjвиконує формальне правило, тобто не дозволяє доступ через неінтіалізовану змінну, але не генерує код для вільної роботи з побічними ефектами.
Holger

21

Розділ 16. Певне призначення

Кожна локальна змінна (§14.4) і кожне порожнє остаточне поле (§4.12.4, §8.3.1.2) повинні мати певно присвоєне значення, коли відбувається будь-який доступ до його значення.

Не має значення, до чого ви намагаєтесь отримати доступ через локальну змінну. Правило полягає в тому, що його слід визначити до цього.

Щоб обчислити вираз доступу до поля foo.x , спочатку потрібно обчислити його primaryчастину ( foo). Це означає, що fooвідбудеться доступ до , що призведе до помилки під час компіляції.

Для кожного доступу до локальної змінної або порожнього кінцевого поля x, x обов'язково повинен бути призначений перед доступом, або виникає помилка під час компіляції.


14

Цінність дотримання правил є якомога простішою, і «не використовувати змінну, яка, можливо, не ініціалізована», настільки просто, наскільки це можливо.

Більш того, існує усталений спосіб виклику статичних методів - завжди використовуйте ім'я класу, а не змінну.

System.out.println(Foo.x);

Змінна "foo" - це небажані накладні витрати, які слід видалити, а помилки та попередження компілятора можуть сприйматися як допомога, що веде до цього.


3

Інші відповіді чудово пояснюють механізм того, що відбувається. Можливо, ви також хотіли обґрунтувати специфікацію Java. Не будучи експертом Java, я не можу вказати вам початкові причини, але дозвольте вказати на це:

  • Кожен фрагмент коду або має значення, або він викликає помилку компіляції.
  • (Для статики, оскільки екземпляр непотрібний, Foo.xце природно.)
  • Тепер, що нам робити foo.x(доступ через змінну екземпляра)?
    • Це може бути помилка компіляції, як у C #, або
    • Це має значення. Оскільки Foo.xвже означає "просто отримати доступ x", розумно, що вираз foo.x має інше значення; тобто кожна частина виразу є дійсною і має доступ x.

Будемо сподіватися, що хтось обізнаний може сказати справжню причину. :-)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.