Bu Java kodu neden derleniyor?


96

Yöntem veya sınıf kapsamında, aşağıdaki satır (uyarı ile) derlenir:

int x = x = 1;

Değişkenlerin varsayılan değerlerini aldığı sınıf kapsamında, aşağıdakiler 'tanımsız başvuru' hatası verir:

int x = x + 1;

İlki x = x = 1aynı 'tanımsız referans' hatasıyla sonuçlanmalı değil mi? Ya da belki ikinci satır int x = x + 1derlenmeli? Yoksa kaçırdığım bir şey mi var?


1
Anahtar kelimeyi staticsınıf kapsamı değişkenine static int x = x + 1;eklerseniz, aynı hatayı alır mısınız? Çünkü C # 'da statik olup olmaması bir fark yaratır.
Jeppe Stig Nielsen

static int x = x + 1Java'da başarısız.
Marcin

1
c # hem int a = this.a + 1;ve int b = 1; int a = b + 1;sınıf kapsamı (her ikisi de Java Tamam) muhtemelen §17.4.5.2 için, fail de - "bir örnek alanı için başlatıcı bir değişken örneğini referans yapamaz yaratılmaktadır." Bir yerde açıkça izin verilip verilmediğini bilmiyorum ama statik böyle bir kısıtlamaya sahip değil. Java'da kurallar farklıdır ve static int x = x + 1aynı nedenle başarısız int x = x + 1yapar
MSAM

Bir bayt kodlu cevaplayıcı şüpheleri giderir.
rgripper

Yanıtlar:


101

tl; dr

İçin alanlar , int b = b + 1çünkü yasadışı bbir yasadışı ileri referanstır b. int b = this.b + 1Şikayet olmadan derleyen yazarak bunu gerçekten düzeltebilirsiniz .

İçin yerel değişkenler , int d = d + 1çünkü yasadışı dkullanımdan önce başlatılmadı. Bu, her zaman varsayılan olarak başlatılan alanlar için geçerli değildir .

Derlemeye çalışarak farkı görebilirsiniz.

int x = (x = 1) + x;

alan bildirimi ve yerel değişken bildirimi olarak. İlki başarısız olacak, ancak ikincisi anlambilimdeki farklılık nedeniyle başarılı olacaktır.

Giriş

Öncelikle, alan ve yerel değişken başlatıcıların kuralları çok farklıdır. Yani bu cevap, kuralları iki bölümde ele alacaktır.

Bu test programını aşağıdakiler boyunca kullanacağız:

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;
    }
}

Beyanı bgeçersizdir ve bir illegal forward referencehata ile başarısız olur .
Beyanı dgeçersizdir ve bir variable d might not have been initializedhata ile başarısız olur .

Bu hataların farklı olması, hataların sebeplerinin de farklı olduğunu ima etmelidir.

Alanlar

Java'daki alan başlatıcıları JLS §8.3.2 , Alanların Başlatma tarafından yönetilir .

Kapsamı bir alanın tanımlandığı §6.3 JLS beyan Kapsamı.

İlgili kurallar şunlardır:

  • mSınıf tipi C (§8.1.6) tarafından bildirilen veya miras alınan bir üyenin bildiriminin kapsamı, iç içe geçmiş tür bildirimleri de dahil olmak üzere C'nin tüm gövdesidir.
  • Örnek değişkenler için başlatma ifadeleri, bildirimi daha sonra metin olarak gerçekleşse bile, sınıf tarafından bildirilen veya sınıf tarafından miras alınan herhangi bir statik değişkenin basit adını kullanabilir.
  • Kullanımdan sonra bildirimleri metin olarak görünen örnek değişkenlerinin kullanımı, bu örnek değişkenleri kapsam dahilinde olsa bile bazen kısıtlanır. Durum değişkenlerine ileriye dönük referansı yöneten kesin kurallar için §8.3.2.3'e bakın.

§8.3.2.3 diyor ki:

