Виконання оператора присвоєння Java


76

У Java я розумію, що присвоєння оцінює значення правильного операнда, тому такі оператори, як x == (y = x)оцінка to true.

Однак цей код видає false.

public static void main(String[]args){
    String x = "hello";
    String y = "goodbye";
    System.out.println(x.equals(x = y));
}

Чому це? На моє розуміння, він спочатку оцінює (x = y), що призначає xзначення y, а потім повертає значення y. Потім x.equals(y)обчислюється, що повинно бути trueз тих пір, xі тепер yмають мати ті самі посилання, але натомість я отримую false.

Знімок екрана, що показує джерело та те, що результат "помилковий"

Що тут відбувається?


13
Думаю, ви хотіли побачити результат заx.equals( y = x )
nits.kk

1
Чи міг компілятор вбудувати xта y?
Ліно,

3
Ви припускаючи , що призначення x = yна правій стороні виконується доx того на лівій стороні оцінюється?
khelwood

@khelwood так, це було моє припущення. Це не повинно
Сем

1
@ nits.kk Я не думаю. OP вже сказав, що вони розуміють, що x == (y = x)оцінює як істинне. Тоді поведінка того, що ви пропонуєте, буде очевидною ...
Педро,

Відповіді:


76

Перш за все: це цікаве питання, але ніколи не повинно виникати у "реальному коді", оскільки присвоєння змінної, яку ви викликаєте в тому самому рядку, бентежить, навіть якщо ви знаєте, як це працює.

Що відбувається тут, це 3 кроки:

  1. з'ясувати, на якому об'єкті слід викликати метод (тобто оцінити перший x , це призведе до посилання на рядок "привіт")
  2. з'ясувати параметри (тобто оцінити x = y, що змінитьсяx щоб вказати на рядок "до побачення", а також повернути посилання на цей рядок)
  3. викличте метод equalsза результатом # 1, використовуючи результат # 2 як параметр (який буде посиланням на рядки "привіт" та "до побачення" відповідно).

Перегляд байт-коду, створеного для цього методу, дає зрозуміти (припускаючи, що ви вільно володієте байт-кодом Java):

     0: ldc           #2                  // String hello
     2: astore_1
     3: ldc           #3                  // String goodbye
     5: astore_2
     6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: aload_1
    10: aload_2
    11: dup
    12: astore_1
    13: invokevirtual #5                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
    16: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
    19: return

Рядок # 9 - це крок 1 вище (тобто оцінює x і запам'ятовує значення).

Рядок 10-12 - це крок 2. Він завантажується y, продублює його (один раз для присвоєння, один раз для поверненого значення виразу присвоєння) і присвоюєx .

Рядок # 13 викликає equalsрезультат, обчислений у рядку # 9, і результат Рядки # 10-12.


36
TL; DR: x.equals(x = y)=> "hello".equals(x = y)=> "hello".equals(x = "goodbye")=> "hello".equals("goodbye")=> false.
Бернхард Баркер,

8
Важливим моментом є те, що він .має вищий пріоритет, ніж= .
Gaurang Tandon

4
Це більше стосується порядку оцінки, аніж переваги. Дужки роблять пріоритет у будь-якому випадку неактуальним.
сплав

38

Гарне питання! І JLS має відповідь ...

§15.12.4.1 (Приклад 15.12.4.1-2). Порядок оцінки під час виклику методу:

Як частина виклику методу екземпляра, існує вираз, який позначає об’єкт, який потрібно викликати. Здається, цей вираз повністю обчислюється перед тим, як обчислюється будь-яка частина будь-якого виразу аргументу для виклику методу.

Отже, у:

String x = "hello";
String y = "goodbye";
System.out.println(x.equals(x = y));

поява xbefore .equalsобчислюється першим, перед виразом аргументуx = y .

Отже, посилання на рядок   hello  запам'ятовується як цільове посилання перед тим, як локальну змінну xзмінювати на посилання на рядок goodbye. Як результат, equalsметод викликається для цільового об'єкта helloз аргументом goodbye, тож результат виклику є false.


28

Важливо пам’ятати, що Stringin java - це об’єкт, а отже посилання. Коли ви телефонуєте

x.equals(...)

Він перевіряє , якщо значення в тому місці , в даний момент , на який посилається xодно , що ви передаєте в. Всередині, ви змінюєте значення , яке xбуде посилаються , але ви все ще кличете equalsз вихідної посиланням (посилання на «привіт»). Отже, зараз ваш код порівнюється, щоб побачити, чи дорівнює "привіт" "до побачення", а це явно не так. Після цього, якщо ви використаєте xзнову, це призведе до посилання на те саме значення, що і y.


якщо я вас розумію, це: чи "hello".equals((x = y))слід повертатися true?
Халаєм Аніс,

3
Ні, оскільки (x = y) поверне значення y, яке є "до побачення". Отже, якщо ви зробили "до побачення" .equals (x = y), це повернуло б істину
Keveloper

5

x=yу дужках означає, що вираз (x=y)є тепер goodbye, тоді як зовнішній х у x.equalsмістить значенняhello


2
Це насправді не пояснює, чому це відбувається, і не надає додаткових деталей, які були б корисними для інших.
Грей

Тепер, коли я прочитав це вголос, я, як правило, з вами погоджуюсь .. інші відповіді досить багатослівні, тому не потрібно редагувати ..
Четан Ядхав, CD,

4

Реймус дав правильну відповідь, але я хотів би пояснити.

У Java (і більшості мов) домовленість змінної йде зліва, а призначення справа.

Давайте розберемо це:

String x = "hello";
//x <- "hello"

String y = "goodbye";
//y <- "goodbye";

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

System.out.println(x.equals(x = y)); //Compound statement

Ось, x.equals(...) називається вихідне посилання на x, або "привіт", воно оновлюється для другого посилання.

Я б написав це як (і це дасть вам очікувану відповідь):

x = y;
// x <- y = "goodbye"

boolean xEqualsX = x.equals(x);
// xEqualsX <- true

System.out.println(xEqualsX);
// "true"

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


2
Re: "завжди": існує таке поняття, як занадто багато явності: чи не рекомендували б ви System.out.println("Bytes: "+1024*k);писати як три твердження?
Девіс Оселедець

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

2

Я спробував ваше запитання затьмарення, обидва ваші вирази правильні. 1) x == (y = x) оцініть як true, це правда, тому що значення x присвоюється y, яке є "привіт", тоді x і y порівняють, вони будуть однаковими, тому результат буде істинним

2) x.equal (x = y) хибне, оскільки значення y присвоюється x, що прощається, тоді x і x порівняти їх значення будуть різними, тому результат буде хибним


1

Я бачу це питання неспеціалістами як "hello".equals("goodbye"). Тож повертає false.


1

У Java String - це клас.

String x = "hello";
String y = "goodbye"; 

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

 System.out.println(x.equals(x = y)); 
//this compare value (hello and goodbye) return true

    System.out.println(x == (y = x)); 
// this compare reference of an object (x and y) return false  

1
Ви насправді запускали цей зразок коду? Як зазначається у запитанні, System.out.println(x.equals(x = y));повертає помилкове, всупереч тому, що стверджується у вашій відповіді.
Чарлі Гардінг

-4

Це бачить, якщо x.equals (присвоїти x до y, завжди повертає true), то в основному x.equals (true)


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