X == (x = y) neden (x = y) == x ile aynı değil?


207

Aşağıdaki örneği düşünün:

class Quirky {
    public static void main(String[] args) {
        int x = 1;
        int y = 3;

        System.out.println(x == (x = y)); // false
        x = 1; // reset
        System.out.println((x = y) == x); // true
     }
}

Java Dil Spesifikasyonunda, x = yparantezler tarafından belirtilen sıraya göre ilk olarak hesaplanması gereken sağ tarafla ( ) karşılaştırmak için bir değişkenin önceki değerini yüklemeyi dikte eden bir öğe olup olmadığından emin değilim .

İlk ifade neden değerlendirilir false, ikincisi ise değerlendirir true? Ben umuyordum (x = y)ilk değerlendirilecek ve daha sonra karşılaştırmak istiyorsunuz xkendisi ile ( 3) ve karşılığında true.


Bu soru, bir Java ifadesindeki alt ifadelerin değerlendirme sırasından farklıdır, çünkü xburada kesinlikle bir 'alt ifade' değildir. Bu gereken yüklenen karşılaştırma için yerine 'değerlendirdi' edilecek. Soru Java'ya özgüdür ve ifade x == (x = y), zor röportaj soruları için yaygın olarak hazırlanmış pratik olmayan yapıların aksine, gerçek bir projeden geldi. Karşılaştırma ve değiştirme deyimi için tek satırlık bir yedek olması gerekiyordu

int oldX = x;
x = y;
return oldX == y;

x86 CMPXCHG komutundan bile daha basit olan Java'da daha kısa bir ifadeyi hak ediyordu.


62
Sol taraf daima sağ taraftan önce değerlendirilir. Köşeli parantezler bununla bir fark yaratmaz.
Louis Wasserman

11
İfadenin değerlendirilmesi x = ykesinlikle önemlidir ve xdeğerine ayarlanan yan etkiye neden olur y.
Louis Wasserman

50
Kendinize ve takım arkadaşlarınıza bir iyilik yapın ve devlet mutasyonunu devlet sınavıyla aynı çizgide karıştırmayın. Bunu yapmak kodunuzun okunabilirliğini büyük ölçüde azaltır. (Atomisite gereklilikleri nedeniyle kesinlikle gerekli olduğu bazı durumlar vardır, ancak zaten var olanlar ve işlevleri anında tanınacaktır.)
jpmc26

50
Asıl soru neden böyle bir kod yazmak istediğinizdir.
klutt

26
Sorunuzun anahtarı, parantezlerin değerlendirme sırasını ima ettiğine dair yanlış inancınızdır. Bu, ilkokulda nasıl matematik öğretildiğimiz ve bazı başlangıç ​​programlama kitaplarının hala yanlış anladığı için yaygın bir inançtır, ancak yanlış bir inançtır. Bu oldukça sık sorulan bir soru. Konu ile ilgili makalelerimi okumaktan faydalanabilirsiniz; C # ile ilgili ama Java için geçerli: ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order ericlippert.com/2009/08/10/precedence-vs-order-redux
Eric Lippert

Yanıtlar:


97

köşeli parantezler tarafından ima edilen sıra ile önce hesaplanması gereken

Hayır. Parantezlerin hesaplama veya değerlendirme sırası üzerinde (genel) bir etkisi olduğu yaygın bir yanılgıdır. İfadenizin bölümlerini yalnızca belirli bir ağaca zorlarlar, doğru işlenenleri iş için doğru işlemlere bağlarlar.

(Ve bunları kullanmazsanız, bu bilgi operatörlerin "önceliğinden" ve ilişkilendirilebilirliğinden gelir; bu, dilin sözdizimi ağacının nasıl tanımlandığının bir sonucudur. parantez kullanın, ancak sadeleştiririz ve o zaman herhangi bir öncelik kuralına güvenmediğimizi söyleriz.)