Bir üyenin bildirimi, kullanılmadan önce metin olarak görünmelidir, yalnızca üye bir sınıf veya arabirim C'nin bir örnek (sırasıyla statik) alanıysa ve aşağıdaki koşulların tümü geçerliyse:

  • Kullanım, C örneğinde (sırasıyla statik) değişken başlatıcıda veya C örneğinde (sırasıyla statik) başlatıcıda gerçekleşir.
  • Kullanım, bir ödevin sol tarafında değildir.
  • Kullanım basit bir isim aracılığıyladır.
  • C, kullanımı kapsayan en içteki sınıf veya arabirimdir.

Bazı durumlar dışında, aslında bildirilmeden önce alanlara başvurabilirsiniz. Bu kısıtlamalar aşağıdaki gibi kodları önlemeyi amaçlamaktadır:

int j = i;
int i = j;

derlemeden. Java spesifikasyonu, "yukarıdaki kısıtlamalar, derleme zamanında döngüsel veya başka şekilde hatalı biçimlendirilmiş başlatmaları yakalamak için tasarlanmıştır" diyor.

Bu kurallar aslında neye indirgeniyor?

Kısacası, kurallar temel olarak, eğer (a) referans bir başlatıcı içindeyse, (b) referans atanmıyorsa, (c) referans a ise, o alana yapılan bir referanstan önce bir alan bildirmeniz gerektiğini söyler. basit ad (niteleyici gibi değildir this.) ve (d) bir iç sınıftan erişilmiyor. Bu nedenle, dört koşulu karşılayan ileriye yönelik bir referans yasa dışıdır, ancak en az bir koşulda başarısız olan ileri bir referans uygundur.

int a = a = 1;derler o (b) ihlal ettiğinden: Referans a edilir o başvurmak için yasal yüzden, tahsis edilen aöncesinde a'ın tam beyanı.

int b = this.b + 1(c) 'yi ihlal ettiği için de derler: referans this.bbasit bir isim değildir (ile nitelenir this.). Bu garip yapı hala mükemmel bir şekilde tanımlanmıştır, çünkü this.bsıfır değerine sahiptir.

Bu nedenle, temel olarak, başlatıcılardaki alan referansları üzerindeki kısıtlamalar int a = a + 1başarılı bir şekilde derlenmesini engeller .

Alan beyanı olduğunu gözlemleyin int b = (b = 1) + bedecektir başarısız nihai çünkü derlemek için bhala yasadışı ileri referanstır.

Yerel değişkenler

Yerel değişken bildirimleri JLS §14.4 , Yerel Değişken Beyanı Beyanları tarafından yönetilir .

Kapsamı yerel bir değişkenin de tanımlandığı JLS §6.3 , beyan Kapsamı:

  • Bir bloktaki yerel değişken bildiriminin kapsamı (§14.4), bildirimin göründüğü bloğun geri kalanıdır, kendi başlatıcısı ile başlar ve yerel değişken bildirim bildiriminde sağdaki diğer bildiricileri içerir.

Başlatıcıların, bildirilen değişkenin kapsamında olduğuna dikkat edin. Öyleyse neden int d = d + 1;derlemiyor?

Bunun nedeni Java'nın kesin atama kuralından kaynaklanmaktadır ( JLS §16 ). Kesin atama, temel olarak, yerel bir değişkene her erişimin bu değişkene önceden bir atama yapması gerektiğini söyler ve Java derleyicisi, atamanın her zaman herhangi bir kullanımdan önce gerçekleştiğinden emin olmak için döngüleri ve dalları kontrol eder (bu nedenle, belirli atamanın, özel ona). Temel kural şudur:

  • Yerel bir değişken veya boş nihai alanın her erişim için x, xkesinlikle erişime önce atanan veya derleme zamanı hatası oluşur edilmelidir.

