Чому цей код Java компілюється?


96

У області методу або класу компілюється рядок нижче (з попередженням):

int x = x = 1;

У межах класу, де змінні отримують свої значення за замовчуванням , наступне дає помилку "невизначене посилання":

int x = x + 1;

Хіба це не перше, що x = x = 1має закінчитися такою ж "невизначеною посиланням"? А може, другий рядок int x = x + 1повинен скласти? Або щось мені не вистачає?


1
Якщо додати ключове слово staticдо змінної class-range, як і в static int x = x + 1;, ви отримаєте ту ж помилку? Тому що в C # це має значення, якщо воно статичне або нестатичне.
Jeppe Stig Nielsen

static int x = x + 1не вдається на Java.
Марцін

1
в c # int a = this.a + 1;і int b = 1; int a = b + 1;в області класу (і в Java це нормально) не вдається, можливо, через §17.4.5.2 - "Ініціатор змінної для поля екземпляра не може посилатися на створений екземпляр." Я не знаю, чи це явно десь дозволено, але статика не має такого обмеження. У Java правила відрізняються, і вони static int x = x + 1не виконуються з тієї самої причини, що int x = x + 1це робить
msam,

Цей аналог з байт-кодом знімає будь-які сумніви.
rgripper

Відповіді:


101

тл; д-р

Для полів , int b = b + 1є незаконним , оскільки bнелегальної вперед посилання b. Ви насправді можете це виправити, написавши int b = this.b + 1, що складається без скарг.

Для локальних змінних , int d = d + 1є незаконним , оскільки dНЕ инициализируется перед використанням. Це не стосується полів, які завжди ініціалізуються за замовчуванням.

Різницю можна побачити, намагаючись компілювати

int x = (x = 1) + x;

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

Вступ

По-перше, правила для ініціалізаторів полів та локальних змінних дуже різні. Тож ця відповідь стосуватиметься правил у двох частинах.

Ми будемо використовувати цю програму тестування протягом усього:

public class test {
    int a = a = 1;
    int b = b + 1;
    public static void Main(String[] args) {
        int c = c = 1;
        int d = d + 1;
    }
}

Визначення bнедійсним і не вдається з illegal forward referenceпомилкою.
Визначення dнедійсним і не вдається з variable d might not have been initializedпомилкою.

Той факт, що ці помилки різні, повинен натякати на те, що причини помилок також різні.

Поля

Польові ініціалізатори на Java керуються JLS § 8.3.2 , Ініціалізація полів.

Сфера поля визначається в JLS §6.3 , Область декларації.

Відповідні правила:

  • Обсяг декларації члена, mоголошеного в або успадкованого класом типу C (§ 8.1.6), є цілим текстом С, включаючи будь-які вкладені декларації типу.
  • Вирази ініціалізації для змінних екземпляра можуть використовувати просте ім'я будь-якої статичної змінної, оголошеної або успадкованої класом, навіть такої, оголошення якої відбувається в текстовому порядку пізніше.
  • Використання змінних екземпляра, декларації яких з’являються після тексту, іноді обмежуються, навіть якщо ці змінні екземпляра знаходяться в області дії. Точні правила, що регулюють пряме посилання на змінні екземплярів, див. У § 8.3.2.3.

Пункт 8.3.2.3 говорить:

Декларація члена повинна з'являтися текстово, перш ніж вона буде використана, лише якщо член є екземплярним (відповідно статичним) полем класу або інтерфейсу C і виконуються всі наступні умови:

  • Використання відбувається в екземплярі (відповідно статичному) змінному ініціалізаторі C або в екземплярі (відповідно статичному) ініціалізаторі C.
  • Використання не з лівого боку завдання.
  • Використання відбувається за допомогою простої назви.
  • C - це найглибший клас або інтерфейс, що додає до використання.

Насправді ви можете посилатися на поля до того, як вони були оголошені, за винятком певних випадків. Ці обмеження призначені для запобігання коду

int j = i;
int i = j;

від складання. Спеціалізація Java говорить, що "обмеження, наведені вище, призначені для того, щоб під час компіляції зафіксувати кругові чи неправильні ініціалізації.

До чого насправді зводяться ці правила?

