Yerel değişkenler neden Java'da başlatılmıyor?


104

Java tasarımcılarının yerel değişkenlere varsayılan bir değer verilmemesi gerektiğini düşünmelerinin bir nedeni var mıydı? Cidden, örnek değişkenlerine varsayılan bir değer verilebiliyorsa, neden aynı şeyi yerel değişkenler için yapamıyoruz?

Ayrıca bu yorumda bir blog gönderisinde açıklandığı gibi sorunlara da yol açar :

Bu kural, bir nihayet blokta bir kaynağı kapatmaya çalışırken en sinir bozucudur. Kaynağı bir denemede somutlaştırır, ancak sonunda kapatmaya çalışırsam, bu hatayı alıyorum. Örneği denemenin dışına taşırsam, bir denemenin içinde olması gerektiğini belirten başka bir hata alırım.

Çok sinir bozucu.



1
Bunun için üzgünüm ... soruyu yazarken bu soru görünmedi .. ancak sanırım iki soru arasında bir fark var ... Java tasarımcılarının neden bu şekilde tasarladığını bilmek istiyorum , oysa işaret ettiğiniz soru bunu sormuyor ...
Shivasubramanian A

Ayrıca şu ilgili C # sorusuna bakın: stackoverflow.com/questions/1542824/…
Raedwald

Basitçe - çünkü derleyicinin başlatılmamış yerel değişkenleri izlemesi kolaydır. Diğer değişkenlerle aynı olsaydı, olurdu. Derleyici sadece size yardım etmeye çalışıyor.
rustyx

Yanıtlar:


62

Yerel değişkenler çoğunlukla bazı hesaplamalar yapmak için bildirilir. Bu nedenle, programcının değişkenin değerini belirleme kararıdır ve varsayılan bir değer almamalıdır. Programcı yanlışlıkla yerel bir değişkeni başlatmadıysa ve varsayılan değeri alıyorsa, çıktı beklenmedik bir değer olabilir. Bu nedenle yerel değişkenler söz konusu olduğunda, derleyici programcıdan tanımsız değerlerin kullanılmasını önlemek için değişkene erişmeden önce bir değerle başlatmasını isteyecektir.


23

Bağlandığınız "sorun" bu durumu açıklıyor gibi görünüyor:

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Yorumcunun şikayeti, derleyicinin finallybölümdeki satırda durup sobunun başlatılmamış olabileceğini iddia etmesidir. Daha sonra yorum, kodu yazmanın başka bir yolundan bahseder, muhtemelen şuna benzer:

// Do some work here ...
SomeObject so = new SomeObject();
try {
  so.DoUsefulThings();
} finally {
  so.CleanUp();
}

Yorumcu bu çözümden memnun değildir çünkü derleyici daha sonra kodun "bir deneme dahilinde olması gerektiğini" söyler. Sanırım bu, bazı kodların artık ele alınmayan bir istisna oluşturabileceği anlamına geliyor. Emin değilim. Kodumun her iki versiyonu da herhangi bir istisnayı işlemez, bu nedenle ilk versiyondaki istisnayla ilgili herhangi bir şey ikinci versiyonda da aynı şekilde çalışmalıdır.

Her neyse, kodun bu ikinci versiyonu onu yazmanın doğru yoludur. İlk versiyonda derleyicinin hata mesajı doğruydu. soDeğişken başlatılmamış olabilir. Özellikle, SomeObjectkurucu başarısız olursa, sobaşlatılmayacaktır ve bu nedenle arama girişiminde bulunmak bir hata olacaktır so.CleanUp. Bölümün sonlandırdığı kaynağı aldıktan sonra her zaman trybölümü girin .finally