Bu yapıldıktan sonra (yani, kodunuz bir programa ayrıştırıldıktan sonra) bu işlenenlerin hala değerlendirilmesi gerekir ve bunun nasıl yapıldığına dair ayrı kurallar vardır: söz konusu kurallar (Andrew'un bize gösterdiği gibi) her işlemin LHS'sini belirtir. önce Java ile değerlendirilir.

Bunun tüm dillerde geçerli olmadığını unutmayın; örneğin, C ++ ' &&da ||, veya gibi kısa devre operatörü kullanmadığınız sürece , işlenenlerin değerlendirme sırası genellikle belirtilmez ve her iki şekilde de ona güvenmemelisiniz.

Öğretmenler, "bu, eklemenin önce gerçekleşmesini sağlar" gibi yanıltıcı ifadeler kullanarak operatör önceliğini açıklamayı bırakmalıdır. Bir ifade verildiğinde x * y + z, uygun açıklama "operatör önceliği, eklemenin herhangi bir" düzen "den bahsetmeden, ve arasında değil x * yve arasında gerçekleşmesini sağlar .zyz


6
Keşke öğretmenlerim altta yatan matematik ve onu temsil etmek için kullandıkları sözdizimi arasında bir miktar ayrım yapmış olsalardı, sanki Roma rakamları ya da Polonyalı gösterim ile bir gün geçirdiysek ya da bu eklemenin aynı özelliklere sahip olduğunu gördük. Ortaokuldaki ilişkilendirilebilirliği ve tüm bu özellikleri öğrendik, bu yüzden bol zaman vardı.
John P

1
Bu kuralın tüm diller için geçerli olmadığını söylemiş olmanıza sevindim. Ayrıca, her iki tarafın da bir dosyaya yazmak veya geçerli saati okumak gibi başka bir yan etkisi varsa, (Java'da bile) gerçekleşen sıraya göre tanımlanmamıştır. Ancak, karşılaştırmanın sonucu soldan sağa (Java'da) değerlendirilmiş gibi olacaktır. Başka bir kenara: oldukça birkaç dil karıştırma atamasına izin vermiyor ve sözdizimi kurallarıyla bu şekilde karşılaştırılıyor ve sorun ortaya çıkmayacak.
Abel

5
@JohnP: Daha da kötüleşiyor. 5 * 4, 5 + 5 + 5 + 5 veya 4 + 4 + 4 + 4 + 4 anlamına mı geliyor? Bazı öğretmenler bu seçimlerden sadece birinin doğru olduğu konusunda ısrar ediyor.
Brian

3
@Brian Ama ... ama ... gerçek sayıların çarpımı değişmeli!
Yörüngedeki Hafiflik Yarışları

2
Düşünme dünyamda bir çift parantez "için gerekli" yi temsil ediyor. ´a * (b + c) ´ hesaplandığında, parantez toplama işleminin sonucunun çarpma için gerekli olduğunu ifade eder . Örtülü operatör tercihleri, LHS-birinci veya RHS-birinci kurallar hariç , parenler tarafından ifade edilebilir . (Bu doğru mu?) @Brian Matematikte, çarpma işleminin tekrarlanan toplama ile ikame edilebildiği nadir görülen durumlar vardır, ancak bu her zaman doğru değildir (karmaşık sayılarla başlar, ancak bunlarla sınırlı değildir). Bu yüzden eğitimcileriniz insanlara neler söylediğine gerçekten dikkat etmelidir ....
syck

164

==ikili bir eşitlik operatörüdür .

İkili bir operatörün sol tarafındaki işlenen , sağ taraftaki işlenenin herhangi bir kısmı değerlendirilmeden önce tam olarak değerlendirilmiş gibi görünür .

Java 11 Teknik Özellikleri> Değerlendirme Sırası> Önce Soldaki İşleneni Değerlendirin


42
"Öyle gözüküyor" ifadesi, sanki kesinmiş gibi gelmiyor, tbh.
Bay Lister

86
"gibi görünüyor" belirtimi, işlemlerin gerçekte bu sırayla kronolojik olarak yapılmasını gerektirmediği anlamına gelir, ancak olsaydı alacağınız aynı sonucu almanızı gerektirir.
Robyn

24
@MrLister "gibi görünüyor" kısmında kötü bir kelime seçimi gibi görünüyor. "Görünüş" ile "geliştiriciye bir fenomen olarak tezahür etmek" kastedilmektedir. "etkili bir şekilde" daha iyi bir ifade olabilir.
Kelvin

18
C ++ topluluğunda bu, "as-if" kuralına eşdeğerdir ... işlenen, teknik olarak olmasa bile, aşağıdaki kurallara göre uygulanmış gibi "davranması gerekir".
Michael Edenfield

2
@Kelvin Katılıyorum, "göründüğü gibi" yerine bu kelimeyi de seçerdim.
MC İmparatoru

149

LouisWasserman'ın dediği gibi, ifade soldan sağa değerlendirilir. Ve java aslında "değerlendirmenin" ne yaptığını umursamıyor, sadece çalışmak için (uçucu olmayan, nihai) bir değer üretmeyi önemsiyor.

//the example values
x = 1;
y = 3;

İlk çıktısını hesaplamak System.out.println()için aşağıdakiler yapılır:

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

ve ikincisini hesaplamak için:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

İkinci değer daima başlangıç değerlerinin ne olursa olsun, true değerlendirecek o Not xve y, etkili atandığı değişkene bir değerin atama karşılaştırarak çünkü ve a = bve bbu sırayla değerlendirilir olacak, hep aynı olması tanım olarak.


"Soldan sağa" matematikte de doğrudur, bu arada, sadece bir parantez veya önceliğe ulaştığınızda, onların içinde yinelenir ve ana katmanda daha ileri gitmeden önce soldan sağa doğru her şeyi değerlendirirsiniz. Fakat matematik bunu asla yapmaz; ayrım sadece önemlidir, çünkü bu bir denklem değil birleşik işlemdir, hem bir ödev hem de bir denklemi tek bir vuruşta yapar . Bunu asla yapmazdım çünkü kod golf yapmadıkça veya performansı optimize etmek için bir yol aramadıkça okunabilirlik zayıftı ve yorumlar olacaktı.
Harper - Monica

25

Java Dil Belirtimi'nde bir değişkenin önceki değerini yüklemeyi dikte eden bir öğe olup olmadığından emin değilim ...

Var. Eğer şartname ne diyor belirsiz dahaki sefere özellikleri okumak ve lütfen daha sonra hala belli olmadığını soru sormak.

... önce (x = y)köşeli parantezlerin ima ettiği sıra ile hesaplanması gereken sağ taraf .

Bu ifade yanlıştır. Parantezler bir değerlendirme sırası anlamına gelmez . Java'da değerlendirme sırası parantezden bağımsız olarak sağdan sola doğrudur. Parantezler, değerlendirme sırasını değil, alt ifade sınırlarının nerede olduğunu belirler.

İlk ifade neden yanlış olarak değerlendirilir, ancak ikinci ifade doğru olarak değerlendirilir?

==İşleç kuralı : bir değeri üretmek için sol tarafı değerlendirmek, bir değeri üretmek için sağ tarafı değerlendirmek, değerleri karşılaştırmak, karşılaştırma ifadenin değeridir.

Diğer bir deyişle, anlamı expr1 == expr2her zaman yazmış temp1 = expr1; temp2 = expr2;ve değerlendirmiş gibi aynıdır temp1 == temp2.

=Sol tarafta yerel bir değişkeni olan işleç için kural şudur: bir değişken üretmek için sol tarafı değerlendirin, bir değer üretmek için sağ tarafı değerlendirin, atamayı gerçekleştirin, sonuç atanan değerdir.

Öyleyse bir araya getirin:

x == (x = y)

Bir karşılaştırma operatörümüz var. Bir değeri üretmek için sol tarafı değerlendirin - şu anki değerini alırız x. Sağ tarafı değerlendirin: bu bir atamadır, böylece bir değişkeni üretmek için sol tarafı değerlendiririz - değişken x- sağ tarafı değerlendiririz - şu anki değerini y- atar xve sonuç atanan değerdir. Ardından, orijinal değerini xatanan değerle karşılaştırırız .

(x = y) == xBir egzersiz olarak yapabilirsiniz . Yine unutmayın, sol tarafı değerlendirmek için tüm kurallar, sağ tarafı değerlendirmek için tüm kurallardan önce gerçekleşir .

Önce (x = y) değerlendirilmesini beklerdim, sonra x'i kendisi (3) ile karşılaştırır ve true değerini döndürürdü.

Beklentiniz, Java kuralları hakkında bir dizi yanlış inanca dayanmaktadır. Umarım şimdi doğru inançlara sahipsiniz ve gelecekte gerçek şeyler bekleyeceksiniz.

Bu soru "Java ifadesinde alt ifadelerin değerlendirme sırası" ndan farklıdır.

Bu ifade yanlıştır. Bu soru tamamen almanca.

x kesinlikle burada bir 'alt ifade' değildir.

Bu ifade de yanlıştır. Her örnekte iki kez bir alt ifadedir .

Karşılaştırma için 'değerlendirilmek' yerine yüklenmesi gerekir.

Bunun ne anlama geldiğini bilmiyorum.

Görünüşe göre hala birçok yanlış inancınız var. Benim tavsiyem, yanlış inançlarınız yerine gerçek inançların yerini alana kadar spesifikasyonu okumanızdır.

Soru Java'ya özgüdür ve zor röportaj soruları için yaygın olarak hazırlanmış uzak getirilmiş pratik olmayan yapıların aksine x == (x = y) ifadesi gerçek bir projeden geldi.

İfadenin yaygınlığı soru ile ilgili değildir. Bu tür ifadelere ilişkin kurallar tarifnamede açıkça tanımlanmıştır; oku onu!

Karşılaştırma ve değiştirme deyimi için tek satırlık bir yedek olması gerekiyordu

Bu tek satırlık değiştirme, kodun okuyucusunda büyük bir karışıklığa neden olduğundan, bunun kötü bir seçim olduğunu öneririm. Kodu daha kısa ve anlaşılması zor hale getirmek bir kazanç değildir. Kodu daha hızlı hale getirme olasılığı düşüktür.

Bu arada, C # etti karşılaştırmak ve yerine bir kütüphane yöntemi olarak yapabilirsiniz bir makine talimat aşağı jitted edilecek. Java tür sisteminde temsil edilemediğinden Java'nın böyle bir yöntemi olmadığına inanıyorum.


8
Herkes JLS'nin tamamını geçebilseydi, Java kitapları yayınlamak için bir neden olmazdı ve bu sitenin en az yarısı da işe yaramazdı.
John McClane

8
@JohnMcClane: Sizi temin ederim, tüm spesifikasyonlardan geçmekte herhangi bir zorluk yoktur, aynı zamanda bunu yapmak zorunda değilsiniz. Java özelliği, en çok ilgilendiğiniz parçalara hızlı bir şekilde ulaşmanıza yardımcı olacak yararlı bir "içindekiler" ile başlar. Ayrıca çevrimiçi ve anahtar sözcük aranabilir. Bununla birlikte, haklısınız: Java'nın nasıl çalıştığını öğrenmenize yardımcı olacak birçok iyi kaynak var; size tavsiyem onları kullanmanızdır!
Eric Lippert

8
Bu cevap gereksiz yere küçümseyici ve kaba. Unutmayın: iyi olun .
walen

7
@LuisG .: Hiçbir yoğunlaşma amaçlanmamış veya ima edilmemiştir; hepimiz birbirimizden öğrenmek için buradayız ve yeni başlayan bir zamanda kendim yapmadığım hiçbir şeyi tavsiye etmiyorum. Kaba da değil. Yanlış inançlarını açıkça ve açık bir şekilde tanımlamak, orijinal postere bir nezakettir . "Nezaket" in arkasına saklanmak ve insanların yanlış inançlara sahip olmalarına izin vermek yararsızdır ve kötü düşünce alışkanlıklarını güçlendirir .
Eric Lippert

5
@LuisG .: JavaScript tasarımı hakkında bir blog yazıyordum ve şimdiye kadar aldığım en yararlı yorumlar, Brendan'ın yanlış yaptığım yeri açıkça ve açık bir şekilde işaret etmesinden kaynaklanıyordu. Bu harikaydı ve zaman ayırdığı için onu takdir ettim, çünkü daha sonra hayatımın sonraki 20 yılını kendi işimde bu hatayı tekrarlamamak ya da daha kötüsü başkalarına öğretmek için yaşadım. Ayrıca, insanların yanlış şeylere nasıl inandıklarına dair bir örnek olarak kendimi kullanarak başkalarındaki aynı yanlış inançları düzeltme fırsatı da verdi.
Eric Lippert

16

Operatör önceliği ve operatörlerin nasıl değerlendirildiği ile ilgilidir.

Parantez '()' daha yüksek önceliğe sahiptir ve soldan sağa ilişkilendirilebilirliği vardır. Eşitlik '==' bu soruda bir sonraki adımdır ve ilişkilendirilebilirlik soldan sağa doğrudur. '=' Ödevi sonlanır ve sağdan sola ilişkilendirilebilirliği vardır.

Sistem ifadeyi değerlendirmek için yığın kullanır. İfade soldan sağa değerlendirilir.

Şimdi orijinal soru geliyor:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

İlk önce x (1) istiflenmeye itilir. daha sonra iç (x = y) değerlendirilir ve x (3) değerine sahip istiflemeye itilir. Şimdi x (1), x (3) ile karşılaştırılacağı için sonuç yanlıştır.

x = 1; // reset
System.out.println((x = y) == x); // true

Burada, (x = y) değerlendirilecektir, şimdi x değeri 3 olur ve x (3) istiflenmeye itilir. Şimdi eşitlikten sonra değişen değerde x (3) yığına itilecek. Şimdi ifade değerlendirilecek ve ikisi de aynı olacak ve sonuç doğrudur.


12

Aynı değil. Sol taraf her zaman sağ taraftan önce değerlendirilir ve köşeli ayraçlar bir yürütme sırası değil, bir komutlar grubu belirtir.

İle:

      x == (x = y)

Temelde aynı şeyi yapıyorsunuz:

      x == y

Ve x karşılaştırmadan sonra y değerini alacaktır .

Birlikte iken:

      (x = y) == x

Temelde aynı şeyi yapıyorsunuz:

      x == x

X sonra y değerini aldı . Ve hep dönecektir gerçek .


9

Kontrol ettiğiniz ilk testte 1 == 3 yapar.

İkinci testte kontrolünüz 3 == 3 yapar.

(x = y) değeri atar ve bu değer test edilir. Önceki örnekte önce x = 1 sonra x 3 atanır. 1 == 3 mü?

İkincisinde, x'e 3 atanır ve açıkçası hala 3'tür. 3 == 3 mü?


8

Diğer, belki daha basit bir örneği düşünün:

int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true

Burada, içindeki artış öncesi operatörün karşılaştırma yapılmadan önce++x uygulanması gerekir - örneğin örneğinizde olduğu gibi karşılaştırma öncesi hesaplanması gerekir .(x = y)

Bununla birlikte, ekspresyon değerlendirmesi hala soldan sağa → sağda gerçekleşir , bu nedenle ilk karşılaştırma aslında 1 == 2ikincisidir 2 == 2.
Aynı şey örneğinizde de olur.


8

İfadeler soldan sağa doğru değerlendirilir. Bu durumda:

int x = 1;
int y = 3;

x == (x = y)) // false
x ==    t