Коротше кажучи, правила в основному кажуть, що ви повинні оголосити поле до посилання на це поле, якщо (a) посилання знаходиться в ініціалізаторі, (b) посилання не призначено, (c) посилання є просте ім'я (немає таких класифікаторів this.) та (г) до нього не можна отримати доступ з внутрішнього класу. Отже, пряма посилання, яка задовольняє всі чотири умови, є незаконною, але пряма посилання, яка не відповідає принаймні одній умові, є ОК.

int a = a = 1;компілюється , тому що він порушує (б): посилання a буде бути призначена, так що це законно , щоб звернутися до aзаздалегідь a«s повної декларації.

int b = this.b + 1також компілюється, оскільки порушує (c): посилання this.b- це не проста назва (воно кваліфікується this.). Ця непарна конструкція все ще є чітко визначеною, оскільки this.bмає значення нуль.

Отже, в основному обмеження щодо посилань на поле всередині ініціалізаторів не дозволяють int a = a + 1успішно складатись.

Зауважте, що декларацію поля int b = (b = 1) + bне вдасться скомпілювати, оскільки остаточне bвсе ще є незаконним прямим посиланням.

Локальні змінні

Оголошення локальних змінних регулюються JLS §14.4 , Заяви про декларацію місцевих змінних.

Область дії локальної змінної визначена в JLS §6.3 , Сфера декларації:

  • Область застосування локальної змінної в блоці (§14.4) - це решта блоку, в якому з’являється декларація, починаючи з власного ініціалізатора і включаючи будь-які подальші декларатори праворуч у операторі декларації локальної змінної.

Зверніть увагу, що ініціалізатори знаходяться в межах декларованої змінної. То чому не int d = d + 1;компілює?

Причина пов’язана з правилом Java про певне призначення ( JLS §16 ). Визначене призначення в основному говорить про те, що кожен доступ до локальної змінної повинен мати попереднє призначення до цієї змінної, а компілятор Java перевіряє цикли та гілки, щоб переконатися, що призначення завжди відбувається перед будь-яким використанням (саме тому певне призначення має цілий розділ специфікації, присвячений до нього). Основне правило:

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

Вхід int d = d + 1;доступ до dлокальної змінної вирішений штрафом, але оскільки до dнього не було призначено d, компілятор видає помилку. У int c = c = 1, c = 1буває, спочатку, що призначає c, а потім cініціалізується на результат цього призначення (що дорівнює 1).

Зауважте, що через певні правила присвоєння, локальне оголошення змінної int d = (d = 1) + d; буде складено успішно ( на відміну від декларації поля int b = (b = 1) + b), оскільки dвоно, безумовно, призначається часом досягнення остаточного dзначення.


+1 для посилань, однак, я думаю, ви помилилися з таким формулюванням: "int a = a = 1; компілюється, тому що він порушує (b)", якщо він порушив будь-яку з 4 вимог, які він не складе. Однак це не так , що IS на лівій стороні поступку (подвійний негативний в редакції JLS не надто допомагає тут). In int b = b + 1b знаходиться праворуч (а не ліворуч) від завдання, щоб це порушило ...
msam

... У чому я не надто впевнений, це наступне: ці 4 умови повинні бути виконані, якщо декларація не відображається в текстовому форматі перед призначенням, у цьому випадку я думаю, що декларація відображається "текстовою" перед призначенням int x = x = 1, в якій У випадку нічого цього не застосовується.
msam

@msam: Це трохи заплутано, але в основному вам потрібно порушити одну з чотирьох умов, щоб зробити пряме посилання. Якщо ваше пряме посилання задовольняє всім чотирьом умовам, це незаконно.
nneonneo

@msam: Також повна заява набирає чинності лише після ініціалізатора.
nneonneo

@mrfishie: Велика відповідь, але в специфікації Java є дивовижна глибина. Питання не таке просте, як здається на перший погляд. (Я колись давно писав компілятор підмножини Java, тому я добре знайомий з багатьма тонкощами JLS).
nneonneo

86
int x = x = 1;

еквівалентно

int x = 1;
x = x; //warning here

перебуваючи в

int x = x + 1; 

спочатку нам потрібно обчислити, x+1але значення x невідоме, тому ви отримуєте помилку (компілятор знає, що значення x невідомо)


4
Цей плюс натяк на правильну асоціативність від OpenSauce я вважав дуже корисним.
TobiMcNamobi

1
Я думав, що повернене значення завдання - це значення, яке присвоюється, а не значення змінної.
zzzzBov