try- finallysonra blok sobaşlatma orada sadece korumak için SomeObjectolursa olsun başka ne emin temizledik alır hale getirmek için, örneği. Çalıştırılması gereken başka şeyler varsa , ancak bunlar SomeObjectörneğin özelliğin tahsis edilip edilmediğiyle ilgili değilse , o zaman başka bir bloğa gitmelidir try- finallymuhtemelen gösterdiğim bloğu saran bir blok.

Kullanmadan önce değişkenlerin manuel olarak atanmasını zorunlu kılmak gerçek sorunlara yol açmaz. Yalnızca küçük sorunlara yol açar, ancak kodunuz bunun için daha iyi olacaktır. Daha sınırlı kapsamı ile değişkenleri var ve olacak try- finallyçok fazla korumaya çalışmayın blokları.

Yerel değişkenlerin varsayılan değerleri olsaydı, o zaman soilk örnekte olurdu null. Bu gerçekten hiçbir şeyi çözmezdi. finallyBlokta bir derleme zamanı hatası almak yerine , kodun "Burada biraz çalışın" bölümünde meydana gelebilecek diğer istisnaları gizleyebilecek bir NullPointerExceptionpusuda olursunuz . (Ya da bölümlerdeki istisnalar otomatik olarak önceki istisnaya mı zincirlenir? Hatırlamıyorum. Öyle olsa bile, gerçek istisnada fazladan bir istisnaya sahip olursunuz.)finally


2
neden final bloğunda sadece bir if (so! = null) ... olmasın?
izb

bu hala derleyici uyarısına / hatasına neden olacaktır - derleyicinin kontrol ederse anladığını sanmıyorum (ama bunu sadece bellek dışında yapıyorum, test edilmiyor).
Chii

6
Try'dan önce sadece SomeObject so = null koyardım ve nihayet cümlesine null kontrol koyardım. Bu şekilde hiçbir derleyici uyarısı olmayacaktır.
Juha Syrjälä

Neden işleri karmaşıklaştırıyorsun? Try-final bloğunu bu şekilde yazın ve değişkenin geçerli bir değeri olduğunu BİLİYORSUNUZ. Boşluk denetimi gerekmez.
Rob Kennedy

1
Rob, örneğiniz "yeni SomeObject ()" basittir ve orada hiçbir istisna oluşturulmamalıdır, ancak çağrı istisnalar üretebiliyorsa, ele alınabilmesi için try bloğunun içinde olması daha iyi olacaktır.
Sarel Botha

12

Ayrıca, aşağıdaki örnekte, SomeObject yapısının içine bir istisna atılmış olabilir, bu durumda 'so' değişkeni boş olur ve CleanUp çağrısı bir NullPointerException oluşturur.

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Yapmaya meyilli olduğum şey şudur:

SomeObject so = null;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  if (so != null) {
     so.CleanUp(); // safe
  }
}

12
Ne yapmayı tercih edersin?
Electric Monk

2
Evet, çirkin. Evet, ben de öyle yapıyorum.
SMBiggs

@ElectricMonk Hangi formun daha iyi olduğunu düşündüğünüz, gösterdiğiniz veya burada getContents (..) yönteminde gösterilen form: javapractices.com/topic/TopicAction.do?Id=126
Atom

11

Son örnek / üye değişkenlerinin varsayılan olarak başlatılmadığına dikkat edin. Çünkü bunlar kesindir ve sonradan programda değiştirilemez. Java'nın onlar için herhangi bir varsayılan değer vermemesinin ve programcıyı onu başlatmaya zorlamasının nedeni budur.

Öte yandan, nihai olmayan üye değişkenleri daha sonra değiştirilebilir. Bu nedenle, derleyici bunların başlatılmamış kalmasına kesinlikle izin vermez, çünkü bunlar daha sonra değiştirilebilir. Yerel değişkenlerle ilgili olarak, yerel değişkenlerin kapsamı çok daha dardır. Derleyici ne zaman kullanıldığını bilir. Bu nedenle, programcıyı değişkeni başlatmaya zorlamak mantıklıdır.


9