- left x = 1
- let t = (x = y) => x = 3
- x == (x = y)
  x == t
  1 == 3 //false

(x = y) == x); // true
   t    == x

- left (x = y) => x = 3
           t    =      3 
-  (x = y) == x
-     t    == x
-     3    == 3 //true

5

Temelde x ilk ifadesi 1 değerine sahipti. Böylece Java 1 == ifadesini aynı olmayacak yeni x değişkeniyle karşılaştırır.

İkincisinde x = y dediniz, bu da x'in değerinin değiştiği anlamına gelir ve bu yüzden tekrar aradığınızda aynı değer olur, bu yüzden neden doğrudur ve x == x


4

== bir karşılaştırma eşitliği operatörüdür ve soldan sağa doğru çalışır.

x == (x = y);

burada x'in eski atanan değeri, yeni x'in yeni atama değeri ile karşılaştırılır, (1 == 3) // false

(x = y) == x;

Oysa burada x'in yeni atama değeri, karşılaştırmadan hemen önce kendisine atanan x'in yeni tutma değeri ile karşılaştırılır, (3 == 3) // true

Şimdi bunu düşünün

    System.out.println((8 + (5 * 6)) * 9);
    System.out.println(8 + (5 * 6) * 9);
    System.out.println((8 + 5) * 6 * 9);
    System.out.println((8 + (5) * 6) * 9);
    System.out.println(8 + 5 * 6 * 9);