2
@zzzzBov правильний. int x = x = 1;еквівалентно int x = (x = 1), ні x = 1; x = x; . Ви не повинні отримувати попередження компілятора для цього.
nneonneo

int x = x = 1;s еквівалент int x = (x = 1)через правильну асоціативність =оператора
Grijesh Chauhan

1
@nneonneo і int x = (x = 1)еквівалентно int x; x = 1; x = x;(оголошення змінної, оцінка ініціалізатора поля, присвоєння змінної результату зазначеної оцінки), отже, попередження
msam

41

Це приблизно еквівалентно:

int x;
x = 1;
x = 1;

По-перше, int <var> = <expression>;завжди еквівалентно

int <var>;
<var> = <expression>;

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


Гаразд, але якщо це пішло так, як ви кажете, чому в області класу другий вислів дає помилку? Я маю на увазі, що ви отримуєте 0значення за замовчуванням для ints, тому я би очікував, що результат буде 1, а не undefined reference.
Марсін,

Погляньте на відповідь @izogfif. Здається, це працює, оскільки компілятор C ++ призначає значення за замовчуванням змінним. Те саме, що робить java для змінних рівня класу.
Марсін,

@Marcin: в Java ints не ініціалізуються до 0, коли вони є локальними змінними. Вони ініціалізуються до 0, лише якщо вони є змінними-членами. Отже, у вашому другому рядку x + 1немає визначеного значення, оскільки він xне ініціалізований.
OpenSauce

1
@OpenSauce Але x це визначається як змінна члена ( «в області видимості класу»).
Jacob Raihle

@JacobRaihle: Ага, добре, не помітив цієї частини. Я не впевнений, що байт-код для ініціалізації змінної до 0 генерується компілятором, якщо він бачить, що існує явна інструкція ініціалізації. Тут є стаття, яка детально описує ініціалізацію класів та об’єктів, хоча я не думаю, що вона вирішує цю точну проблему: javaworld.com/jw-11-2001/jw-1102-java101.html
OpenSauce

12

У Java або будь-якою сучасною мовою завдання приходить справа.

Припустимо, якщо у вас є дві змінні x і y,

int z = x = y = 5;

Це твердження є дійсним, і таким чином компілятор їх розділяє.

y = 5;
x = y;
z = x; // which will be 5

Але у вашому випадку

int x = x + 1;

Компілятор дав виняток, оскільки він поділяється так.

x = 1; // oops, it isn't declared because assignment comes from the right.

Попередження про х = х НЕ х = 1
Асім Ghaffar

8

int x = x = 1; не дорівнює:

int x;
x = 1;
x = x;

javap нам знову допомагає, це інструкція JVM, створена для цього коду:

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

більше як:

int x = 1;
x = 1;

Тут немає причин викидати невизначену помилку посилання. Зараз існує використання змінної до її ініціалізації, тому цей код повністю відповідає специфікації. Насправді взагалі не використовується змінна , а лише призначення. А компілятор JIT піде ще далі, він усуне такі конструкції. Якщо чесно сказати, я не розумію, як цей код пов'язаний із специфікацією JLS щодо ініціалізації змінних та використання. Без використання немає проблем. ;)

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

Якщо ми пишемо:

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

дорівнює:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

Самий правильний вираз просто призначається змінним по черзі, без будь-якої рекурсії. Ми можемо зіпсувати змінні будь-яким способом:

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;

7

У int x = x + 1;доповненні 1 до й, так що значення x, це ще не створено.

Але in int x=x=1;буде компілювати без помилок, оскільки ви присвоюєте 1 x.


5

Ваш перший фрагмент коду містить другий, =а не плюс. Це буде компілюватися в будь-якому місці, поки другий фрагмент коду не буде зібраний ні в одному місці.


5

У другому фрагменті коду x використовується перед його оголошенням, тоді як у першому фрагменті коду він просто призначається двічі, що не має сенсу, але є дійсним.


5

Давайте розбимо його крок за кроком, право асоціативне

int x = x = 1

x = 1, присвоїти змінній x 1

int x = x, призначте, що x є собі, як int. Оскільки x раніше було призначено як 1, він зберігає 1, хоча і надмірним способом.

Це добре складається.

int x = x + 1

x + 1, додайте один до змінної x. Однак, якщо x не визначено, це призведе до помилки компіляції.