Sorunuzun asıl cevabı, yöntem değişkenlerinin basitçe yığın işaretçisine bir sayı eklenerek somutlaştırılmasıdır. Onları sıfırlamak fazladan bir adım olacaktır. Sınıf değişkenleri için, öbek üzerinde başlatılmış belleğe yerleştirilirler.

Neden fazladan adım atmıyorsunuz? Geri adım atın - Kimse bu durumda "uyarının" Çok İyi Bir Şey olduğundan bahsetmedi.

Değişkeninizi asla ilk geçişte sıfır veya null olarak başlatmamalısınız (onu ilk kodladığınızda). Ya onu gerçek değere atayın ya da hiç atamayın çünkü yapmazsanız java size ne zaman gerçekten hata yaptığınızı söyleyebilir. Electric Monk'un cevabını harika bir örnek olarak ele alalım. İlk durumda, eğer SomeObject'in kurucusu bir istisna attığı için try () başarısız olursa, sonunda sonunda bir NPE ile sonuçlanacağını söylemesi şaşırtıcı derecede yararlıdır. Yapıcı bir istisna atamazsa, denemede olmamalıdır.

Bu uyarı, her yolu kontrol ettiği ve değişkeni herhangi bir yolda kullandıysanız, ona giden her yolda onu başlatmanız gerektiğinden emin olduğu için beni aptalca şeyler yapmaktan kurtaran harika bir çok yollu kötü programcı denetleyicisidir. . Yapılacak doğru şey olduğunu belirleyene kadar değişkenleri asla açık bir şekilde başlatmam.

Üstelik, "int size" yerine açıkça "int size = 0" demek ve bir sonraki programcının bunun sıfır olmasını istediğinizi anlamasını sağlamak daha iyi değil mi?

Kapak tarafında, derleyicinin tüm başlatılmamış değişkenleri 0 olarak başlatması için tek bir geçerli neden bulamıyorum.


1
Evet ve kodun akış biçimi nedeniyle aşağı yukarı sıfırlanması gereken başkaları da var - cevabı bunu yansıtacak şekilde "asla" güncellememeliydim.
Bill K

4

Bence birincil amaç C / C ++ ile benzerliği korumaktı. Ancak, derleyici, sorunu en aza indirecek olan başlatılmamış değişkenlerin kullanılması konusunda sizi algılar ve uyarır. Performans açısından bakıldığında, bir sonraki ifadede değişkenin değerinin üzerine yazsanız bile derleyicinin bir atama ifadesi yazmak zorunda kalmayacağından, başlatılmamış değişkenleri bildirmenize izin vermek biraz daha hızlıdır.


1
Muhtemelen, derleyici, değişkenle herhangi bir şey yapmadan önce her zaman değişkene atama yapıp yapmadığınızı belirleyebilir ve böyle bir durumda otomatik bir varsayılan değer atamasını bastırabilir. Derleyici, bir atamanın erişimden önce olup olmadığını belirleyemezse, varsayılan atamayı oluşturur.
Greg Hewgill

2
Evet, ancak programcının değişkeni yanlışlıkla başlatılmamış olarak bırakıp bırakmadığını bilmesini sağladığı ileri sürülebilir.
Mehrdad Afshari

1
Derleyici bunu her iki durumda da yapabilir. :) Kişisel olarak, derleyicinin başlatılmamış bir değişkeni bir hata olarak değerlendirmesini tercih ederim. Bir yerde hata yapmış olabileceğim anlamına geliyor.
Greg Hewgill

Java adamı değilim, ancak C # ile başa çıkma şeklini seviyorum. Aradaki fark, bu durumda, derleyicinin bir uyarı vermesi gerekiyordu, bu da doğru programınız için birkaç yüz uyarı almanıza neden olabilir;)
Mehrdad Afshari

Üye değişkenler için de uyarıyor mu?
Adeel Ansari

4