Çıktı:

342

278

702

342

278

Bu nedenle, Parantezler en büyük rolünü sadece karşılaştırma ifadelerinde değil aritmetik ifadelerde oynar.


1
Sonuç yanlış. Davranış, aritmetik ve karşılaştırma işleçleri arasında farklı değildir. x + (x = y)ve (x = y) + xkarşılaştırma işleçleriyle orijinalle benzer davranış gösterir.
JJJ

1
@JJJ x + (x = y) ve (x = y) + x ile ilgili bir karşılaştırma yoktur, sadece x'e y değeri atar ve bunu x'e ekler.
Nisrin Dhoondia

1
... evet, mesele bu. Aritmetik ve karşılaştırma ifadeleri arasında hiçbir fark olmadığı için, "Parantezler yalnızca karşılaştırma ifadelerinde değil aritmetik ifadelerde önemli rol oynar ."
JJJ

2

Burada bir şey iki operatör üzerinden arithmatic operatörler / ilişkisel operatörler önceliklerinin sırasıdır =vs ==baskın biridir ==o önce olduğu için (İlişkisel Operatörler ağırlıkta olmak) =atama operatörleri. Önceliğe rağmen, değerlendirme sırası LTR'dir (SAĞA SOL) öncelik, değerlendirme siparişinden sonra ortaya çıkar. Bu nedenle, herhangi bir kısıtlama değerlendirmesinden bağımsız olarak LTR'dir.