int x = x + 1, таким чином, цей рядок компілює помилки, оскільки права частина рівних не буде компілюватися, додаючи одну до неназначеної змінної


Ні, це правильно асоціативно, коли є два =оператори, тож це те саме, що int x = (x = 1);.
Джеппе Стіг Нільсен

Ах, мої замовлення вимкнено. Вибач за це. Мав робити їх назад. Я змінив його зараз.
steventnorris

3

Другий int x=x=1- компільований, тому що ви присвоюєте значення x, але в іншому випадку int x=x+1тут змінна x не ініціалізується. Пам'ятайте, що в java локальна змінна не ініціалізується до значення за замовчуванням. Примітка. Якщо вона ( int x=x+1) в області класу також, то вона також дасть помилку компіляції, оскільки змінна не створюється.


2
int x = x + 1;

успішно компілюється у Visual Studio 2008 з попередженням

warning C4700: uninitialized local variable 'x' used`

2
Інтересуючий. Це C / C ++?
Марцін

@Marcin: так, це C ++. @msam: вибачте, я думаю, що я побачив тег cзамість, javaале, мабуть, це було інше питання.
izogfif

Він компілюється, оскільки в C ++ компілятори призначають значення за замовчуванням для примітивних типів. Використовуйте bool y;та y==trueповерне помилкове.
Шрі Харша Чілакапаті

@SriHarshaChilakapati, це якийсь стандарт у компіляторі C ++? Тому що, коли я компілюю void main() { int x = x + 1; printf("%d ", x); }в Visual Studio 2008, під час налагодження я отримую виняток, Run-Time Check Failure #3 - The variable 'x' is being used without being initialized.а у Release я отримую номер, 1896199921надрукований у консолі.
izogfif

1
@SriHarshaChilakapati Розмова про інші мови: У C # для staticполя (статична змінна рівня класу) застосовуються ті самі правила. Наприклад, поле, оголошене як public static int x = x + 1;компіляція без попередження у Visual C #. Можливо, те саме в Java?
Jeppe Stig Nielsen

2

x не ініціалізований у x = x + 1.

Мова програмування Java має статичний тип, що означає, що всі змінні повинні бути спочатку оголошені перед тим, як їх можна використовувати.

Дивіться примітивні типи даних


3
Необхідність ініціалізації змінних перед використанням їх значень не має нічого спільного зі статичним введенням тексту. Статично введений: вам потрібно оголосити, що таке змінна. Ініціалізація перед вживанням: перед тим, як ви зможете використовувати це, потрібно довести значення.
Джон Брайт

@JonBright: Потреба оголошувати типи змінних також не має нічого спільного зі статичним набором тексту. Наприклад, існують статично набрані мови з висновками про тип.
хаммар

@hammar, на мою думку, ви можете аргументувати це двома способами: виводячи тип, ви неявно оголошуєте тип змінної таким чином, що система може зробити висновок. Або висновок типу - це третій спосіб, коли змінні не вводяться динамічно під час виконання, а знаходяться на рівні джерела, залежно від їх використання та зроблених таким чином висновків. Так чи інакше, твердження залишається вірним. Але ти маєш рацію, я не думав про системи інших типів.
Джон Брайт,

2

Рядок коду не складається з попередженням через те, як насправді працює код. Під час запуску коду int x = x = 1Java спочатку створює змінну x, як визначено. Потім він запускає код призначення ( x = 1). Оскільки xце вже визначено, система не має помилок, встановивши xзначення 1. Це повертає значення 1, оскільки це тепер значення x. Тому xзараз остаточно встановлено як 1.
Java в основному виконує код, як ніби це:

int x;
x = (x = 1); // (x = 1) returns 1 so there is no error

Тим НЕ менше, у своїй другій частині коду, int x = x + 1, то + 1твердження вимагає , xщоб визначити, що на той час це не так . Оскільки оператори присвоєння завжди означають, що код, який знаходиться праворуч від =, запускається першим, код зазнає невдачі, оскільки xне визначений. Java запустить такий код:

int x;
x = x + 1; // this line causes the error because `x` is undefined

-1

Виконавець читав твердження справа наліво, і ми створили протилежне. Тому це спочатку дратувало. Зробіть це звичним для читання тверджень (коду) справа наліво, у вас не буде такої проблеми.

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