(Sorudan bu kadar uzun süre sonra yeni bir cevap göndermek tuhaf görünebilir, ancak bir kopya çıktı.)

Benim için sebep şuna geliyor: Yerel değişkenlerin amacı, örnek değişkenlerin amacından farklıdır. Yerel değişkenler, bir hesaplamanın parçası olarak kullanılmak içindir; örnek değişkenler durumu içermelidir. Yerel bir değişkeni bir değer atamadan kullanırsanız, bu neredeyse kesinlikle bir mantık hatasıdır.

Bununla birlikte, örnek değişkenlerinin her zaman açıkça başlatılmış olmasını zorunlu kılarak tamamen geride kalabildim; hata, sonucun başlatılmamış bir örnek değişkenine izin verdiği herhangi bir kurucuda ortaya çıkabilir (örneğin, yapıcıda değil, bildirimde başlatılmamış). Ama bu karar değil Gosling, et. al., 90'ların başında aldı, işte buradayız. (Ve yanlış arama yaptıklarını söylemiyorum.)

Ben olabilir değil de, yerel değişkenler varsaymak arkasında olsun. Evet, mantığımızı iki kez kontrol etmek için derleyicilere güvenmemeliyiz ve biri yapmaz, ancak derleyici birini yakaladığında yine de kullanışlıdır. :-)


"Bununla birlikte, örnek değişkenlerinin her zaman açıkça başlatılmış olmasını zorunlu kılarak tamamen geride kalabildim ..." ki bu, FWIW, TypeScript'te aldıkları yöndür.
TJ Crowder

3

Değişkenleri başlatmamak daha etkilidir ve yerel değişkenler söz konusu olduğunda bunu yapmak güvenlidir çünkü başlatma derleyici tarafından izlenebilir.

Bir değişkenin başlatılmasına ihtiyaç duyduğunuz durumlarda, bunu her zaman kendiniz yapabilirsiniz, dolayısıyla bu bir problem değildir.


3

Yerel değişkenlerin arkasındaki fikir, yalnızca ihtiyaç duydukları sınırlı kapsam içinde var olmalarıdır. Bu nedenle, değere ilişkin belirsizlik için çok az neden olmalıdır veya en azından bu değerin nereden geldiği. Yerel değişkenler için varsayılan bir değere sahip olmaktan kaynaklanan birçok hatayı hayal edebiliyorum.

Örneğin, aşağıdaki basit kodu göz önünde bulundurun ... ( NB, açık bir şekilde başlatılmamışsa, yerel değişkenlere belirtildiği gibi varsayılan bir değer atandığını gösterme amacıyla varsayalım )

System.out.println("Enter grade");
int grade = new Scanner(System.in).nextInt(); //I won't bother with exception handling here, to cut down on lines.
char letterGrade; //let us assume the default value for a char is '\0'
if (grade >= 90)
    letterGrade = 'A';
else if (grade >= 80)
    letterGrade = 'B';
else if (grade >= 70)
    letterGrade = 'C';
else if (grade >= 60)
    letterGrade = 'D';
else
    letterGrade = 'F';
System.out.println("Your grade is " + letterGrade);

Her şey söylendiğinde ve yapıldığında, derleyicinin letterGrade'e varsayılan bir '\ 0' değeri atadığını varsayarak , bu kod yazıldığı şekliyle düzgün çalışacaktır. Ancak, ya else ifadesini unutursak?

Kodumuzun bir test çalıştırması aşağıdakilere neden olabilir:

Enter grade
43
Your grade is

Bu sonuç, bekleneceği gibi, kesinlikle kodlayıcının niyeti değildi. Aslında, muhtemelen çoğu durumda (veya en azından önemli bir sayıda) varsayılan değer istenen değer olmayacaktır , bu nedenle çoğu durumda varsayılan değer hatayla sonuçlanacaktır. Bu unutulması neden ayıklama acı için, kullanmadan önce yerel bir değişken bir başlangıç değeri atamak için kodlayıcı zorlamak için daha mantıklı = 1olarak for(int i = 1; i < 10; i++)uzakta bulunmaktadır zorunda değil kolaylık ağır basar = 0içinde for(int i; i < 10; i++).