1
Cevap yanlış. Operatör önceliği değerlendirme sırasını etkilemez. Açıklama için en çok oy alan cevapların bazılarını, özellikle de bu cevabı okuyun .
JJJ

1
Doğru, bu aslında önceliğe ilişkin kısıtlamalar yanılsamasını öğretme şeklimiz tüm bu şeylere geliyor, ancak doğru bir etkisi olmadığını belirtti çünkü değerlendirme sırası sağdan sola kalır
Himanshu Ahuja

-1

Soldaki ikinci karşılaştırmada y'ye x (solda) atandıktan sonra atama 3 == 3'ü karşılaştırmanız kolaydır. İlk örnekte x = 1'i yeni atama x = 3 ile karşılaştırıyorsunuz. x'in soldan sağa her zaman geçerli durum okuma ifadeleri alınır.


-2

Bir Java derleyicisi yazmak veya bir Java derleyicisinin düzgün çalıştığını doğrulamak için programları test etmek istiyorsanız, sorduğunuz soru türü çok iyi bir sorudur. Java'da, bu iki ifadenin gördüğünüz sonuçları üretmesi gerekir. Örneğin, C ++ 'da bunu yapmak zorunda değiller - bu yüzden birisi Java derleyicisinde bir C ++ derleyicisinin parçalarını yeniden kullandıysa, teorik olarak derleyicinin olması gerektiği gibi davranmadığını görebilirsiniz.

Bir yazılım geliştiricisi olarak, okunabilir, anlaşılabilir ve bakımı kolay kod yazarken, kodunuzun her iki sürümü de kötü sayılır. Kodun ne yaptığını anlamak için Java dilinin tam olarak nasıl tanımlandığını bilmek gerekir . Hem Java hem de C ++ kodu yazan biri koda bakarak ürperir. Neden tek bir kod satırının ne yaptığını sormanız gerekiyorsa, o koddan kaçınmalısınız. (Sanırım "neden" sorunuzu doğru cevaplayan adamların kendilerinin de bu kod dizisinden kaçınmasını umuyoruz).


"Kodun ne yaptığını anlamak için Java dilinin tam olarak nasıl tanımlandığını bilmek gerekir." Ama ya her iş arkadaşınız bunu sağduyu olarak düşünürse?
BinaryTreeee
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.