İçinde int d = d + 1;, erişim dyerel ince ince değişkenine çözümlenir, ancak ddaha önce derişilmediği için derleyici bir hata verir. İçinde int c = c = 1, c = 1önce gerçekleşir, atanır cve ardından cbu atamanın sonucuna başlatılır (1'dir).

Çünkü kesin atama kuralları, yerel değişken bildirimi geldiğini hatırlatırız int d = (d = 1) + d; olacak (başarıyla derlemek aksine saha beyanı int b = (b = 1) + bnedeniyle,) dkesinlikle zaman atanır nihai dulaşılır.


Referanslar için +1, ancak bu ifadeyi yanlış anladığınızı düşünüyorum: "int a = a = 1; (b) 'yi ihlal ettiği için derler", eğer derlemeyeceği 4 gereksinimden herhangi birini ihlal ederse. O Ancak öyle değil IS (burada pek yardımcı olmuyor JLS kelimelerde çift negatif) bir atamanın sol tarafında. In int b = b + 1b ... bu ihlal edecek, böylece atama (değil solda) sağda
M SAM

... Ben çok emin bir değilim Ne şudur: beyanı bu durumda beyan göreve kadar "metin olarak" görünmüyor düşünüyorum göreve kadar metin olarak görünmüyorsa o 4 koşulların yerine getirilmesi gerekir int x = x = 1ki, bunların hiçbiri geçerli olmaz.
msam

@msam: Biraz kafa karıştırıcı, ama temelde ileri bir referans yapmak için dört koşuldan birini ihlal etmeniz gerekiyor. Senin ileri referans Eğer tatmin dört koşullar, bu yasa dışıdır.
nneonneo

@msam: Ayrıca, tam bildirim yalnızca başlatıcıdan sonra yürürlüğe girer.
nneonneo

@mrfishie: Büyük cevap, ancak Java spesifikasyonunda şaşırtıcı miktarda derinlik var. Soru, yüzeyde göründüğü kadar basit değil. (Bir zamanlar bir Java derleyicisi alt kümesi yazmıştım, bu yüzden JLS'nin giriş ve çıkışlarının çoğuna oldukça aşinayım).
nneonneo

86
int x = x = 1;

eşdeğerdir

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

içindeyken

int x = x + 1; 

ilk önce hesaplamamız gerekiyor x+1ama x'in değeri bilinmiyor, bu yüzden bir hata alıyorsunuz (derleyici x'in değerinin bilinmediğini biliyor)


4
Bu artı OpenSauce'un sağ-ilişkilendirilebilirliği ile ilgili ipucu çok yararlı buldum.
TobiMcNamobi

1
Bir atamanın dönüş değerinin değişken değeri değil, atanan değer olduğunu düşündüm.
zzzzBov

2
@zzzzBov doğru. int x = x = 1;eşdeğerdir int x = (x = 1), değil x = 1; x = x; . Bunu yaptığınız için derleyici uyarısı almamalısınız.
nneonneo

int x = x = 1;s operatörün x = (x = 1)sağ çağrışımı nedeniyle int'e eşdeğer=
Grijesh Chauhan

1
@nneonneo ve int x = (x = 1)eşdeğerdir int x; x = 1; x = x;(değişken bildirimi, alan başlatıcının değerlendirilmesi, değişkenin söz konusu değerlendirmenin sonucuna atanması), dolayısıyla uyarı
msam

41

Kabaca şuna eşittir:

int x;
x = 1;
x = 1;

İlk olarak, int <var> = <expression>;her zaman eşdeğerdir

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

Bu durumda, ifadeniz x = 1aynı zamanda bir ifadedir. x = 1var xzaten bildirilmiş olduğundan geçerli bir ifadedir . Aynı zamanda 1 değerine sahip bir ifadedir ve daha sonra xtekrar atanır .


Tamam, ama dediğin gibi gittiyse, neden sınıf kapsamında ikinci ifade bir hata veriyor? Demek istediğim 0, ints için varsayılan değeri elde edersiniz , bu nedenle sonucun undefined reference.
Marcin

@İzogfif cevabına bir göz atın. Çalışmak gibi görünüyor çünkü C ++ derleyicisi değişkenlere varsayılan değerler atar. Java'nın sınıf düzeyindeki değişkenler için yaptığı gibi.
Marcin

@Marcin: Java, ints edilir değil yerel değişkenler olduğunuzda 0'a başlatıldı. Üye değişkenlerse, yalnızca 0 olarak başlatılırlar. Yani ikinci satırınızda x + 1tanımlı bir değer yok çünkü xbaşlatılmamış.
OpenSauce

1
@OpenSauce Ancak x edilir ( "sınıfı kapsamındaki") bir üye değişkeni olarak tanımlanmaktadır.
Jacob Raihle

@JacobRaihle: Ah tamam, o kısmı görmedim. Açık bir başlatma talimatı olduğunu görürse, 0'a bir var'ı başlatmak için bayt kodunun derleyici tarafından oluşturulacağından emin değilim. Burada, sınıf ve nesne başlatmayla ilgili bazı ayrıntılara giren bir makale var, ancak bunun tam olarak bu sorunu ele aldığını
düşünmüyorum

12

Java'da veya herhangi bir modern dilde, atama sağdan gelir.

Varsayalım ki x ve y olmak üzere iki değişkeniniz varsa,

int z = x = y = 5;

Bu ifade geçerlidir ve derleyici onları bu şekilde böler.

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

Ama senin durumunda

int x = x + 1;

Derleyici, böyle bölündüğü için bir istisna verdi.

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

uyarı x = x değil x = 1
Asim Ghaffar

8

int x = x = 1; eşit değildir:

int x;
x = 1;
x = x;

javap yine bize yardımcı oluyor, bunlar bu kod için oluşturulan JVM talimatı:

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

daha çok gibi:

int x = 1;
x = 1;

Tanımlanmamış referans hatası atmak için bir neden yok. Artık, başlatılmadan önce değişken kullanımı vardır, bu nedenle bu kod, spesifikasyona tamamen uygundur. Aslında değişken kullanımı yoktur , sadece atamalar vardır. Ve JIT derleyicisi daha da ileri gidecek, bu tür yapıları ortadan kaldıracaktır. Dürüst olmak gerekirse, bu kodun JLS'nin değişken başlatma ve kullanım spesifikasyonuna nasıl bağlı olduğunu anlamıyorum. Kullanım yok sorun yok. ;)

Lütfen yanılıyorsam düzeltin. Birçok JLS paragrafına atıfta bulunan diğer yanıtların neden bu kadar çok artı topladığını anlayamıyorum. Bu paragrafların bu davayla hiçbir ortak yanı yoktur. Sadece iki seri görev ve daha fazlası yok.

Yazarsak:

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

eşittir:

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

Çoğu ifade, herhangi bir özyineleme olmaksızın değişkenlere tek tek atanır. Değişkenleri istediğimiz gibi karıştırabiliriz:

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

7

İçinde int x = x + 1;1'i x'e eklerseniz, değeri nedir, xhenüz yaratılmadı.

Ama in int x=x=1;hata olmadan derlenecektir çünkü 1'i atarsınız x.


5

İlk kod =parçanız, artı yerine ikinci kod içeriyor . Bu, herhangi bir yerde derlenirken, ikinci kod parçası her iki yerde de derlenmeyecektir.


5

İkinci kod parçasında, x, bildiriminden önce kullanılırken, ilk kod parçasında sadece iki kez atanır, bu anlamsız ama geçerlidir.


5

Adım adım parçalayalım, doğru ilişkisel

int x = x = 1

x = 1, x değişkenine 1 atayın

int x = x, x'in ne olduğunu kendisine int olarak atayın. X önceden 1 olarak atandığından, fazlalık bir şekilde de olsa 1'i tutar.

Bu iyi derler.

int x = x + 1

x + 1, x değişkenine bir ekleyin. Ancak, x tanımsız olduğundan bu bir derleme hatasına neden olur.

int x = x + 1, eşitlerin sağ kısmı atanmamış bir değişkene bir ekleme derlemeyeceğinden, bu satır hataları derler


Hayır, iki =operatör olduğunda doğru ilişkilidir , bu yüzden aynıdır int x = (x = 1);.
Jeppe Stig Nielsen

Ah, siparişlerim iptal. Bunun için üzgünüm. Geriye doğru yapmalıydım. Şimdi değiştirdim.
steventnorris

3

İkincisi int x=x=1, değeri x'e atadığınız için derlemedir, ancak diğer durumda int x=x+1burada x değişkeni başlatılmaz, Java'da yerel değişkenin varsayılan değere başlatılmadığını unutmayın. Not int x=x+1Sınıf kapsamında da ( ) ise, değişken oluşturulmadığı için de derleme hatası verecektir.


2
int x = x + 1;

Visual Studio 2008'de uyarı ile başarıyla derlenir

warning C4700: uninitialized local variable 'x' used`

2
İlgi çekici. C / C ++ mı?
Marcin

@Marcin: evet, C ++. @msam: üzgünüm, sanırım cyerine etiket gördüm javaama görünüşe göre diğer soru buydu .
izogfif

Derler çünkü C ++ 'da derleyiciler ilkel türler için varsayılan değerler atar. Kullan bool y;ve y==trueyanlış döndürür.
Sri Harsha Chilakapati

@SriHarshaChilakapati, C ++ derleyicisinde bir tür standart mı? Çünkü void main() { int x = x + 1; printf("%d ", x); }Visual Studio 2008'de derlediğimde Debug'da istisnayı Run-Time Check Failure #3 - The variable 'x' is being used without being initialized.alıyorum ve Release'de 1896199921konsolda yazdırılan numarayı alıyorum .
izogfif

1
@SriHarshaChilakapati Diğer diller hakkında konuşmak: C # 'da, bir staticalan (sınıf düzeyinde statik değişken) için aynı kurallar geçerlidir. Örneğin public static int x = x + 1;, Visual C # 'da uyarı olmadan derlenir olarak bildirilen bir alan . Java'da muhtemelen aynı mı?
Jeppe Stig Nielsen

2

x, başlatılmamış x = x + 1;.

Java programlama dili statik olarak yazılmıştır, bu da tüm değişkenlerin kullanılmadan önce bildirilmesi gerektiği anlamına gelir.

İlkel veri türlerini görün


3
Değerlerini kullanmadan önce değişkenleri başlatma ihtiyacının statik yazmayla ilgisi yoktur. Statik olarak yazılmış: bir değişkenin ne tür olduğunu belirtmeniz gerekir. Kullanmadan önce başlat: Değeri kullanabilmeniz için kanıtlanabilir bir değere sahip olması gerekir.
Jon Bright

@JonBright: Değişken türlerini bildirme ihtiyacının da statik yazmayla ilgisi yoktur. Örneğin, tür çıkarımına sahip statik olarak yazılmış diller vardır.
hammar

@hammar, benim gördüğüm şekilde, bunu iki şekilde tartışabilirsiniz: tür çıkarımı ile, değişkenin türünü sistemin çıkarabileceği şekilde örtük olarak bildiriyorsunuz. Veya tür çıkarımı, değişkenlerin çalışma zamanında dinamik olarak yazılmadığı, ancak kullanımlarına ve bu şekilde yapılan çıkarımlara bağlı olarak kaynak düzeyinde olduğu üçüncü bir yoldur. Her iki durumda da ifade doğru kalır. Ama haklısın, diğer tip sistemleri düşünmüyordum.
Jon Bright

2

Kodun gerçekte nasıl çalıştığından dolayı kod satırı bir uyarı ile derlenmez. Kodu çalıştırdığınızda int x = x = 1, Java önce xtanımlandığı gibi değişkeni oluşturur . Daha sonra atama kodunu ( x = 1) çalıştırır. Yana xzaten tanımlanmış, sistem ayarını hiçbir hata yok xartık değeri olduğu için, bu döner değeri 1 1 x. Bu nedenle xartık 1 olarak ayarlanmıştır.
Java, temelde kodu şu şekilde çalıştırır:

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

Ancak, kod ikinci parça, int x = x + 1, + 1deyim gerektirir xo zamana ki değil, tanımlanması gerekir. Atama ifadeleri her zaman =ilk çalıştırılan öğesinin sağındaki kod anlamına geldiğinden x, tanımsız olduğu için kod başarısız olacaktır . Java, kodu şu şekilde çalıştırır:

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

-1

Daha kapsamlı ifadeleri sağdan sola doğru okuyun ve tam tersini yapmak için tasarladık. Bu yüzden ilk başta sinirlendi. İfadeleri (kodu) sağdan sola okumak için bunu bir alışkanlık haline getirin, böyle bir problem yaşamayacaksınız.

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.