Try-catch-nihayet bloklarının biraz dağınık olabileceği doğrudur (ama alıntının önerdiği gibi aslında bir catch-22 değildir), örneğin bir nesne yapıcısına kontrol edilmiş bir istisna attığında, ancak biri için sebep veya başka bir şey , sonunda bloğun sonunda bu nesneye bir şey yapılmalıdır . Bunun mükemmel bir örneği, kapatılması gereken kaynaklarla uğraşırken verilebilir.

Bunu geçmişte halletmenin bir yolu böyle olabilirdi ...

Scanner s = null; //declared and initialized to null outside the block. This gives us the needed scope, and an initial value.
try {
    s = new Scanner(new FileInputStream(new File("filename.txt")));
    int someInt = s.nextInt();
} catch (InputMismatchException e) {
    System.out.println("Some error message");
} catch (IOException e) {
    System.out.println("different error message"); 
} finally {
    if (s != null) //in case exception during initialization prevents assignment of new non-null value to s.
        s.close();
}

Bununla birlikte, Java 7'den itibaren, bu nihayet bloğu artık böyle kaynaklarla deneme kullanıldığında gerekli değildir.

try (Scanner s = new Scanner(new FileInputStream(new File("filename.txt")))) {
...
...
} catch(IOException e) {
    System.out.println("different error message");
}

Bununla birlikte, (adından da anlaşılacağı gibi) bu yalnızca kaynaklarla çalışır.

Ve önceki örnek biraz iğrenç olsa da, bu belki de, yerel değişkenler ve bunların nasıl uygulandıklarından ziyade bu sınıfların uygulandığı yolu ya da dene-yakala biçiminden daha çok bahsediyor.

Alanların varsayılan bir değerle başlatıldığı doğrudur, ancak bu biraz farklıdır. Örneğin, int[] arr = new int[10];bu diziyi başlatır başlatmaz dediğinizde, nesne belirli bir konumda bellekte var olur. Bir an için varsayılan değerlerin olmadığını varsayalım, ancak bunun yerine başlangıç ​​değeri, o anda o bellek konumunda bulunan 1 ve 0 serisi ne olursa olsun. Bu, bazı durumlarda deterministik olmayan davranışlara yol açabilir.

Varsayalım ki ...

int[] arr = new int[10];
if(arr[0] == 0)
    System.out.println("Same.");
else
    System.out.println("Not same.");

Same.Bir çalışmada Not same.gösterilebilmesi ve bir başkasında gösterilmesi tamamen mümkündür . Referans değişkenlerden bahsetmeye başladığınızda sorun daha da ağır hale gelebilir.

String[] s = new String[5];

Tanıma göre, s'nin her bir öğesi bir String'i işaret etmelidir (veya boştur). Bununla birlikte, başlangıç ​​değeri, bu bellek konumunda meydana gelen 0 ve 1 serileriyse, yalnızca her seferinde aynı sonuçları alacağınızın garantisi değil, aynı zamanda nesnenin s [0] puan alacağının garantisi de yoktur. hatta (bu, herhangi anlamlı işaret varsayarak) olan bir dize (belki de bir Tavşan, var : p )! Yazı tipine yönelik bu endişe eksikliği, Java Java'yı yapan hemen hemen her şeyin karşısında uçacaktır. Bu nedenle, yerel değişkenler için varsayılan değerlere sahip olmak en iyi ihtimalle isteğe bağlı olarak görülebilirken, örnek değişkenler için varsayılan değerlere sahip olmak bir zorunluluktur .


1

yanılmıyorsam başka bir sebep olabilir

Üye değişkenlerin varsayılan değerinin verilmesi, sınıf yüklemesinin bir parçasıdır

Java'da sınıf yükleme bir çalışma zamanıdır, bir nesne oluşturduğunuzda sınıfın yüklendiği anlamına gelir, yalnızca üye değişkenler varsayılan değerle başlatılır JVM, yerel değişkenlerinize varsayılan değer vermek için zaman almaz çünkü bazı yöntemler asla olmayacaktır. yöntem çağrısı koşullu olabileceğinden, bu varsayılanlar asla kullanılmayacaksa neden onlara varsayılan değeri vermek ve performansı düşürmek için zaman ayırın?


0

Eclipse, size başlatılmamış değişkenler için uyarılar bile verir, bu yüzden yine de oldukça açık hale gelir. Kişisel olarak, bunun varsayılan davranış olmasının iyi bir şey olduğunu düşünüyorum, aksi takdirde uygulamanız beklenmedik değerler kullanabilir ve derleyicinin bir hata atması yerine hiçbir şey yapmaz (ama belki bir uyarı verir) ve sonra kaşınıyorsunuz bazı şeylerin neden olması gerektiği gibi davranmadığı konusunda kafanıza.


0

Yerel değişkenler bir yığın üzerinde depolanır, ancak örnek değişkenleri yığın üzerinde depolanır, bu nedenle yığında olduğu gibi, yığındaki varsayılan bir değer yerine yığındaki önceki bir değerin okunması ihtimali vardır. Bu nedenle jvm, onu başlatmadan yerel bir değişkeni kullanmaya izin vermez.


2

Java 7'den önce, örnek değişkenleri yığın üzerinde depolanır ve yığın üzerinde yerel değişkenler bulunur. Ancak, yerel bir değişkenin başvurduğu herhangi bir nesne öbek içinde bulunacaktır. Java 7'den itibaren, "Java Hotspot Sunucu Derleyicisi" "kaçış analizi" gerçekleştirebilir ve yığın yerine yığın üzerinde bazı nesneleri tahsis etmeye karar verebilir.
2013

0

Örnek değişkeni varsayılan değerlere sahip olacaktır, ancak yerel değişkenler varsayılan değerlere sahip olamaz. Yerel değişkenler temelde yöntem / davranış içinde olduğundan, temel amacı bazı işlemler veya hesaplamalar yapmaktır. Bu nedenle, yerel değişkenler için varsayılan değerler ayarlamak iyi bir fikir değildir. Aksi takdirde beklenmedik yanıtların nedenlerini kontrol etmek çok zor ve zaman alıcıdır.


-1

Cevap, örnek değişkenlerinin sınıf yapıcıda veya herhangi bir sınıf yönteminde başlatılabileceğidir, ancak yerel değişkenler söz konusu olduğunda, yöntemde sonsuza kadar kalan her şeyi tanımladığınızda.


-2

2 nedeni takip etmeyi düşünebilirim

  1. Yanıtların çoğunun söylediği gibi yerel değişkeni başlatma kısıtlaması getirilerek, yerel değişkene programcının istediği bir değer atanması ve beklenen sonuçların hesaplanmasının sağlanması sağlanır.
  2. Örnek değişkenler, yerel değişkenler (aynı ad) bildirilerek gizlenebilir - beklenen davranışı sağlamak için, yerel değişkenler bir değer başlatılmaya zorlanır. (Yine de bundan tamamen kaçınırdı)

Alanlar geçersiz kılınamaz. En fazla, gizlenebilirler ve gizlemenin başlatma kontrolüne nasıl müdahale edeceğini göremiyorum?
meriton

Sağa gizli: Biri örnekle aynı ada sahip yerel değişken oluşturmaya karar verirse, bu kısıtlama nedeniyle, yerel değişken önceden seçilmiş bir değerle başlatılır (örnek değişkeninin değeri dışında)
Mitra
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.