Java ile Nesne Yönelimli Programlama · Ders Notu
Bölüm 3.1: Kontrol Yapıları
Bir program yalnız yukarıdan aşağıya akan satırlardan oluşsaydı her çalıştırmada aynı işi aynı sırayla yapardı. Kullanıcının girdiğine göre farklı davranamaz, bir işi bin kez yapmak için de bin satır yazmak gerekirdi. Kontrol yapıları bu iki eksiği giderir. Karar yapıları bir koşula bakarak iki ya da daha çok yoldan birini seçer. Döngüler bir deyim grubunu koşul sağlandıkça tekrarlar. Önce sıralı akış, ardından if, if-else, else if zinciri, iç içe kararlar ve switch yapısı işlenir. Sonra for, while ve do-while döngüleri, iç içe döngüler ve akışı döngünün ortasında değiştiren break ile continue gelir. Her yapı için kod satır satır izlenir, değişkenlerin değerleri tabloyla gösterilir ve Java derleyicisinin gerçek hata iletileri verilir. Kodların hepsi JDK 21 ile çalıştırılmış, çıktılar bu çalıştırmalardan alınmıştır.
Öğrenme çıktıları
- Akış kontrolü
- Sıralı işlem, karar ve döngü yapılarının programın akışını nasıl yönlendirdiğini açıklamak.
- Karar yapıları
if,if-else,else ifzinciri, iç içe kararlar ve&&,||ile birleşik koşullarla koşula bağlı kod yazmak; kısa devre değerlendirmenin sonucunu önceden bulmak.- switch-case
- Bir değeri sabitlerle karşılaştırmak;
break'in görevini ve aşağı düşme davranışını açıklamak; klasikswitchile ok biçimliswitchifadesini ayırt etmek. - Döngüler
for,whilevedo-whiledöngülerini doğru yerde seçmek; sonsuz döngü ve sınır hatalarından kaçınmak.- İç içe döngüler
- Desen, tablo ve basamak örneklerinde iç içe döngülerin kaç kez döndüğünü hesaplamak.
- İzleme
- Bir programı adım adım izleyerek değişken değerlerini ve ekran çıktısını önceden bulmak;
breakvecontinue'nun etkisini göstermek.
3.1.1Kontrol yapıları
Java kaynak kodu deyimlerden oluşur. Bir değişken tanımı, bir atama ya da bir println çağrısı birer deyimdir ve sonları noktalı virgülle biter. Derleyici bu deyimleri yazıldıkları sırayla bayt koduna çevirir. Çalışma sırasında ise hangi deyimin ne zaman, kaç kez çalışacağını kontrol yapıları belirler.
Kontrol yapısı (control structure), deyimlerin hangi sırayla ve kaç kez çalışacağını belirleyen dil yapısıdır. Programın bu sıraya göre izlediği yola akış denir.
3.1.1.1Üç temel yapı
Java'da üç temel kontrol yapısı vardır (Şekil 3.1.1). Sıralı işlemlerde deyimler yukarıdan aşağıya, birer kez çalışır. Karar yapılarında bir koşula bakılır ve iki ya da daha çok yoldan biri seçilir; Java'daki karşılıkları if-else ve switch'tir. Döngülerde koşul sağlandıkça aynı deyim grubu tekrarlanır; Java'da for, while ve do-while döngüleri bulunur.
Bu üç yapı küçük görünse de bilinen her algoritmayı yazmaya yeter. Bu sonuç, Böhm ile Jacopini'nin 1966'da gösterdiği yapısal program kuramına dayanır: sıra, seçim ve tekrar bir araya getirilerek her hesaplanabilir iş ifade edilebilir. Yapılar birbirinin içine de yerleştirilebilir. Bir döngünün gövdesinde karar, bir kararın kolunda döngü bulunabilir. Gerçek programlar bu iç içe yerleşimin sonucudur.
Kararlar programa esneklik kazandırır. Aynı program farklı girdilere farklı cevap verir: negatif sayıyı pozitife çevirir, 85 alan öğrenciye B, 45 alana F yazar. Döngüler ise tekrar eden işleri kısaltır. 1'den 1.000.000'a kadar sayıları toplamak için bir milyon satır değil, üç satırlık bir döngü yeter. Kod hem kısalır hem de değiştirmesi kolaylaşır: sınırı 1.000.000'dan 2.000.000'a çıkarmak için yalnız bir sayı değişir.
3.1.1.2Sıralı işlemler
Kontrol yapısı içermeyen her program sıralıdır. Kod yukarıdan aşağıya satır satır çalışır ve bir satır bitmeden sonraki başlamaz. Aşağıdaki dört satır bu düzenin en küçük örneğidir:
int x = 5;
int y = 10;
int sonuc = x + y;
System.out.println(sonuc);İlk satır x adlı bir int değişken ayırır ve içine 5 yazar. İkinci satır y için aynısını 10 ile yapar. Üçüncü satır önceki iki satırın sonuçlarını okur, 5 + 10 = 15'i hesaplar ve sonuc değişkenine yazar. Son satır bu değeri ekrana basar. Tablo 3.1.1, her satırdan sonra bellekte hangi değişkenlerin bulunduğunu gösterir. Boş hücre, değişkenin henüz tanımlanmadığı anlamına gelir.
| Satır | Deyim | x | y | sonuc | Ekran |
|---|---|---|---|---|---|
| 1 | int x = 5; | 5 | |||
| 2 | int y = 10; | 5 | 10 | ||
| 3 | int sonuc = x + y; | 5 | 10 | 15 | |
| 4 | System.out.println(sonuc); | 5 | 10 | 15 | 15 |
sonuc kutusundaki 15, önceki iki satırın yazdığı değerlerden hesaplanmıştır.Sıranın önemi bir deneyle görülür. Üçüncü satır en başa alınsın: int sonuc = x + y; satırı çalıştığında x ve y henüz tanımlı değildir. Java bu durumda programı hiç derlemez ve cannot find symbol iletisini verir. Sıralı yapıda her satır, kendisinden önceki satırların bıraktığı duruma dayanır.
Değişkene değer vermeden okumak. int sonuc; satırından hemen sonra System.out.println(sonuc); yazılırsa derleyici variable sonuc might not have been initialized hatası verir. Java, yerel bir değişkenin okunmadan önce mutlaka bir değer aldığını derleme sırasında denetler. Bu denetim, değişkene yalnız bazı kollarda değer veren karar yapılarında özellikle önem kazanır.
3.1.2if ile karar verme
Karar verme, programın hangi yolu izleyeceğini çalışma sırasında seçmesidir. Seçim derleme sırasında yapılamaz, çünkü çoğu zaman girdiye bağlıdır: kullanıcının yazdığı sayı, okunan dosyanın içeriği, bir sensörden gelen ölçüm. Gündelik hayattaki karşılığı yol ayrımındaki bir tabeladır: “Hava yağmurluysa şemsiye al, değilse alma.” Java'da kararlar if-else ve switch ile yazılır. Bu kesim if ailesini, sonraki kesimler çok yollu kararları ve switch'i işler.
3.1.2.1Koşul ve karşılaştırma operatörleri
Koşul (condition), sonucu true ya da false olan mantıksal bir ifadedir (boolean expression). Java'da koşulun tipi her zaman boolean'dır.
Koşulların çoğu iki değerin karşılaştırılmasıyla kurulur. Java'nın altı karşılaştırma operatörü Tablo 3.1.2'de, x = 7 için örnekleriyle verilmiştir.
| Operatör | Anlamı | Örnek | Sonuç |
|---|---|---|---|
== | eşit mi | x == 7 | true |
!= | eşit değil mi | x != 7 | false |
< | küçük mü | x < 10 | true |
> | büyük mü | x > 10 | false |
<= | küçük ya da eşit mi | x <= 7 | true |
>= | büyük ya da eşit mi | x >= 8 | false |
İki karakterli operatörlerin arasına boşluk girmez: < = yazılırsa derleyici bunu iki ayrı simge olarak okur ve hata verir. Matematikteki ≤, ≥ ve ≠ işaretlerinin Java'da karşılığı yoktur; yerlerine <=, >= ve != kullanılır.
En önemli ayrım = ile == arasındadır. Tek eşittir atamadır: sağdaki değeri soldaki değişkene yazar. Çift eşittir karşılaştırmadır: iki değerin eşit olup olmadığını sorar ve bir boolean üretir. C dilinde if (x = 7) derlenir ve her zaman doğru sayılır; bu yüzden C'de çok sinsi bir hata kaynağıdır. Java ise koşulun boolean olmasını şart koşar. x = 7 ifadesinin değeri int tipinde 7 olduğu için program derlenmez:
error: incompatible types: int cannot be converted to boolean
if (x = 7) {
^Bu kural yalnız int gibi sayısal değişkenleri korur. Değişkenin kendisi boolean ise if (bitti = true) derlenir, bitti'ye true yazar ve koşul hep doğru çıkar. Bu tuzaktan kaçmanın en kolay yolu boolean bir değişkeni hiçbir şeyle karşılaştırmamaktır: if (bitti) ve if (!bitti) yeterlidir.
3.1.2.2if-else yapısı
if deyimi (if statement), koşul doğruysa bir bloğu çalıştıran karar deyimidir. İsteğe bağlı else kolu, koşul yanlış olduğunda çalışacak bloğu verir.
if (koşul) {
// Koşul doğruysa burası çalışır
} else {
// Koşul yanlışsa burası çalışır
}Yapının işleyişi üç adımdır. Önce parantez içindeki koşul değerlendirilir. Koşul true ise if bloğu çalışır ve else bloğu atlanır. Koşul false ise if bloğu atlanır ve else bloğu çalışır. Her iki durumda da akış yapının sonunda birleşir ve sonraki deyimle devam eder (Şekil 3.1.3). İki bloktan en fazla biri çalışır; else varsa tam olarak biri çalışır.
if-else akış diyagramı. Koşul doğruysa (E) if bloğu, yanlışsa (H) else bloğu çalışır; iki yol birleşir ve sonraki deyime geçilir.Yazımda iki kural vardır. Koşul her zaman parantez içinde yazılır: if koşul derlenmez, if (koşul) derlenir. Bloklar süslü parantezle açılıp kapanır. Süslü parantezler arasındaki deyim grubuna blok denir. Bir blok içinde tanımlanan değişken yalnız o blokta yaşar; bloktan çıkınca adı da kaybolur.
3.1.2.3Yalnız if ve süslü parantezler
else kolu isteğe bağlıdır. Koşul yanlış olduğunda yapılacak özel bir iş yoksa yalnız if yazılır:
int sicaklik = 35;
if (sicaklik > 30) {
System.out.println("Hava sıcak!");
}
System.out.println("Program devam ediyor.");Hava sıcak!
Program devam ediyor.sicaklik 35 olduğu için 35 > 30 doğrudur ve blok çalışır. Ardından if'ten sonraki deyime geçilir. Sıcaklık 20 olsaydı koşul yanlış olur, blok atlanır ve ekranda yalnız “Program devam ediyor.” görünürdü. if yapısının dışındaki deyim, koşulun sonucundan bağımsız olarak her durumda çalışır.
Süslü parantezler, blokta tek deyim olduğunda yazılmayabilir. Java'nın kuralı şudur: parantez yoksa if'e yalnız ilk deyim bağlıdır. Girinti, yani satır başındaki boşluklar, Java için hiçbir anlam taşımaz. Python gibi girintiyle blok kuran dillere alışmış biri için bu durum aşağıdaki tuzağı doğurur:
int sicaklik = 35;
if (sicaklik > 40)
System.out.println("Çok sıcak!");
System.out.println("Bol su için.");Bol su için.35 > 40 yanlıştır, bu yüzden “Çok sıcak!” yazılmaz. “Bol su için.” satırı ise iki satırın aynı hizada durmasına rağmen if'in parçası değildir. Derleyicinin gördüğü yapı şudur: if (sicaklik > 40) println("Çok sıcak!"); ve ardından ayrı, koşulsuz bir println("Bol su için.");. Programcının niyeti iki satırı birlikte koşula bağlamaksa bunu yalnız süslü parantezler sağlar. Bu nedenle tek satırlık bloklarda bile { } kullanmak iyi bir alışkanlıktır. Sonradan bloğa ikinci bir satır eklendiğinde aynı hata yeniden doğmaz.
Koşuldan sonra noktalı virgül koymak. if (x > 5); { … } derlenir, ama noktalı virgül tek başına boş deyimdir. Derleyici bunu “koşul doğruysa hiçbir şey yapma” diye okur. Arkasından gelen blok if'e bağlı değildir ve her durumda çalışır. x = 3 ile denendiğinde if (x > 5); { System.out.println("x beşten büyük"); } yine “x beşten büyük” yazar. Derleyici bu satır için uyarı da vermez.
3.1.2.4Yaş kontrolü
İlk tam karar örneği, ehliyet yaşını denetleyen beş satırlık bir koddur:
int yas = 18;
if (yas >= 18) {
System.out.println("Ehliyet alabilirsiniz.");
} else {
System.out.println("Ehliyet alamazsınız.");
}Birinci satır yas değişkenine 18 atar. İkinci satırda koşul değerlendirilir: 18 >= 18 doğrudur, çünkü >= eşitliği de kapsar. Akış diyagramında bu, evet (E) koluna gitmek demektir (Şekil 3.1.4). Üçüncü satır çalışır ve “Ehliyet alabilirsiniz.” yazılır. else bloğu, yani beşinci satır atlanır.
yas = 18 için koşul doğrudur ve akış kalın çizilen E koluna gider.yas | yas >= 18 | Çalışan blok | Ekran |
|---|---|---|---|
| 16 | false | else | Ehliyet alamazsınız. |
| 18 | true | if | Ehliyet alabilirsiniz. |
| 25 | true | if | Ehliyet alabilirsiniz. |
Tablodaki 18, bir sınır değerdir: koşulun doğrudan yanlışa geçtiği nokta. Kararlı kodlar sınırın iki yanında ve tam üstünde denenmelidir. Koşul yanlışlıkla yas > 18 yazılsaydı 16 ve 25 doğru sonuç verir, yalnız 18 yanlış sonuç verirdi. Bu tür bir hata yalnız sınır değeriyle yapılan denemede ortaya çıkar.
3.1.2.5Mutlak değer
İkinci örnek kullanıcıdan bir ondalıklı sayı okur ve mutlak değerini yazar. Bir sayının mutlak değeri, sayı negatifse işaret değiştirilmiş hâli, değilse kendisidir: |−7,5| = 7,5 ve |3| = 3.
Scanner scanner = new Scanner(System.in);
System.out.print("Bir sayı girin: ");
double sayi = scanner.nextDouble();
double mutlakDeger;
if (sayi < 0) {
mutlakDeger = -sayi;
} else {
mutlakDeger = sayi;
}
System.out.println("Mutlak değer: " + mutlakDeger);Kod dosyanın başında import java.util.Scanner; satırı olduğunu varsayar. Birinci satır klavyeden okumak için bir Scanner nesnesi oluşturur. İkinci satır kullanıcıya ne gireceğini söyler. Burada println değil print kullanılmıştır: print satır atlamaz ve imleç sorunun hemen yanında bekler. Üçüncü satırda nextDouble() kullanıcının yazdığı sayıyı okur.
Dördüncü satır mutlakDeger değişkenini tanımlar ama değer vermez. Değeri aşağıdaki if-else verecektir. Java bu satırı kabul eder, çünkü iki kolun her biri değişkene bir değer yazar. Derleyici bunu denetleyebilir: hangi kol çalışırsa çalışsın, son satıra gelindiğinde değişken mutlaka bir değer almıştır. else kolu silinseydi derleyici son satır için variable mutlakDeger might not have been initialized hatası verirdi.
sayi < 0 doğrudur; işaretlenen kutuda mutlakDeger = -sayi çalışır.Kullanıcı −7,5 girsin. Beşinci satırda -7.5 < 0 doğrudur. Altıncı satırda -sayi ifadesi −(−7,5) = 7,5'i hesaplar ve mutlakDeger'e yazar. else bloğu atlanır. Son satır Mutlak değer: 7.5 yazar. Tablo 3.1.4 aynı programın dört girdiyle çalıştırılmasını özetler.
| Klavyeden | sayi | sayi < 0 | Ekran |
|---|---|---|---|
| -7,5 | −7.5 | true | Mutlak değer: 7.5 |
| 7,5 | 7.5 | false | Mutlak değer: 7.5 |
| 0 | 0.0 | false | Mutlak değer: 0.0 |
| -7.5 | — | — | InputMismatchException |
Son satır Java'ya özgü bir ayrıntıdır. Scanner, sayıları bilgisayarın bölge ayarına göre okur. Türkçe ayarda ondalık ayırıcı virgüldür. Bu yüzden -7,5 kabul edilir, -7.5 ise sayı sayılmaz ve program şu iletiyle durur:
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:947)
at java.base/java.util.Scanner.next(Scanner.java:1602)
at java.base/java.util.Scanner.nextDouble(Scanner.java:2573)Okuma bölge ayarına uyar, yazma ise uymaz. println bir double değeri her zaman noktayla yazar: 7.5. Aynı programda girişte virgül, çıkışta nokta görülmesinin nedeni budur. Okumanın da noktayla yapılması isteniyorsa Scanner oluşturulduktan sonra scanner.useLocale(java.util.Locale.US) çağrılır.
3.1.3Çok yollu kararlar
Bir if-else yapısı iki yol arasında seçim yapar. Gerçek problemlerde çoğu zaman daha fazla seçenek vardır. Bir sayı pozitif, negatif ya da sıfır olabilir; bir not A, B, C, D ya da F'ye karşılık gelebilir. Java bu durumlar için iki araç sunar: koşulları art arda dizen else if zinciri ve kararları birbirinin içine yerleştiren iç içe yapı. Kesimin sonunda birden çok koşulu tek bir ifadede birleştiren && ve || operatörleri ile koşul operatörü ?: işlenir.
3.1.3.1else-if zinciri
else-if zinciri (else-if chain), koşulları yukarıdan aşağıya sırayla deneyip ilk doğru koşulun bloğunu çalıştıran yapıdır. Hiçbir koşul doğru değilse, varsa, en sondaki else bloğu çalışır.
Aşağıdaki tam program bir sayının işaretini bulur. Üç olası sonuç, iki koşul ve bir son else ile ayrılır:
import java.util.Scanner;
public class PozitifNegatif {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Bir sayı girin: ");
double sayi = scanner.nextDouble();
if (sayi > 0) {
System.out.println("Sayı pozitif.");
} else if (sayi < 0) {
System.out.println("Sayı negatif.");
} else {
System.out.println("Sayı sıfır.");
}
}
}Program üç ayrı girdiyle çalıştırıldığında şu çıktıları verir:
Bir sayı girin: 4
Sayı pozitif.Bir sayı girin: -2
Sayı negatif.Bir sayı girin: 0
Sayı sıfır.4 girildiğinde ilk koşul 4 > 0 doğrudur. “Sayı pozitif.” yazılır ve zincirin geri kalanına hiç bakılmaz. −2 girildiğinde ilk koşul yanlıştır, akış ikinci koşula geçer: -2 < 0 doğrudur ve “Sayı negatif.” yazılır. 0 girildiğinde iki koşul da yanlıştır ve son else çalışır. Üç kolun hiçbiri aynı anda çalışamaz: bir kol seçildiği anda zincir biter.
else if Java'da ayrı bir anahtar sözcük değildir. Yapı, bir else kolunun içine yazılmış yeni bir if'ten ibarettir. Programın derleyicinin gözündeki biçimi şöyledir: if (sayi > 0) { … } else { if (sayi < 0) { … } else { … } }. Programcılar bunu daha okunaklı olduğu için tek düzeyde, alt alta yazar. Zincirin her halkası, kendinden önceki bütün koşulların yanlış olduğu bilinerek değerlendirilir. İkinci halkadaki sayi < 0 koşuluna yalnız pozitif olmayan sayılar ulaşır.
3.1.3.2Harf notu zincirinin izlenmesi
Zincirin sırayı nasıl kullandığını en iyi harf notu örneği gösterir. Sayısal not 90 ve üstündeyse A, 80–89 arasında B, 70–79 arasında C, 60–69 arasında D, 60'ın altında F verilir.
int not = 75;
if (not >= 90) {
System.out.println("A");
} else if (not >= 80) {
System.out.println("B");
} else if (not >= 70) {
System.out.println("C");
} else if (not >= 60) {
System.out.println("D");
} else {
System.out.println("F");
}not = 75 için akış şöyledir. İlk koşul 75 >= 90 yanlıştır ve akış sonraki else if'e geçer. 75 >= 80 da yanlıştır. 75 >= 70 doğrudur: ilk doğru koşul bulunmuştur. “C” yazılır. Kalan koşul, yani not >= 60, hiç sınanmaz (Şekil 3.1.6).
else-if zincirinin akış diyagramı, not = 75. İlk iki elmasta H koluna gidilir; üçüncü elmasta (not ≥ 70) koşul doğrudur ve “Yaz C” kutusuna geçilir.not | >= 90 | >= 80 | >= 70 | >= 60 | Sınanan koşul | Ekran |
|---|---|---|---|---|---|---|
| 95 | doğru | — | — | — | 1 | A |
| 80 | yanlış | doğru | — | — | 2 | B |
| 75 | yanlış | yanlış | doğru | — | 3 | C |
| 60 | yanlış | yanlış | yanlış | doğru | 4 | D |
| 45 | yanlış | yanlış | yanlış | yanlış | 4 | F |
Koşullardaki not < 90 gibi üst sınırların yazılmamış olmasına dikkat edilmelidir. else if (not >= 80) halkasına ancak not >= 90 yanlışsa gelinir; demek ki bu noktada not zaten 90'dan küçüktür. Zincir, üst sınırı sıranın kendisinden alır. Bu yüzden koşulların sırası önemlidir. Aynı dört koşul ters sırayla yazılsın:
if (not >= 60) {
harf = "D";
} else if (not >= 70) {
harf = "C";
} else if (not >= 90) {
harf = "A";
} else {
harf = "F";
}Bu kod 95 için de, 75 için de “D” üretir. 60 ve üstündeki her sayı ilk koşulu sağlar ve zincir ilk halkada biter. not >= 90 halkasına hiçbir değer ulaşamaz. Derleyici bu hatayı yakalamaz, çünkü kod Java kurallarına uygundur. Yanlış olan mantıktır. Kural basittir: >= ile yazılan zincirde eşikler büyükten küçüğe, < ile yazılan zincirde küçükten büyüğe dizilir.
Zincir yerine art arda bağımsız if'ler yazmak. if (not >= 90) harf = "A"; if (not >= 80) harf = "B"; if (not >= 70) harf = "C"; biçiminde yazılan kodda her if ayrı ayrı sınanır. 95 için önce "A", sonra "B", sonra "C" atanır ve sonuç C olur. else sözcüğü, “önceki koşullar yanlışsa” anlamını taşır; o olmadan koşullar birbirini dışlamaz.
3.1.3.3İç içe kararlar
İç içe karar (nested decision), bir karar yapısının bloğu içine yazılmış başka bir karar yapısıdır. İç karar yalnız dış koşul onu çalıştıracak kolu seçtiğinde sınanır.
if (koşul1) {
if (koşul2) {
// koşul1 VE koşul2 doğru
}
} else {
// koşul1 yanlış
}İç içe yapı, ilk sorunun cevabına göre ikinci bir soru sorulacaksa kullanışlıdır (Şekil 3.1.7). İç if'in gövdesine ancak koşul1 ve koşul2 birlikte doğruysa girilir. koşul1 yanlışsa iç if hiç sınanmaz ve doğrudan dış else çalışır.
koşul1 doğruysa koşul2 sınanır; yanlışsa dış else bloğu çalışır. koşul1 doğru, koşul2 yanlışsa hiçbir blok çalışmaz.Harf notu örneğinin iki kademeli bir sürümü yapıyı somutlaştırır. Önce geçip geçmediğe bakılır, geçen öğrenciler için A ile B ayrılır:
int not = 85;
if (not >= 60) {
if (not >= 90) {
System.out.println("Harf notu: A");
} else {
System.out.println("Harf notu: B");
}
} else {
System.out.println("Harf notu: F");
}not = 85 için dış koşul 85 >= 60 doğrudur ve dış if bloğuna girilir. Orada iç koşul sınanır: 85 >= 90 yanlıştır, akış iç else'e gider ve “Harf notu: B” yazılır. Dış else atlanır (Şekil 3.1.8). not = 45 olsaydı dış koşul yanlış olur, iç if hiç sınanmaz ve doğrudan “Harf notu: F” yazılırdı.
not = 85. Dış elmasta E koluna, iç elmasta H koluna gidilir ve “Yaz B” kutusu çalışır.Çok derin iç içe yapılar okunması zor koda yol açar. Dört beş düzey içe girmiş bir if'te hangi else'in hangi if'e ait olduğunu bulmak için parantez saymak gerekir. İki çare vardır. Birincisi düzgün girintidir: her iç düzey dört boşluk içeriden başlar. İkincisi yapıyı düzleştirmektir. Yalnız iki koşulun birlikte doğru olduğu durumla ilgilenilen bir kod && ile tek bir if'e indirilebilir: if (yas >= 18) { if (ehliyetVar) … } ile if (yas >= 18 && ehliyetVar) … aynı işi yapar.
Sarkık else sorunu. Süslü parantezsiz iç içe if'lerde bir else'in hangi if'e bağlandığı belirsiz görünebilir. Java'nın kuralı kesindir: else, kendisinden önceki ve henüz else'i olmayan en yakın if'e bağlanır. Aşağıdaki kodda girinti else'i iç if'e bağlı gösterir ve kural da aynı sonucu verir:
int x = 7;
if (x > 5)
if (x > 10)
System.out.println("A");
else
System.out.println("B");x = 7 için 7 > 5 doğrudur, iç koşul 7 > 10 yanlıştır ve iç if'in else'i “B” yazar. Aynı kod x = 3 ile çalıştırıldığında hiçbir şey yazmaz: dış koşul yanlıştır ve dış if'in else'i yoktur. else sola, dış if'in hizasına kaydırılsa da sonuç değişmez; girinti derleyici için anlamsızdır. else gerçekten dış if'e bağlanmak isteniyorsa iç if süslü parantezle kapatılır:
int x = 7;
if (x > 5) {
if (x > 10) {
System.out.println("A");
}
} else {
System.out.println("B");
}Bu sürüm x = 7 için hiçbir şey, x = 3 için “B” yazar. İki kod aynı satırlardan oluşur, ama parantezler else'in sahibini değiştirmiştir.
3.1.3.4Birleşik koşullar ve kısa devre
Birden çok koşul mantıksal operatörlerle tek bir koşulda birleştirilir. Java'nın üç mantıksal operatörü vardır: && (ve), || (veya) ve ! (değil). a && b ancak iki taraf da doğruysa doğrudur. a || b, taraflardan en az biri doğruysa doğrudur. !a, a'nın tersidir.
a | b | a && b | a || b | !a |
|---|---|---|---|---|
true | true | true | true | false |
true | false | false | true | false |
false | true | false | true | true |
false | false | false | false | true |
Mantıksal operatörlerin en sık kullanıldığı yer aralık denetimidir. Notun 60 ile 89 arasında olup olmadığı matematikteki gibi 60 <= not < 90 biçiminde yazılamaz. Java bu ifadeyi soldan sağa okur: önce 60 <= not hesaplanır ve bir boolean çıkar. Ardından bu boolean 90 ile karşılaştırılmaya çalışılır, ama bir boolean'ın 90'dan küçük olup olmadığı sorulamaz. Derleyici şu iletiyi verir:
error: bad operand types for binary operator '<'
first type: boolean
second type: intDoğru yazım iki karşılaştırmayı && ile bağlar: not >= 60 && not < 90. Burada || kullanmak da yaygın bir hatadır. not >= 60 || not < 90 her sayı için doğrudur: 60'tan küçük her sayı 90'dan da küçüktür.
Kısa devre değerlendirme (short-circuit evaluation): && ve || önce sol tarafı hesaplar. Sonuç yalnız sol taraftan belli oluyorsa sağ taraf hiç hesaplanmaz. &&'de sol taraf false ise sonuç false, ||'de sol taraf true ise sonuç true'dur.
Kısa devre bir hız ayrıntısından fazlasıdır; programın davranışını değiştirir. Aşağıdaki kod bunu üç satırda gösterir:
int a = 10, b = 0, sayac = 0;
boolean s1 = b != 0 && a / b > 2;
boolean s2 = b == 0 || ++sayac > 0;
boolean s3 = b == 0 && ++sayac > 0;
System.out.println(s1 + " " + s2 + " " + s3);
System.out.println("sayac = " + sayac);false true true
sayac = 1Başlangıçta b = 0'dır. a / b hesaplanırsa sıfıra bölme hatası (ArithmeticException) oluşur. sayac ise sağ tarafların çalışıp çalışmadığını saymak için konmuştur: ++sayac her çalıştığında 1 artar.
s1:&&önce sol tarafı hesaplar:b != 0yanlıştır. Sol taraf yanlışsa&&'nin sonucu sağ taraf ne olursa olsun yanlıştır.a / b > 2hiç hesaplanmaz ve hata oluşmaz.s1 = false.s2:||için sol tarafb == 0doğrudur. Sonuç zaten doğrudur;++sayacçalışmaz vesayac0 kalır.s2 = true.s3: yine&&; sol tarafb == 0doğrudur. Sonuç henüz belli değildir, sağ tarafın da hesaplanması gerekir.++sayacçalışır vesayac1 olur;1 > 0doğrudur.s3 = true.
s1 hesaplanmıştır: sol taraf false olduğu için sağ taraf “atlandı” olarak işaretlidir. Altta üç satır da bitmiştir: yalnız s3'ün sağ tarafı çalışmış ve sayac 1 olmuştur.Sonuçta üç sağ taraftan yalnız biri çalışmıştır. Bu davranışın iki yüzü vardır. Yararlı yüzü, s1'deki gibi tehlikeli bir işlemi korumaktır. b != 0 && a / b > 2 yazıldığında bölme ancak payda sıfır değilse yapılır. Aynı kalıp nesnelerde de sık görülür: s != null && s.length() > 0. Tehlikeli yüzü ise yan etkilidir. Sağ tarafa ++sayac gibi değişken değiştiren bir işlem yazılırsa bu işlemin çalışıp çalışmayacağı sol tarafa bağlı olur. Kodu okuyan kişi bunu kolayca gözden kaçırır. Yan etkili işlemleri && ve ||'nin sağına yazmamak en güvenli yoldur.
Java'da kısa devre yapmayan iki operatör daha vardır: & ve |. boolean değerlerle kullanıldıklarında iki tarafı da her zaman hesaplarlar. b != 0 & a / b > 2 yazılırsa sol taraf yanlış olduğu hâlde bölme yapılır ve program java.lang.ArithmeticException: / by zero ile durur. Koşullarda tek & yazmak çoğu zaman bir yazım hatasıdır.
3.1.3.5Koşul operatörü
Bir koşula göre iki değerden birini seçmek çok sık yapılan bir iştir. Bunun için if-else yazılabilir:
int a = 17, b = 42;
int enBuyuk;
if (a > b) {
enBuyuk = a;
} else {
enBuyuk = b;
}
System.out.println(enBuyuk);Java aynı işi tek bir ifadeyle yapan koşul operatörünü (conditional operator) sunar. Üç parçası olduğu için üçlü operatör (ternary operator) diye de anılır: koşul ? değer1 : değer2. Koşul doğruysa ifadenin değeri değer1, yanlışsa değer2'dir.
int a = 17, b = 42;
int enBuyuk = (a > b) ? a : b;
System.out.println(enBuyuk);17 > 42 yanlış olduğu için ikinci değer seçilir ve 42 yazılır. if-else bir deyimdir, bir iş yapar. ?: ise bir ifadedir, bir değer üretir. Bu yüzden atamanın sağına, bir metodun argümanına ya da bir dizgi birleştirmesinin içine yazılabilir: System.out.println(not >= 60 ? "geçti" : "kaldı");. İç içe koşul operatörleri (a ? b : c ? d : e) çabuk okunmaz hâle gelir; ikiden çok seçenekte else if zinciri tercih edilir. İki sayıdan büyüğünü bulmak için Java'nın hazır Math.max(a, b) metodu da kullanılabilir.
3.1.4switch-case
Bazı kararlar hep aynı değişkenin farklı sabit değerlere eşit olup olmadığını sorar: ay 1 mi, 2 mi, 3 mü…; işlem '+' mı, '-' mi? Bu tür kararlar else if zinciriyle de yazılabilir, ama on iki halkalı bir zincirde her halkada ay == tekrarlanır. switch yapısı bu durum için tasarlanmıştır.
3.1.4.1Yapı ve işleyiş
switch deyimi (switch statement), parantez içindeki ifadenin değerini case etiketlerindeki sabitlerle karşılaştırır ve eşleşen etiketin altındaki koda geçer. Eşleşen etiket yoksa, varsa, default etiketinin altındaki kod çalışır.
switch (değişken) { // kontrol edilen değer
case değer1: // her case bir değer
// İşlemler 1
break;
case değer2:
// İşlemler 2
break;
// ...
default: // hiçbiri eşleşmezse
// Varsayılan işlemler (opsiyonel)
}Parantez içindeki ifadeye seçici denir. Seçici bir kez hesaplanır. Değeri case etiketleriyle yukarıdan aşağıya karşılaştırılır. Eşleşen etiket bulunursa onun altındaki deyimler çalışır. break'e gelindiğinde switch'ten çıkılır ve kapanış parantezinden sonraki deyime geçilir. Hiçbir etiket eşleşmezse default bölümü çalışır. default da yoksa hiçbir şey yapılmadan switch'ten çıkılır (Şekil 3.1.10).
switch-case işleyişi. Seçici sırayla değer1 ve değer2 ile karşılaştırılır. Eşleşen case'in işlemleri çalışır ve break ile switch'ten çıkılır; hiçbiri eşleşmezse default işlemleri çalışır.Gündelik bir benzetme asansör düğmeleridir. Basılan kat numarası, gidilecek katı doğrudan seçer. Asansör her katta “burası 3. kat mı?” diye durmaz. switch da kavramsal olarak böyledir: etiketlerin sırayla denendiği düşünülebilir, ama derleyici etiketler sık dizilmiş tam sayılarsa bir atlama tablosu kurar ve doğrudan doğru etikete atlar.
3.1.4.2Mevsim belirleme
Ay numarasından mevsimi bulan kod, switch'in bütün parçalarını bir arada gösterir:
int ay = 4;
String mevsim;
switch (ay) {
case 3: case 4: case 5:
mevsim = "İlkbahar";
break;
case 6: case 7: case 8:
mevsim = "Yaz";
break;
case 9: case 10: case 11:
mevsim = "Sonbahar";
break;
case 12: case 1: case 2:
mevsim = "Kış";
break;
default:
mevsim = "Bilinmiyor";
}
System.out.println(ay + ". ay: " + mevsim);4. ay: İlkbaharBirinci satır ay'a 4 atar. İkinci satır mevsim'i tanımlar ama değer vermez. Üçüncü satırda seçici ay bir kez hesaplanır: değeri 4'tür. Ardından eşleşen etiket aranır. Dördüncü satırda aynı satıra yazılmış üç etiket vardır: case 3: case 4: case 5:. 4 bunlardan biriyle eşleşir. Üç etiketin altında tek bir kod bulunur; hangisi eşleşirse eşleşsin akış aynı koda iner. mevsim'e "İlkbahar" atanır. break, akışı switch'in kapanış parantezinin arkasına taşır. Son satır “4. ay: İlkbahar” yazar.
case grupları. ay = 4 ilk grupla eşleşir ve mevsim “İlkbahar” olur.default dalı burada iki iş görür. Birincisi, 0, 13 ya da −5 gibi geçersiz ay numaralarına bir cevap verir. İkincisi, derleyicinin değişken denetimini geçmeyi sağlar. mevsim yalnız case'lerde atansaydı derleyici şöyle düşünürdü: seçici hiçbir etiketle eşleşmezse mevsim hiç değer almaz. Son satırdaki okuma bu yüzden reddedilir:
error: variable mevsim might not have been initialized
System.out.println(mevsim);
^default eklenince switch'ten çıkan her yol mevsim'e bir değer yazmış olur ve hata kaybolur. 0'dan 13'e kadar bütün ay değerleri denendiğinde 0 ve 13 için “Bilinmiyor”, diğerleri için doğru mevsim yazılır.
3.1.4.3Aşağı düşme
Aşağı düşme (fall-through), break ile bitmeyen bir case'in kodundan sonra alttaki case'lerin kodunun da çalışmasıdır.
case etiketi bir karşılaştırma deyimi değil, bir giriş noktasıdır. switch yalnız nereden başlanacağını seçer. Akış bir kez içeri girdikten sonra alttaki etiketlere bakmaz; break'e ya da switch'in sonuna kadar satır satır ilerler. Aşağıdaki kodda ilk iki case'in break'i yoktur:
int gun = 2;
switch (gun) {
case 1:
System.out.println("Pazartesi");
case 2:
System.out.println("Salı");
case 3:
System.out.println("Çarşamba");
break;
default:
System.out.println("Diğer gün");
}
System.out.println("switch bitti");Salı
Çarşamba
switch bittigun = 2 ile seçici hesaplanır. case 1 atlanır, case 2 eşleşir ve “Salı” yazılır. Burada break yoktur. Akış alttaki case 3: satırına gelir. Bu etiket “gün 3 mü?” diye sormaz; akış onun altındaki koda düşer ve “Çarşamba” da yazılır, oysa gün 3 değildir. Ardından gelen break switch'ten çıkarır ve default çalışmaz. Son olarak “switch bitti” yazılır (Şekil 3.1.12).
case 2 çalışmış (yeşil), break olmadığı için akış case 3'e düşer (işaretli). case 1 atlanmıştır.gun | Giriş noktası | Çalışan kod | Ekran |
|---|---|---|---|
| 1 | case 1 | case 1, case 2, case 3, break | Pazartesi, Salı, Çarşamba |
| 2 | case 2 | case 2, case 3, break | Salı, Çarşamba |
| 5 | default | default | Diğer gün |
Aşağı düşme her zaman hata değildir. Mevsim örneğindeki case 3: case 4: case 5: satırı aslında bu davranışın bilerek kullanılmasıdır: case 3 ve case 4 boş etiketlerdir ve akış alttaki ortak koda düşer. Hata, düşmenin istenmediği yerde break'in unutulmasıdır. Derleyici bu durumda uyarı vermez. Bilerek bırakılan düşmeler bu yüzden genellikle bir açıklama satırıyla işaretlenir: // break yok: aşağı düşme istenir.
Aşağıdaki kod ne yazar?
int k = 2;
int puan = 0;
switch (k) {
case 1: puan += 10;
case 2: puan += 20;
case 3: puan += 30; break;
case 4: puan += 40;
}
System.out.println(puan);Seçici 2'dir; akış case 2'den girer ve puan 20 olur. break olmadığı için case 3'e düşülür ve puan 50 olur. Bu satırdaki break switch'ten çıkarır; case 4 çalışmaz. Ekrana 50 yazılır. k = 1 olsaydı 10 + 20 + 30 = 60, k = 4 olsaydı 40 yazılırdı.
3.1.4.4char ve String ile switch
Seçici yalnız int olmak zorunda değildir. Bir char seçiciyle basit bir hesap makinesi yazılabilir:
int a = 12, b = 4;
char islem = '*';
int sonuc;
switch (islem) {
case '+': sonuc = a + b; break;
case '-': sonuc = a - b; break;
case '*': sonuc = a * b; break;
case '/': sonuc = a / b; break;
default:
System.out.println("Geçersiz işlem");
sonuc = 0;
}
System.out.println(a + " " + islem + " " + b
+ " = " + sonuc);12 * 4 = 48char sabitleri tek tırnakla yazılır: '+', '*'. Kısa case'ler tek satıra sığdırılabilir: case '+': sonuc = a + b; break;. Derleyici için satır sonlarının önemi yoktur. islem = '*' için üçüncü etiket eşleşir, sonuc 48 olur ve “12 * 4 = 48” yazılır. islem = '%' gibi tanımsız bir işlem için default çalışır: “Geçersiz işlem” ve ardından “12 % 4 = 0” yazılır. default'taki sonuc = 0; satırı burada da zorunludur; yoksa son satır might not have been initialized hatası verir.
Bu kodda gözden kaçan bir tehlike vardır: b = 0 ve islem = '/' için a / b çalışır ve program ArithmeticException: / by zero ile durur. Gerçek bir hesap makinesinde '/' kolunun önce b != 0 koşulunu denetlemesi gerekir.
Java 7'den beri seçici bir String de olabilir:
String komut = "dur";
switch (komut) {
case "basla":
System.out.println("Motor çalıştı");
break;
case "dur":
System.out.println("Motor durdu");
break;
default:
System.out.println("Bilinmeyen komut");
}Motor durduEtiketler çift tırnaklı dizgi sabitleridir. Karşılaştırma equals metodunun yaptığı gibi içerik üzerinden yapılır; == ile yapılan nesne kimliği karşılaştırması değildir. Karşılaştırma büyük-küçük harfe duyarlıdır: komut = "Dur" için hiçbir etiket eşleşmez ve “Bilinmeyen komut” yazılır. Kullanıcının yazdığı metin önce küçük harfe çevrilirse bu sorun ortadan kalkar:
String komut = "Dur";
switch (komut.toLowerCase()) {
case "basla":
System.out.println("Motor çalıştı");
break;
case "dur":
System.out.println("Motor durdu");
break;
default:
System.out.println("Bilinmeyen komut");
}Bu kod “Motor durdu” yazar. Türkçe metinlerde toLowerCase() bilgisayarın bölge ayarına bağlıdır: Türkçe ayarda “I” harfi “ı”ya döner. Yalnız ASCII harflerinden oluşan komutlar için toLowerCase(java.util.Locale.ROOT) kullanmak bu farkı ortadan kaldırır.
3.1.4.5switch kuralları
Klasik switch'in kuralları, yapının bir atlama tablosuna çevrilebilmesi için konmuştur. Her kural bir derleyici iletisiyle denetlenir (Tablo 3.1.8).
- Seçicinin tipi.
byte,short,char,int, bunların sarmalayıcıları (Byte,Short,Character,Integer),Stringveenumolabilir.long,float,doublevebooleanile klasikswitchyazılamaz. - Etiketler sabittir.
casedeğerleri derleme sırasında bilinen sabitler olmalıdır:5,'a',"dur"ya da değeri sabit olan birfinaldeğişken. Sıradan bir değişken etiket olamaz. - Etiketler tektir. Aynı değer iki
case'te kullanılamaz. - Yalnız eşitlik sınanır. Aralık (
ay >= 3 && ay <= 5) ya da birleşik koşulcase'e yazılamaz. Bunlar içinif-elsegerekir.
| Kod | Derleyici iletisi |
|---|---|
double d = 2.5; switch (d) { … } | error: selector type double is not allowed |
long d = 2; switch (d) { … } | error: selector type long is not allowed |
int b = 2; switch (a) { case b: break; } | error: constant expression required |
switch (a) { case 1: break; case 1: break; } | error: duplicate case label |
Üçüncü satırdaki b, final int b = 2; olarak tanımlansaydı kod derlenirdi. final bir değişkenin değeri değişemez ve sabit bir ifadeyle verilmişse derleyici onu bir sabit gibi kullanır. Neden böyle bir kural olduğu, etiketlerin derleme sırasında tabloya yazıldığı düşünülünce anlaşılır: tabloyu kuran derleyici, çalışma sırasında değişebilecek bir değeri bilemez.
3.1.4.6switch mi, else-if mi?
İki yapı aynı işi farklı güçlü yanlarla yapar. Tablo 3.1.9 farkları bir araya getirir.
if-else / else if | switch-case | |
|---|---|---|
| Ne sınanır? | Her türlü boolean koşul: aralık, birleşik koşul, farklı değişkenler | Tek bir ifadenin sabit değerlerle eşitliği |
| Esneklik | Daha esnek | Yalnız belirli tipler ve sabit case değerleri |
| Çok seçenekte | Uzun zincirler karmaşık görünebilir | Düzenli ve okunaklı bir liste |
| Tuzak | Koşul sırası yanlışsa yanlış dal seçilir | break unutulursa alttaki case'lere düşülür |
| Örnek | if (not >= 90) … else if (not >= 80) … | switch (ay) { case 3: … } |
Her switch bir else if zinciriyle yazılabilir: her case bir == karşılaştırmasına, default son else'e dönüşür. Tersi her zaman mümkün değildir. Harf notu örneğindeki not >= 90 gibi aralıklar switch'e ancak 90'dan 100'e kadar on bir ayrı etiket yazılarak aktarılabilir. Seçim kuralı şöyle özetlenebilir: tek bir değişken birkaç sabitle karşılaştırılıyorsa switch, aralıklar ya da birden çok değişken varsa if-else.
3.1.4.7switch ifadesi
Java 14 ile switch'e yeni bir biçim eklendi. Mevsim örneği bu biçimle şöyle yazılır:
int ay = 11;
String mevsim = switch (ay) {
case 3, 4, 5 -> "İlkbahar";
case 6, 7, 8 -> "Yaz";
case 9, 10, 11 -> "Sonbahar";
case 12, 1, 2 -> "Kış";
default -> "Bilinmiyor";
};
System.out.println(mevsim);Sonbaharswitch ifadesi (switch expression), ok (->) etiketleri kullanan ve bir değer üreten switch biçimidir. Java 14'ten beri dilin standart parçasıdır.
Yeni biçimin dört farkı vardır. Birincisi, bir case virgülle ayrılmış birden çok değer alabilir: case 3, 4, 5. İkincisi, ok biçiminde aşağı düşme yoktur. Her kolun sağındaki ifade ya da blok çalışır ve switch biter; break gerekmez. Üçüncüsü, switch bir değer üretir ve bu değer doğrudan bir değişkene atanabilir. Atama bir deyim olduğu için sondaki kapanış parantezinden sonra noktalı virgül konur: };. Dördüncüsü, ifade biçimi bütün olasılıkları kapsamalıdır. Seçici bir int olduğunda olası değerler sonsuza yakın olduğu için default zorunludur. default silinirse derleyici şu iletiyi verir:
error: the switch expression does not cover all possible input valuesBir kolda tek bir değer yerine birkaç deyim gerekiyorsa sağ taraf bir blok olur ve üretilecek değer yield ile verilir:
int gun = 3;
String tur = switch (gun) {
case 6, 7 -> "hafta sonu";
default -> {
System.out.println("iş günü sayısı hesaplanıyor");
yield "hafta içi";
}
};
System.out.println(tur);iş günü sayısı hesaplanıyor
hafta içiOk biçimi ile iki noktalı biçim aynı switch'te karıştırılamaz; derleyici different case kinds used in the switch iletisini verir. Ok biçimi, değer üretmeyen bir deyim olarak da kullanılabilir: switch (gun) { case 6, 7 -> System.out.println("tatil"); default -> System.out.println("iş"); }. Bu derste klasik biçim esas alınır, çünkü eski kodların çoğu onunla yazılmıştır ve aşağı düşme davranışı yalnız onda görülür. Java 14 ve sonrasıyla yazılan yeni kodlarda ok biçimi daha güvenlidir: break unutma hatası bu biçimde yapılamaz.
3.1.5Uygulama: sıcaklık dönüşümü
Kararlardan döngülere geçmeden önce küçük bir sıralı program, formüllerin Java'ya nasıl çevrildiğini gösterir. Problem, bir Celsius değerini Fahrenheit ve Kelvin ölçeklerine dönüştürmektir. Üç ölçeğin birkaç ortak noktası Tablo 3.1.10'da verilmiştir.
| Celsius (°C) | Fahrenheit (°F) | Kelvin (K) | Anlamı |
|---|---|---|---|
| −40 | −40 | 233,15 | İki ölçeğin kesiştiği nokta |
| 0 | 32 | 273,15 | Suyun donma noktası |
| 25 | 77 | 298,15 | Oda sıcaklığı |
| 100 | 212 | 373,15 | Suyun kaynama noktası (deniz seviyesinde) |
Dönüşüm formülleri şunlardır: °F = (°C × 1,8) + 32, °C = (°F − 32) / 1,8 ve K = °C + 273,15. Java programı ilk ve son formülü tek bir değer için hesaplar:
public class Program {
public static void main(String[] args) {
double celsius = 25.0;
double fahrenheit = (celsius * 1.8) + 32;
double kelvin = celsius + 273.15;
System.out.println(celsius + " °C = " + fahrenheit + " °F = " + kelvin + " K");
}
}25.0 °C = 77.0 °F = 298.15 KProgram sıralıdır: üç satır hesaplama, bir satır yazdırma; karar ya da döngü yoktur. Ondalıklı değerler için double kullanılır. Koddaki 1.8 ve 273.15 Java sabitleridir ve kodda ondalık ayırıcı her zaman noktadır. Parantez önceliği açıkça gösterir: önce celsius * 1.8, sonra + 32. Çarpma toplamadan önce yapıldığı için parantez yazılmasa da sonuç aynı olurdu, ama formülün okunuşunu kolaylaştırır. Yazdırma satırındaki + işaretleri toplama değil birleştirmedir: bir dizgiyle (String) yan yana gelen + sayıyı metne çevirip ekler. double değerler 25.0 biçiminde yazılır.
Kayan noktalı sayılar her değeri tam tutamaz. 1,8 ikili sistemde sonlu bir kesirle yazılamadığı için bazı sonuçlar küçük bir hata taşır. 37 °C için celsius * 1.8 + 32 ifadesi 98.60000000000001 yazar; −40 °C için celsius + 273.15 ise 233.14999999999998 yazar. Bu sayılar yanlış değildir, yalnız son basamakta yuvarlama izi taşır. Ekranda düzgün görünmeleri için System.out.printf("%.2f%n", kelvin) ile iki basamağa yuvarlanarak yazdırılırlar. printf, println'ın aksine bölge ayarına uyar: Türkçe ayarlı bir bilgisayarda çıktı 233,15 olur.
Formülü tam sayılarla yazmak. int c = 37; için 9 / 5 * c + 32 ifadesi 69 verir: 9 / 5 iki int'in bölümüdür, kesir atılır ve sonuç 1'dir. c / 5 * 9 + 32 ise 37 / 5 = 7 yüzünden 95 verir. c * 9 / 5 + 32 soldan sağa hesaplanır: 333 / 5 = 66, sonuç 98; yine de 98,6 yerine 98'dir. Kesirli sonuç için işlemlerden en az biri double olmalıdır: c * 1.8 + 32 ya da c * 9.0 / 5 + 32.
3.1.6Döngüler ve for
Döngü (loop), bir deyim grubunu bir koşul sağlandıkça tekrarlayan kontrol yapısıdır. Tekrarlanan deyim grubuna gövde, gövdenin her bir çalışmasına tur (iteration) denir.
Döngünün gündelik karşılığı pistte on tur koşmaktır. Her turda aynı hareketler yapılır ve tur sayacı bir artar. Sayaç ona ulaşınca koşu biter. Programda da durum aynıdır: gövde aynı kalır, her turda değişen bir ya da birkaç değişken vardır ve bir koşul ne zaman durulacağını söyler. Döngüler bir veri kümesini sırayla işlemek için de kullanılır. Bir dizinin her öğesine bakmak, bir dosyanın her satırını okumak ya da bir ölçüm serisinin ortalamasını almak döngüyle yapılır. Aynı satırı 100 kez yazmak yerine tek bir döngü yazılır; kod kısalır ve tekrar sayısı tek bir sayı değiştirilerek ayarlanır.
3.1.6.1Döngü türleri
Java'nın üç döngüsü vardır. Üçü de aynı fikre dayanır, fark koşulun nerede sınandığında ve başlangıç ile güncellemenin nereye yazıldığındadır.
for: tekrar sayısı belli olduğunda kullanılır. Başlangıç, koşul ve güncelleme tek satırdadır:for (int i = 0; i < 5; i++) { … }.while: bir koşul sağlandığı sürece çalışır; tekrar sayısı önceden bilinmeyebilir:while (koşul) { … }.do-while: koşul sona yazılır; gövde en az bir kez çalışır:do { … } while (koşul);.
for ve while koşulu gövdeden önce sınar; gövde hiç çalışmayabilir (en az 0 tur). do-while koşulu gövdeden sonra sınar; gövde en az bir kez çalışır.3.1.6.2for döngüsü
for döngüsü (for loop), başlangıç, koşul ve güncelleme adımlarını başlığında, aralarında noktalı virgülle, tek satırda toplayan döngüdür.
for başlığının parçaları. ① Başlangıç en başta bir kez çalışır. ② Koşul her turdan önce sınanır. ③ Güncelleme her turun sonunda çalışır. ④ Gövde tekrarlanan işlemlerdir.Başlığın üç parçası farklı zamanlarda çalışır (Şekil 3.1.15). Çalışma sırası şöyledir: 1 → 2 → 4 → 3 → 2 → 4 → 3 → … → 2 yanlış → döngüden çık. Yani başlangıç bir kez, ardından her turda koşul, gövde ve güncelleme. Koşul yanlış çıktığında gövde çalışmaz ve döngüden sonraki deyime geçilir. for (int i = 0; i < 5; i++) için gövde i = 0, 1, 2, 3, 4 değerleriyle 5 kez çalışır. i 5 olunca koşul yanlış olur. Koşul toplam 6 kez sınanır: beşi doğru, sonuncusu yanlış.
for döngüsünün akış diyagramı. Başlangıç bir kez çalışır; koşul doğruysa gövde ve güncelleme yapılıp koşula dönülür, yanlışsa döngüden çıkılır.Şekil 3.1.16 aynı sırayı akış diyagramıyla gösterir. Döngünün işleyişi altı adımda özetlenebilir:
- Başlangıç: döngü değişkeni ilk değerini alır. Bu adım bir kez yapılır.
- Koşul kontrol edilir.
- Koşul doğruysa gövde çalışır.
- Güncelleme: döngü değişkeni değişir.
- Koşula geri dönülür; koşul doğru oldukça 3–4–5 tekrarlanır.
- Koşul yanlış olunca döngüden çıkılır ve sonraki deyimle devam edilir.
Koşul ilk kontrolde yanlışsa gövde hiç çalışmaz. for (int i = 10; i <= 1; i++) başlığı geçerlidir, ama gövdesi sıfır kez çalışır.
Başlıkta tanımlanan int i yalnız döngünün içinde yaşar. Döngüden sonra i'yi okumaya çalışmak cannot find symbol hatası verir. Döngü değişkeninin son değeri döngüden sonra gerekiyorsa değişken döngüden önce tanımlanır ve başlıkta yalnız ilk değeri verilir: int i; for (i = 1; i < 6; i++) { … }. Bu durumda döngü bittiğinde i 6'dır, çünkü döngü i < 6 yanlış olunca biter.
Güncelleme yalnız i++ olmak zorunda değildir. i-- ile geriye doğru, i += 2 ile ikişer, i *= 2 ile katlanarak ilerlenebilir. Tablo 3.1.11 birkaç başlığın davranışını verir.
| Başlık | i değerleri | Tur |
|---|---|---|
for (int i = 1; i <= 5; i++) | 1, 2, 3, 4, 5 | 5 |
for (int i = 10; i >= 1; i -= 2) | 10, 8, 6, 4, 2 | 5 |
for (int i = 1; i <= 100; i *= 2) | 1, 2, 4, 8, 16, 32, 64 | 7 |
for (int i = 10; i <= 1; i++) | — | 0 |
for (int i = 1; i != 9; i += 2) | 1, 3, 5, 7 | 4 |
for (int i = 1; i != 10; i += 2) | 1, 3, 5, …, 9, 11, … | sonsuz |
for (int i = 0; i <= 10; i--) | 0, −1, −2, … | sonsuz* |
Son iki satır dikkat ister. i != 10 koşulu tek sayılarla hiçbir zaman yanlış olmaz: i 9'dan 11'e atlar ve 10'u görmez. Bu yüzden koşullarda != yerine < ya da <= tercih edilir; i <= 10 yazılsaydı döngü 1, 3, 5, 7, 9 ile beş turda biterdi. Son satırda güncelleme yanlış yöndedir: i azaldıkça i <= 10 hep doğru kalır. Yıldızlı “sonsuz” sözcüğü pratik anlamdadır. int en küçük değeri olan −2.147.483.648'den sonra bir eksiltilince en büyük değere, 2.147.483.647'ye sarar ve koşul yanlış olur. Döngü bu yüzden 2.147.483.649 turdan sonra biter; ekrana yazan bir gövdeyle bu saatler sürer.
3.1.6.31'den 10'a toplam
Döngülerin en temel kullanımı bir değeri adım adım biriktirmektir. Aşağıdaki kod 1'den 10'a kadar sayıların toplamını bulur:
int toplam = 0;
for (int sayac = 1; sayac <= 10; sayac++) {
toplam += sayac;
}
System.out.println("Toplam: " + toplam);Toplam: 55Biriktirici (accumulator), döngünün her turunda değer eklenerek sonucu toplayan değişkendir. Toplam için 0 ile, çarpım için 1 ile başlatılır; bu sayılar işlemin etkisiz elemanıdır.
toplam 0 ile başlar. Başlangıç adımı sayac'ı 1 yapar. Koşul 1 <= 10 doğrudur ve ilk tur çalışır: toplam += sayac, yani toplam = toplam + sayac, 0 + 1 = 1 verir. Güncelleme sayac'ı 2 yapar, koşul yine doğrudur ve ikinci turda toplam 1 + 2 = 3 olur. Bu düzen her turda aynı biçimde tekrarlanır (Tablo 3.1.12).
| Tur | sayac | Koşul sayac <= 10 | toplam (gövdeden sonra) |
|---|---|---|---|
| 1 | 1 | doğru | 0 + 1 = 1 |
| 2 | 2 | doğru | 1 + 2 = 3 |
| 3 | 3 | doğru | 3 + 3 = 6 |
| 4 | 4 | doğru | 6 + 4 = 10 |
| 5 | 5 | doğru | 10 + 5 = 15 |
| 6 | 6 | doğru | 15 + 6 = 21 |
| 7 | 7 | doğru | 21 + 7 = 28 |
| 8 | 8 | doğru | 28 + 8 = 36 |
| 9 | 9 | doğru | 36 + 9 = 45 |
| 10 | 10 | doğru | 45 + 10 = 55 |
| — | 11 | yanlış | 55 (döngü biter) |
sayac = 2). Kalın çizilen geri dönüş oku güncellemeden koşula giden yoldur.Gövde tam 10 kez çalışır, koşul 11 kez sınanır. Döngüden sonra toplam yazılır. sayac ise artık tanımlı değildir: yalnız döngünün içinde yaşar. Sonuç, Gauss'un formülüyle de denetlenebilir: 1'den n'ye kadar sayıların toplamı n(n + 1)/2'dir ve n = 10 için 55 verir. Bilinen bir formülle karşılaştırmak, bir döngünün sınırlarını doğrulamanın kolay bir yoludur.
Biriktiriciyi döngünün içinde başlatmak. int toplam = 0; satırı gövdeye taşınırsa her turda toplam yeniden 0 olur. Daha da önemlisi, değişken artık gövdenin bloğunda yaşar ve döngüden sonraki println satırı cannot find symbol hatası verir. Biriktirici her zaman döngüden önce tanımlanır.
3.1.6.4Aralık toplama
Döngünün sınırları sabit olmak zorunda değildir. Aşağıdaki program kullanıcıdan iki sayı okur ve aradaki tam sayıları toplar:
import java.util.Scanner;
public class AralikToplama {
public static void main(String[] args) {
Scanner klavye = new Scanner(System.in);
System.out.print("Başlangıç değeri (a): ");
int a = klavye.nextInt();
System.out.print("Bitiş değeri (b): ");
int b = klavye.nextInt();
int toplam = 0;
for (int i = a; i <= b; i++) {
toplam += i;
}
System.out.println("Sayıların toplamı: " + toplam);
}
}3 ve 7 girildiğinde döngü değişkeni i, kullanıcının girdiği a'dan başlar ve 3 + 4 + 5 + 6 + 7 = 25 hesaplanır:
Başlangıç değeri (a): 3
Bitiş değeri (b): 7
Sayıların toplamı: 25Girdiler ters sırada verilirse sonuç şaşırtıcıdır:
Başlangıç değeri (a): 7
Bitiş değeri (b): 3
Sayıların toplamı: 0İlk kontrolde 7 <= 3 yanlıştır. Gövde hiç çalışmaz ve toplam 0 kalır. Program çökmez, hata iletisi de vermez; yalnız yanlış bir cevap verir. Çözüm, döngüden önce sırayı düzeltmektir. a > b ise iki değişkenin değerleri üçüncü bir değişken yardımıyla yer değiştirilir:
if (a > b) {
int gecici = a;
a = b;
b = gecici;
}Bu blok int toplam = 0; satırının önüne eklendiğinde 7 ve 3 girdisi de 25 verir. Değişkenlerin yerini değiştirmek için a = b; b = a; yazmak yetmez: ilk atamadan sonra a'nın eski değeri kaybolur ve iki değişken de 3 olur. gecici değişkeni bu değeri saklamak içindir.
3.1.7while ve do-while
3.1.7.1while döngüsü
while döngüsü (while loop), koşulu her turdan önce sınayan ve koşul doğru olduğu sürece gövdeyi tekrarlayan döngüdür. Başlığında yalnız koşul bulunur.
int i = 0;
while (i < 5) {
// Döngü içinde yapılacak işlemler
i++;
}while yalnız koşulu denetler. Başlangıç döngüden önce, güncelleme gövdenin içinde ayrıca yazılır. Koşul gövdeden önce sınandığı için gövde hiç çalışmayabilir. Tekrar sayısı önceden belli değilse while doğal seçimdir: “kullanıcı 0 girene kadar oku”, “sayı 0 olana kadar son basamağı at”, “hata kalmayana kadar dene”.
while, sağda do-while akış diyagramı. while'da koşul gövdenin önünde, do-while'da arkasındadır; iki döngüde de gövde, güncellemeyi kendisi yapar.Güncelleme programcıya bırakıldığı için while'ın en büyük tehlikesi sonsuz döngüdür. Yukarıdaki kodda i++ unutulursa i hep 0 kalır, 0 < 5 hep doğrudur ve program hiç bitmez. Derleyici bunu yakalamaz, çünkü bazı programlar bilerek sonsuz döngüyle yazılır: bir sunucu ya da bir gömülü sistemin ana döngüsü hiç bitmemelidir.
for ve while birbirine dönüştürülebilir. 1'den 10'a toplam iki biçimde de yazılabilir:
int toplam = 0;
int sayac = 1;
while (sayac <= 10) {
toplam += sayac;
sayac++;
}
System.out.println("Toplam: " + toplam);Toplam: 55İki döngüde de aynı üç parça vardır: başlangıç sayac = 1, koşul sayac <= 10, güncelleme sayac++. for bunları tek satırda toplar; while'da parçalar dağınıktır ve güncelleme unutulabilir. İki sürüm arasında küçük bir fark daha vardır: while sürümünde sayac döngüden önce tanımlandığı için döngüden sonra da okunabilir ve değeri 11'dir.
3.1.7.2Faktöriyel
Bir pozitif tam sayının faktöriyeli, 1'den o sayıya kadar bütün tam sayıların çarpımıdır: 5! = 1 × 2 × 3 × 4 × 5 = 120. Tanım gereği 0! = 1'dir. while ile yazılmış hâli şöyledir:
int n = 5;
int faktoriyel = 1;
int sayac = 1;
while (sayac <= n) {
faktoriyel *= sayac;
sayac++;
}
System.out.println(n + " faktöriyeli: " + faktoriyel);5 faktöriyeli: 120Çarpımın başlangıç değeri 1'dir. 0 ile başlansaydı her çarpma 0 verir ve sonuç hep 0 olurdu. Tablo 3.1.13 döngünün her turunu gösterir.
| Tur | Koşul | faktoriyel *= sayac | sayac (turdan sonra) |
|---|---|---|---|
| 1 | 1 <= 5 doğru | 1 × 1 = 1 | 2 |
| 2 | 2 <= 5 doğru | 1 × 2 = 2 | 3 |
| 3 | 3 <= 5 doğru | 2 × 3 = 6 | 4 |
| 4 | 4 <= 5 doğru | 6 × 4 = 24 | 5 |
| 5 | 5 <= 5 doğru | 24 × 5 = 120 | 6 |
| — | 6 <= 5 yanlış | 120 (döngü biter) | 6 |
Faktöriyel çok hızlı büyür ve int'in sınırını çabuk aşar. int en çok 2.147.483.647'yi tutar; 12! = 479.001.600 sığar, 13! = 6.227.020.800 sığmaz. n = 13 ile aynı kod 1932053504 yazar. Java tam sayı taşmasında hata vermez, sonuç sessizce yanlıştır. long kullanılınca 20!'ye kadar doğru sonuç alınır; 21! long'u da taşırır.
3.1.7.3Üs alma
Bir tabanın tam sayı kuvveti de çarpım biriktirerek hesaplanır: 34 = 3 × 3 × 3 × 3 = 81. Tekrar sayısı (üs) belli olduğu için for uygundur:
import java.util.Scanner;
public class UsAlma {
public static void main(String[] args) {
Scanner tarayici = new Scanner(System.in);
System.out.print("Taban ve üs girin: ");
int taban = tarayici.nextInt();
int us = tarayici.nextInt();
long sonuc = 1;
for (int i = 1; i <= us; i++) {
sonuc = sonuc * taban;
}
System.out.println("Sonuç: " + sonuc);
System.out.println("Math.pow: " + Math.pow(taban, us));
tarayici.close();
}
}Taban ve üs girin: 3 4
Sonuç: 81
Math.pow: 81.0sonuc 1 ile başlar ve döngü us kez taban ile çarpar. us = 0 ise döngü hiç dönmez ve sonuç doğru biçimde 1 olur. sonuc taşmaya karşı long seçilmiştir: 231 int'e sığmaz ama long'a sığar. long'un da sınırı vardır; 2 63 girdisiyle program Sonuç: -9223372036854775808 yazar.
Java'nın hazır Math.pow(taban, us) metodu aynı işi yapar, ama sonucu double olarak döndürür. Bu yüzden 81 değil 81.0 yazılır. Kendi döngümüz algoritmanın nasıl çalıştığını gösterir. Gerçek programlarda hazır metotlar tercih edilir: hem denenmiş hem de kesirli üsler gibi döngünün yapamadığı durumları da kapsar.
3.1.7.4do-while döngüsü
do-while döngüsü (do-while loop), önce gövdeyi çalıştırıp sonra koşulu sınayan döngüdür. Gövde koşuldan bağımsız olarak en az bir kez çalışır.
int i = 0;
do {
// Döngü içinde yapılacak işlemler
i++;
} while (i < 5);do-while, koşul sağlandığı sürece tekrar eder; tek farkı koşulu turun sonunda sınamasıdır. Yazımda bir ayrıntı vardır: while (koşul)'dan sonraki noktalı virgül zorunludur. Unutulursa derleyici ';' expected hatası verir. while döngüsünde ise bu noktalı virgül bir hatadır ve sinsi bir sonsuz döngü doğurabilir.
3.1.7.5Girdi doğrulama
do-while'ın tipik kullanımı kullanıcıdan geçerli bir değer alınana kadar soruyu tekrarlamaktır. Soru en az bir kez sorulmalıdır; bu tam olarak do-while'ın yaptığı iştir.
import java.util.Scanner;
public class DoWhileGirdi {
public static void main(String[] args) {
Scanner klavye = new Scanner(System.in);
int sayi;
do {
System.out.print("1-10 arası bir sayı girin: ");
sayi = klavye.nextInt();
} while (sayi < 1 || sayi > 10);
System.out.println("Kabul edilen sayı: " + sayi);
}
}Kullanıcı sırayla 15, 0 ve 7 yazsın:
1-10 arası bir sayı girin: 15
1-10 arası bir sayı girin: 0
1-10 arası bir sayı girin: 7
Kabul edilen sayı: 715 ve 0 aralık dışındadır: sayi < 1 || sayi > 10 koşulu doğrudur ve soru yeniden sorulur. 7 geçerlidir: koşul yanlıştır ve döngüden çıkılır. sayi döngüden önce tanımlanmıştır, çünkü hem koşulda hem de döngüden sonra kullanılır. Gövdenin içinde tanımlansaydı while (sayi < 1 …) satırı onu göremez ve cannot find symbol hatası verirdi: koşul, gövde bloğunun dışındadır.
Tablo 3.1.14 aynı kodu 12, −3 ve 4 girdileriyle izler. Koşuldaki ||'nin kısa devresi de burada görülür.
| Tur | Okunan sayi | sayi < 1 | sayi > 10 | Koşul | Sonuç |
|---|---|---|---|---|---|
| 1 | 12 | yanlış | doğru | doğru | soru tekrarlanır |
| 2 | −3 | doğru | sınanmaz | doğru | soru tekrarlanır |
| 3 | 4 | yanlış | yanlış | yanlış | döngüden çıkılır |
-3 < 1 doğru olduğu için || sağ tarafa bakmaz ve soru yeniden sorulur.Soru toplam üç kez sorulur ve “Kabul edilen sayı: 4” yazılır. Geçerli bir sayı gelene kadar döngü sürer. Kullanıcı sayı yerine “on” gibi bir metin yazarsa nextInt() InputMismatchException fırlatır. Bu durumu da karşılamak için hasNextInt() ile önce girdinin sayı olup olmadığına bakılabilir.
while ile do-while arasındaki fark, koşul ilk denemede yanlış olduğunda ortaya çıkar:
int i = 10;
while (i < 5) {
System.out.println("while: " + i);
i++;
}
int j = 10;
do {
System.out.println("do-while: " + j);
j++;
} while (j < 5);
System.out.println("i = " + i + ", j = " + j);do-while: 10
i = 10, j = 11i = 10 ile while koşulu önce sınar: 10 < 5 yanlıştır ve gövde hiç çalışmaz; i 10 kalır. j = 10 ile do bloğuna koşula bakılmadan girilir. “do-while: 10” yazılır ve j 11 olur. Koşul ancak şimdi sınanır: 11 < 5 yanlıştır ve döngüden çıkılır. Aynı koşul, farklı sonuç: while 0 tur, do-while 1 tur çalışmıştır.
Bu farkın gerçek bir sonucu basamak saymada görülür. Bir sayının basamaklarını sayan while (sayi > 0) { basamak++; sayi /= 10; } döngüsü 2024 için 4 verir (2024 → 202 → 20 → 2 → 0), ama 0 için 0 verir: koşul ilk denemede yanlıştır. Oysa 0 bir basamaklı bir sayıdır. Döngü do-while'a çevrilince gövde en az bir kez çalışır ve 0 için 1 bulunur:
int sayi = 0, basamak = 0;
do {
basamak++;
sayi /= 10;
} while (sayi != 0);
System.out.println(basamak);Koşul burada sayi != 0 olarak yazılmıştır. Böylece negatif sayılar da doğru sayılır: −45 için 2 bulunur. while (sayi > 0) ile negatif sayılar hiç dönmez.
3.1.7.6Üç döngünün karşılaştırması
for | while | do-while | |
|---|---|---|---|
| Koşul ne zaman sınanır? | Her turdan önce | Her turdan önce | Her turdan sonra |
| En az kaç tur? | 0 | 0 | 1 |
| Başlangıç / güncelleme | Başlıkta, tek satırda | Döngü dışında / gövdede | Döngü dışında / gövdede |
| Ne zaman? | Tekrar sayısı belli: “1'den n'ye”, “dizinin her öğesi” | Bir koşul değişene kadar: “sayı 0 olana kadar” | İşlem en az bir kez yapılmalı: girdi doğrulama, menü |
| Söz dizimi | for (bas; koşul; günc) { } | while (koşul) { } | do { } while (koşul); |
Üç döngü birbirine dönüştürülebilir; seçim okunabilirliğe göre yapılır. Bir döngü sayaçla ilerliyorsa for yazılır: okuyan kişi başlığa bakarak döngünün kaç kez döneceğini anlar. Döngünün bitişi bir olaya bağlıysa while daha açıktır. Gövdenin en az bir kez çalışması gerekiyorsa do-while seçilir.
3.1.7.7Sonsuz döngü ve sınır hataları
Döngü hatalarının çoğu iki gruba girer: hiç bitmeyen döngüler ve bir tur fazla ya da eksik dönen döngüler. İki grubu da derleyici yakalamaz.
Sonsuz döngü, koşulu hiçbir zaman yanlış olmayan döngüdür. En yaygın nedenleri şunlardır: while'da güncellemeyi unutmak, güncellemeyi yanlış yönde yapmak (i-- ile i <= 10) ve != koşulunun hedef değeri atlaması. Daha sinsi bir neden, döngü başlığından sonra konan noktalı virgüldür:
int i = 0;
while (i < 5); {
i++;
}Noktalı virgül, gövdesi boş bir döngü kurar. i hiç artmaz, 0 < 5 hep doğrudur ve program sessizce donar. Alttaki blok döngüye ait değildir; akış oraya hiç ulaşmaz. Aynı hata for başlığında yapılırsa sonsuz döngü oluşmaz ama mantık yine bozulur: for (int i = 0; i < 3; i++); { System.out.println("merhaba"); } boş gövdeyi üç kez çalıştırır ve “merhaba”yı yalnız bir kez yazar.
Sınır hatası (off-by-one error), bir döngünün bir tur fazla ya da bir tur eksik dönmesine yol açan, genellikle başlangıç değerinde ya da koşuldaki karşılaştırma operatöründe yapılan hatadır.
“1'den 10'a kadar” için i < 10 yazmak 10'u atlar ve 9 tur döner. i = 0 ile i <= 10 ise 11 tur döner. Bu hatalar elle izlemeyle kolay bulunur: ilk turda döngü değişkeninin değeri ve son turda değeri ayrı ayrı denetlenir. Bir döngünün kaç tur döndüğü bitiş − başlangıç + 1 formülüyle de hesaplanabilir (<= ile, birer artışta). Tipik bir örnek, her satırı 10 kez “+-” olan bir desendir. Satır 20 karakterdir; iç döngü j = 0'dan başlayıp j < 21 ile dönerse 21 karakter yazılır ve satır fazladan bir “+” ile biter.
Kayan noktalı sayılarla sayaç tutmak da bir sınır hatası kaynağıdır. for (double x = 0; x != 1.0; x += 0.1) döngüsü hiç bitmez: on kez 0,1 eklemek tam olarak 1,0 değil 0,9999999999999999 verir ve x 1,0'ı atlar. Döngü sayaçları her zaman tam sayı olmalıdır; ondalık değer gerekiyorsa gövdede sayaçtan hesaplanır: double x = i * 0.1;.
3.1.8İç içe döngüler
İç içe döngü (nested loop), bir döngünün gövdesinde bulunan başka bir döngüdür. Dıştaki döngüye dış döngü, gövdedekine iç döngü denir.
İç içe döngüler iki boyutlu yapılarla çalışırken kullanılır: tablolar, matrisler, ekrana çizilen desenler, bir resmin pikselleri. Benzetmesi saattir. Akrep (dış döngü) bir ilerlediğinde yelkovan (iç döngü) tam bir tur atar. Yelkovan turunu bitirmeden akrep yerinden oynamaz.
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 2; j++) {
// İç içe döngü gövdesi
}
}Dış döngünün her turunda iç döngü baştan başlar ve sonuna kadar döner. Bu yüzden gövdenin toplam çalışma sayısı iki döngünün tur sayılarının çarpımıdır: dış 3 tur × iç 2 tur = 6 kez (Şekil 3.1.20).
Çarpım kuralı, iç içe döngülerin maliyetini de gösterir. Her biri 1.000 tur dönen iki döngü 1.000.000, üç döngü 1.000.000.000 kez gövde çalıştırır. Bir milyar işlem bugünün bilgisayarında birkaç saniye sürebilir. Döngülerin iç içe sayısı arttıkça kod hem karmaşıklaşır hem de yavaşlar. Bir problemin kaç iç içe döngü gerektirdiği, algoritmanın hızını belirleyen başlıca etkendir.
3.1.8.1İç içe döngünün işleyişi
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 2; j++) {
System.out.println("i = " + i + ", j = " + j);
}
}Akış adım adım şöyledir. Dış döngü başlar: i = 1, 1 <= 3 doğrudur. İç döngü başlar: j = 1 ve gövde “i = 1, j = 1” yazar. j++ ile j 2 olur, “i = 1, j = 2” yazılır. j 3 olunca 3 <= 2 yanlıştır ve iç döngü tamamlanır. Akış dış döngüye döner: i++ ile i 2 olur, koşul doğrudur ve iç döngü j = 1'den yeniden başlar. Bu düzen i = 3 turu da bitene kadar sürer. Son olarak i 4 olur, 4 <= 3 yanlıştır ve dış döngü de biter.
| Adım | i | j | Olay | Ekran |
|---|---|---|---|---|
| 1 | 1 | 1 | iç döngü başlar | i = 1, j = 1 |
| 2 | 1 | 2 | i = 1, j = 2 | |
| 3 | 1 → 2 | 3 | 3 <= 2 yanlış: iç döngü biter, i++ | |
| 4 | 2 | 1 | iç döngü yeniden başlar | i = 2, j = 1 |
| 5 | 2 | 2 | i = 2, j = 2 | |
| 6 | 2 → 3 | 3 | iç döngü biter, i++ | |
| 7 | 3 | 1 | iç döngü yeniden başlar | i = 3, j = 1 |
| 8 | 3 | 2 | i = 3, j = 2 | |
| 9 | 3 → 4 | 3 | iç döngü biter; 4 <= 3 yanlış: dış döngü biter |
Gövde 3 × 2 = 6 kez çalışır. Dış koşul 4 kez, iç koşul 3 × 3 = 9 kez sınanır: iç döngü her başlayışında iki doğru ve bir yanlış sınama yapar. Çıktıda i'nin yavaş, j'nin hızlı değiştiği görülür: i 1'deyken j 1 ve 2'yi dolaşır.
3.1.8.2Yıldız deseni
İç döngünün sınırı her zaman sabit olmak zorunda değildir. Sınır dış döngünün değişkenine bağlanırsa her satırda farklı sayıda tur dönülür:
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= i; j++) {
System.out.print("* ");
}
System.out.println();
}*
* *
* * *
* * * * İç döngünün koşulu j <= i'dir. Birinci satırda i = 1 olduğu için iç döngü bir kez döner; ikinci satırda iki, üçüncüde üç, dördüncüde dört kez döner. print("* ") satır atlamaz; yıldızlar yan yana dizilir. İç döngü bitince println() alt satıra geçer. Bu satır iç döngünün dışında, dış döngünün içindedir: her satırın sonunda bir kez çalışır.
i. satırda j = 1 … i hücreleri dolmuştur.Toplam yıldız sayısı 1 + 2 + 3 + 4 = 10'dur. Genel olarak n satırlık üçgende n(n + 1)/2 yıldız yazılır. Bu sayı, iç sınırın dışa bağlı olduğu döngülerin tipik maliyetidir: dikdörtgen ızgaranın n2'sinin yaklaşık yarısı. İç döngü j = i'den başlayıp 4'e kadar dönseydi tur sayıları 4, 3, 2, 1 olur ve toplam yine 10 çıkardı; bu kez desen ters çevrilmiş bir üçgen olurdu.
println()'ı yanlış düzeye koymak. İç döngünün içine yazılırsa her yıldızdan sonra satır atlanır ve on satırlık tek bir sütun çıkar. Dış döngünün dışına yazılırsa bütün yıldızlar tek satıra dizilir. Hangi satırın hangi döngüye ait olduğu yalnız süslü parantezlerle belirlenir; girintinin düzgün tutulması bu ilişkiyi göze görünür kılar.
3.1.8.3Basamak değeri
for (int onlar = 0; onlar <= 4; onlar++) {
for (int birler = 1; birler <= 9; birler++) {
int sayi = onlar * 10 + birler;
System.out.println("Onlar basamağı: " + onlar
+ ", Birler basamağı: " + birler + " --> " + sayi);
}
}Bu kod iki basamaklı sayıları onlar ve birler basamağından kurar. Kaç satır yazdığını bulmak için iki döngünün tur sayıları çarpılır. Dış döngü onlar = 0 … 4 için 5 tur, iç döngü birler = 1 … 9 için 9 tur döner: 5 × 9 = 45 satır. İlk ve son satırlar şunlardır:
Onlar basamağı: 0, Birler basamağı: 1 --> 1
Onlar basamağı: 0, Birler basamağı: 2 --> 2
Onlar basamağı: 0, Birler basamağı: 3 --> 3
…
Onlar basamağı: 4, Birler basamağı: 8 --> 48
Onlar basamağı: 4, Birler basamağı: 9 --> 49sayi = onlar * 10 + birler 1'den 49'a kadar değer alır. birler 0'dan başlamadığı için 10, 20, 30 ve 40 hiç yazılmaz; 0 da yazılmaz. 1'den 49'a 49 sayı vardır, bunların 4'ü atlandığı için 45 satır kalır. İki hesap birbirini doğrular.
Kodun yazdırma satırı iki satıra bölünmüştür. Java'da uzun bir dizgi + ile parçalara ayrılıp sonraki satırda sürdürülebilir. Tırnak içindeki bir metin ise satır sonunda kesilemez. "Onlar basamağı: yazıp satırı bitirmek unclosed string literal hatası verir.
3.1.8.4Çarpım tablosu
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
int carpim = i * j;
System.out.print(i + "x" + j + "=" + carpim + "\t");
}
System.out.println(); // Yeni satır
}Dış döngü satırları (i), iç döngü sütunları (j) gezer: 10 × 10 = 100 çarpım. print satır atlamaz; her çarpımın arkasına eklenen "\t" bir sekme (tab) karakteridir ve sütunları bir sonraki sekme durağına hizalar. Satır bitince println() ile alt satıra geçilir.
i = 2 turudur: iç döngü j = 1, 2, 3, 4 … ile sütunları gezer.1x1=1 1x2=2 1x3=3 1x4=4 1x5=5 1x6=6 …
2x1=2 2x2=4 2x3=6 2x4=8 2x5=10 2x6=12 …
3x1=3 3x2=6 3x3=9 3x4=12 3x5=15 3x6=18 …
…
10x1=10 10x2=20 10x3=30 10x4=40 10x5=50 …Sekme durakları genellikle 8 karakter aralıklıdır. “10x10=100” gibi 9 karakterlik bir hücre bir durağı aşar ve satırın geri kalanını kaydırabilir. Daha düzenli bir tablo için printf ile sabit genişlikli alanlar kullanılır: System.out.printf("%4d", i * j); her sayıyı 4 karakterlik bir alana sağa yaslayarak yazar. %-4d sola yaslar, %04d eksik basamakları 0 ile doldurur.
3.1.9break ve continue
Döngüler normalde koşul yanlış olunca biter. Bazen döngünün ortasında karar vermek gerekir: aranan şey bulunduysa kalan turlar boşunadır; bir öğe işe yaramıyorsa o tur atlanmalıdır. Java bu iki iş için iki deyim sunar.
break, içinde bulunduğu en yakın döngüyü (ya da switch'i) hemen sonlandırır; akış döngüden sonraki deyime geçer. continue, o anki turun kalanını atlar ve bir sonraki tura geçer; for'da önce güncelleme, sonra koşul çalışır.
3.1.9.1Döngülerde break
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break;
}
System.out.println("Döngü adımı: " + i);
}
System.out.println("Döngüden çıkıldı.");Döngü adımı: 1
Döngü adımı: 2
Döngü adımı: 3
Döngü adımı: 4
Döngüden çıkıldı.i = 1, 2, 3 ve 4 için i == 5 yanlıştır; break atlanır ve adım yazılır. i = 5'te koşul doğrudur ve break çalışır. Döngü o anda sona erer: 5 yazılmaz, 6–10 hiç denenmez. Akış döngüden sonraki satıra geçer ve “Döngüden çıkıldı.” yazılır.
break: 1–4 yazılmış, 5'te döngü durmuş, 6–10 hiç işlenmemiştir. Altta continue: tek sayılar yazılmış, çift sayılar atlanmış, ama döngü 10'a kadar sürmüştür.break'ten hemen sonra, aynı bloğa yazılan bir deyime hiçbir zaman ulaşılamaz. Java bunu derleme hatası sayar: for (…) { break; System.out.println(i); } kodu error: unreachable statement verir. break bu yüzden hemen her zaman bir if'in içinde kullanılır.
3.1.9.2Asal sayı: bayrak ve break
Bir sayının asal olup olmadığını bulmak break'in en bilinen kullanımıdır. Asal sayı, 1'den büyük olan ve yalnız 1'e ve kendisine bölünen tam sayıdır. Program 2'den başlayarak bölen arar; ilk böleni bulduğu anda aramayı keser.
import java.util.Scanner;
public class AsalSayi {
public static void main(String[] args) {
Scanner tarayici = new Scanner(System.in);
System.out.print("Bir sayı girin: ");
int sayi = tarayici.nextInt();
boolean asalMi = true; // bayrak (flag)
if (sayi < 2) asalMi = false; // 0 ve 1 asal değildir
for (int bolen = 2; bolen <= sayi / 2; bolen++) {
if (sayi % bolen == 0) { // tam bölünüyor
asalMi = false;
break; // döngüyü kır
}
}
if (asalMi) {
System.out.println(sayi + " bir asal sayıdır.");
} else {
System.out.println(sayi + " bir asal sayı değildir.");
}
tarayici.close();
}
}Bir sayı girin: 21
21 bir asal sayı değildir.
Bir sayı girin: 97
97 bir asal sayıdır.asalMi bir bayraktır (flag): döngü boyunca bir durumun gerçekleşip gerçekleşmediğini taşıyan boolean değişken. Önce sayının asal olduğu varsayılır. 0 ve 1 asal olmadığı için sayi < 2 durumunda bayrak hemen indirilir. Döngü 2'den sayi / 2'ye kadar bölenleri dener. Tam bölen bulunursa bayrak indirilir ve break ile döngüden çıkılır. Döngüden sonra bayrağa bakılarak sonuç yazılır.
sayi | Denenen bölenler | Deneme | asalMi |
|---|---|---|---|
| 21 | 2 (kalan 1), 3 (kalan 0) → break | 2 | false |
| 97 | 2, 3, …, 48; hiçbiri bölmez | 47 | true |
| 91 | 2, 3, 4, 5, 6, 7 (kalan 0) → break | 6 | false |
| 2 | yok (2 <= 1 yanlış) | 0 | true |
| 1 | yok; bayrak sayi < 2 ile indirilir | 0 | false |
97 asaldır ve program bunu ancak 47 denemeden sonra anlar. Daha hızlı bir yol vardır. Bir sayının 1'den ve kendisinden başka böleni varsa bu bölenlerden biri sayının karekökünden küçük ya da eşittir. Çünkü sayi = a × b ise a ile b'nin ikisi birden √sayi'den büyük olamaz; çarpımları sayi'yi aşardı. Döngü koşulu buna göre değiştirilir:
for (int bolen = 2; bolen * bolen <= sayi; bolen++) {
if (sayi % bolen == 0) {
asalMi = false;
break;
}
}bolen * bolen <= sayi koşulu Math.sqrt çağırmadan ve kayan nokta hatası taşımadan karekök sınırını uygular. 97 için √97 ≈ 9,85'tir; bölenler 2 … 9 denenir ve 47 yerine yalnız 8 deneme yapılır. 1.000.003 gibi altı basamaklı bir asal sayıda fark çok daha büyüktür: sayi / 2 sınırıyla 500.000, karekök sınırıyla 999 deneme.
3.1.9.3Döngülerde continue
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
System.out.println("Tek sayı: " + i);
}Tek sayı: 1
Tek sayı: 3
Tek sayı: 5
Tek sayı: 7
Tek sayı: 9i = 1 tektir, koşul yanlıştır ve sayı yazılır. i = 2 çifttir: continue çalışır, gövdenin geri kalanı, yani yazdırma satırı atlanır ve doğrudan i++'ya geçilir. Bu düzen 10'a kadar sürer. i = 11 olunca 11 <= 10 yanlıştır ve döngü normal biçimde biter. continue döngüyü bitirmez, yalnız o turu kısa keser. Aynı iş continue olmadan, koşulu tersine çevirerek de yazılabilir: if (i % 2 != 0) System.out.println(…);. continue, atlanacak durumlar gövdenin başında ayıklanmak istendiğinde, yani gövde uzun olduğunda kodu düzleştirir.
break: i == 5 olduğunda akış döngüden tamamen çıkar. Sağda continue: i çift olduğunda yazdırma atlanır ve güncellemeye (i++) geçilir; döngü sürer.continue'nun while döngüsünde gizli bir tehlikesi vardır. for'da güncelleme başlıktadır ve continue'dan sonra da çalışır. while'da ise güncelleme gövdenin içindedir ve continue onu da atlayabilir:
int i = 0;
while (i < 5) {
if (i == 2) {
continue;
}
System.out.print(i + " ");
i++;
}
System.out.println("bitti");Bu kod “0 1 ” yazar ve sonra sonsuza kadar döner. i 2 olunca continue gövdenin kalanını, yani i++'yı da atlar. i hep 2 kalır, koşul hep doğrudur ve “bitti” hiç yazılmaz. Döngü for (int i = 0; i < 5; i++) biçimine çevrilirse güncelleme başlığa taşınır ve çıktı “0 1 3 4 bitti” olur.
3.1.9.4İç içe döngülerde break ve etiketler
break yalnız içinde bulunduğu en yakın döngüyü bitirir. İç döngüdeki bir break dış döngüye dokunmaz:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) {
break;
}
System.out.println(i + " " + j);
}
}1 1
2 1
3 1Her i için iç döngü j = 1'i yazar, j = 2'de break ile biter. Dış döngü ise sürer ve bir sonraki i ile iç döngüyü yeniden başlatır. Sonuçta her i için yalnız j = 1 yazılır.
İki döngüden birden çıkmak gerekiyorsa Java etiketli break sunar. Dış döngünün önüne bir ad ve iki nokta yazılır; break bu adla kullanılır:
dis:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j == 4) {
break dis;
}
System.out.println(i + " x " + j);
}
}
System.out.println("bitti");1 x 1
1 x 2
1 x 3
2 x 1
bittidis: etiketi dış döngüyü adlandırır. i = 1 turunda üç çarpım (1, 2, 3) 4'e eşit değildir ve hepsi yazılır. İç döngü biter, dış döngü i = 2'ye geçer. 2 × 1 = 2 yazılır. i = 2, j = 2'de 2 × 2 = 4 olur ve koşul doğrudur. break dis; yalnız iç döngüden değil, etiketli dış döngüden de çıkar. Kalan hücreler (2 × 3 ve bütün i = 3 turu) hiç ziyaret edilmez. Akış iki döngünün de sonrasına atlar ve “bitti” yazılır.
break. Yeşil hücreler yazdırılmıştır; 2 × 2 hücresinde koşul doğru olmuş ve break dis iki döngüden birden çıkmıştır. Kesikli hücreler hiç ziyaret edilmemiştir.Etiketsiz break olsaydı yalnız iç döngüden çıkılır ve i = 3 turu da çalışırdı. Bu turdaki çarpımlar 3, 6 ve 9'dur; hiçbiri 4'e eşit olmadığı için üçü de yazılır. Çıktı yedi satır olurdu: 1 x 1, 1 x 2, 1 x 3, 2 x 1, 3 x 1, 3 x 2, 3 x 3 ve ardından “bitti”. continue da etiketle kullanılabilir. continue dis; iç döngünün kalanını bırakır ve dış döngünün bir sonraki turuna geçer:
dis:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) {
continue dis;
}
System.out.println(i + " " + j);
}
}1 1
2 1
3 1Etiketler seyrek kullanılır. Akışı birden çok düzey öteye taşıdıkları için kodun izlenmesini zorlaştırırlar. İki döngüden çıkmanın daha yaygın yolu, iç içe döngüyü ayrı bir metoda taşıyıp aranan bulunduğunda return ile metottan dönmektir. Java'da goto sözcüğü ayrılmış bir sözcüktür ama kullanılamaz; etiketli break ve continue, dilin izin verdiği tek “atlama” biçimidir.
3.1.10Sık yapılan hatalar
Kontrol yapılarındaki hataların bir kısmını derleyici yakalar ve iletisiyle yerini gösterir. Bir kısmı ise derlenir, çalışır ve yalnız yanlış sonuç verir. İkinci grup daha tehlikelidir, çünkü programcıyı uyaran hiçbir şey yoktur. Tablo 3.1.18 karar yapılarındaki, Tablo 3.1.19 döngülerdeki tipik hataları toplar. Derleyici iletileri JDK 21 ile alınmıştır.
| Hata | Örnek | Sonuç | Doğrusu |
|---|---|---|---|
= ile karşılaştırma | if (x = 5) | incompatible types: int cannot be converted to boolean | if (x == 5) |
if'ten sonra noktalı virgül | if (x > 5); { … } | Derlenir; blok her zaman çalışır | Noktalı virgülü silmek |
| Parantezsiz blok | if (k) a(); b(); else c(); | 'else' without 'if' | Birden çok deyim için { } |
Parantezsiz blok, else'siz | if (k) a(); b(); | Derlenir; b() her zaman çalışır | if (k) { a(); b(); } |
| Aralığı matematikteki gibi yazmak | 60 <= not < 90 | bad operand types for binary operator '<' | not >= 60 && not < 90 |
| Zincirde yanlış sıra | if (not >= 60) … else if (not >= 90) … | Derlenir; 90 dalına hiç ulaşılmaz | Eşikleri büyükten küçüğe dizmek |
break'i unutmak | case 2: … case 3: … | Derlenir; alttaki case'ler de çalışır | Her case'i break ile bitirmek |
default olmadan atama | Değişken yalnız case'lerde atanıyor | variable mevsim might not have been initialized | default eklemek |
Değişken case etiketi | int b = 2; … case b: | constant expression required | Sabit ya da final değişken |
| Ondalık girişte nokta | Türkçe ayarda nextDouble() için -7.5 | InputMismatchException | -7,5 yazmak ya da useLocale |
| Hata | Örnek | Sonuç | Doğrusu |
|---|---|---|---|
| Güncellemeyi unutmak | while (i < 5) { println(i); } | Sonsuz döngü; derleyici yakalamaz | Gövdede i++ |
| Yanlış yönde güncelleme | for (int i = 0; i <= 10; i--) | Pratikte sonsuz döngü | i++ |
| Döngü başlığında noktalı virgül | while (i < 5); { i++; } | Gövdesi boş sonsuz döngü | Noktalı virgülü silmek |
| Sınır hatası | “1'den 10'a” için i < 10 | Derlenir; 9 tur döner | i <= 10; ilk ve son turu denetlemek |
| Döngü değişkenini dışarıda kullanmak | for (int i …) { } sonra println(i) | cannot find symbol | i'yi döngüden önce tanımlamak |
break'ten sonra kod | break; System.out.println(i); | unreachable statement | Satırı break'in önüne almak |
| Yanlış başlangıç değeri | int faktoriyel = 0; | Derlenir; sonuç hep 0 | Toplam 0, çarpım 1 ile başlar |
| Biriktiriciyi döngüde sıfırlamak | Gövdede int toplam = 0; | Her turda sıfırlanır; döngü dışından erişilemez | Döngüden önce tanımlamak |
while'da continue | if (i == 2) continue; ardından i++ | Sonsuz döngü | for kullanmak ya da i++'yı önce yapmak |
do-while sonunda noktalı virgül yok | } while (i < 3) | ';' expected | } while (i < 3); |
int taşması | int ile 13! | Derlenir; 1932053504 yazar | long |
Derlenen hataları bulmanın en güvenilir yolu elle izlemedir. Kod kâğıt üzerinde birkaç küçük girdiyle, her turdan sonra değişkenler bir tabloya yazılarak çalıştırılır. Özellikle üç girdi denenmelidir: döngünün hiç dönmemesi gereken durum (ör. n = 0), tek tur dönmesi gereken durum ve bir sınır değeri. Her turdan sonra değişkenleri gösteren bir izleme tablosu, sınır hatalarını kod çalıştırılmadan ortaya çıkarır.
3.1.11Özet
- Üç temel yapı: sıralı işlem, karar ve döngü. Her algoritma bunların birleşimiyle yazılabilir. Sıralı kodda her satır önceki satırların bıraktığı duruma dayanır.
- Koşul: sonucu
trueya dafalseolanbooleanifadedir.=atama,==karşılaştırmadır; Javaif (x = 7)'yi derlemez. if/else: koşul doğruysa bir blok, değilse diğeri çalışır; iki bloktan en fazla biri çalışır. Parantez yoksaif'e yalnız ilk deyim bağlıdır; girinti anlamsızdır.else ifzinciri: koşullar yukarıdan aşağıya denenir, ilk doğru koşulun bloğu çalışır ve zincir biter. Koşulların sırası üst sınırların yerini tutar.- İç içe kararlar: iç koşul yalnız dış koşul doğruysa sınanır.
else, henüzelse'i olmayan en yakınif'e bağlanır. Derin yapılar&&ile düzleştirilir. - Kısa devre:
&&sol taraffalse,||sol taraftrueise sağ tarafı hesaplamaz.b != 0 && a / b > 2güvenlidir; sağ tarafa yan etkili işlem yazılmaz.?:iki değerden birini seçen ifadedir. switch: bir değeri sabitcaseetiketleriyle karşılaştırır.casebir giriş noktasıdır;breakyoksa akış alttakicase'lere düşer. Seçicibyte,short,char,int, sarmalayıcıları,Stringya daenumolabilir; etiketler sabit ve tektir.switchifadesi (Java 14+):case 3, 4, 5 -> …biçimi; aşağı düşme yoktur, değer üretir, bütün olasılıkları kapsamalıdır (intiçindefaultzorunlu).for: başlangıç, koşul ve güncelleme tek satırda; çalışma sırası başlangıç, sonra her turda koşul → gövde → güncelleme. Başlıkta tanımlanan değişken yalnız döngüde yaşar.whilevedo-while:whilekoşulu önce sınar, 0 tur dönebilir;do-whilesonra sınar, en az 1 tur döner ve sonunda;ister. Girdi doğrulamado-while'ın tipik kullanımıdır.- Biriktirici: toplam 0 ile, çarpım 1 ile başlar ve döngüden önce tanımlanır.
int12!'ye,long20!'ye kadar yeter; taşma sessizdir. - İç içe döngüler: iç döngü dış döngünün her turunda baştan başlar; gövde tur sayılarının çarpımı kadar çalışır. İç sınır dışa bağlıysa (
j <= i) toplam n(n + 1)/2'dir. breakvecontinue:breaken yakın döngüyü bitirir,continueo turun kalanını atlar. Etiketlibreak dis;dıştaki adlandırılmış döngüden de çıkar. Asal sayı aramasında bayrak vebreakbirlikte kullanılır; sınırbolen * bolen <= sayiile kısaltılır.- Hatalar: sonsuz döngü, sınır hatası, yanlış koşul sırası ve unutulan
breakderlenir ama yanlış çalışır; bunlar elle izlemeyle bulunur.
3.1.12Kavram listesi
- kontrol yapısı control structure
- koşul condition
- blok block
- if deyimi if statement
- else-if zinciri else-if chain
- iç içe karar nested decision
- kısa devre değerlendirme short-circuit evaluation
- koşul operatörü conditional operator
- switch deyimi switch statement
- aşağı düşme fall-through
- switch ifadesi switch expression
- döngü loop
- for döngüsü for loop
- while döngüsü while loop
- do-while döngüsü do-while loop
- biriktirici accumulator
- iç içe döngü nested loop
- break / continue
- tur iteration
- sonsuz döngü infinite loop
- sınır hatası off-by-one error
- bayrak flag
- etiketli break labeled break
3.1.13Kendini Dene
Aşağıdaki zincir
sicaklik= 18, 15, 0 ve −3 için ne yazar?if (sicaklik < 0) System.out.println("donuyor"); else if (sicaklik < 15) System.out.println("soğuk"); else if (sicaklik < 25) System.out.println("ılık"); else System.out.println("sıcak");Cevap
18: ılık; 15: ılık; 0: soğuk; −3: donuyor.
<ile yazılan zincirde eşikler küçükten büyüğe dizilir. 15,15 < 15yanlış olduğu için “soğuk” halkasını geçer ve “ılık”a düşer. 0 da0 < 0yanlış olduğu için “donuyor” olmaz.int a = 0, b = 5;içinboolean s = (a != 0) && (b / a > 1) || b > 3;satırı çalıştırılıyor. Program sıfıra bölme hatası verir mi?snedir?Cevap
Hata vermez;
s = true.&&,||'den önce gelir; ifade((a != 0) && (b / a > 1)) || (b > 3)diye okunur.a != 0yanlış olduğu içinb / akısa devreyle hiç hesaplanmaz ve parantez içifalseolur. Ardındanb > 3doğru çıkar ve sonuçtrue'dur.Aşağıdaki
switchk= 3, 1, 5 ve 7 içint'ye hangi değeri verir?int t = 0; switch (k) { case 1: t += 1; case 3: t += 3; case 5: t += 5; break; default: t += 100; }Cevap
k = 3: 3 + 5 = 8.k = 1: 1 + 3 + 5 = 9.k = 5: 5.k = 7: 100. İlk ikicase'tebreakolmadığı için akışcase 5'e kadar düşer;case 5'tekibreakdefault'a geçişi engeller.int i; for (i = 3; i <= 30; i += 4) { … }döngüsü kaç tur döner? Gövdei'nin hangi değerlerini görür? Döngüden sonraikaçtır?Cevap
7 tur: 3, 7, 11, 15, 19, 23, 27. Sonraki değer 31'dir ve
31 <= 30yanlıştır; döngüden sonrai = 31. Tur sayısı ⌊(30 − 3) / 4⌋ + 1 = 6 + 1 = 7 diye de hesaplanabilir.int n = 100, adim = 0; while (n > 1) { n /= 2; adim++; }kodundan sonraadimvenkaçtır?Cevap
adim = 6,n = 1.n: 100 → 50 → 25 → 12 → 6 → 3 → 1. Tam sayı bölmesi kesri atar: 25 / 2 = 12, 3 / 2 = 1. Döngü, sayıyı 1'e indirmek için gereken yarılama sayısını, yani ⌊log2 100⌋ = 6'yı bulur.int x = 0; do { x += 3; } while (x < 0);veint y = 0; while (y < 0) { y += 3; }kodlarından sonraxveykaçtır?Cevap
x = 3,y = 0. Koşul iki döngüde de ilk sınamada yanlıştır.do-whilegövdeyi koşula bakmadan bir kez çalıştırır;whileise koşulu önce sınar ve gövdeye hiç girmez.Aşağıdaki kodda gövde kaç kez çalışır ve
saykaç olur?int say = 0; for (int i = 1; i <= 5; i++) for (int j = 1; j <= i; j++) if ((i + j) % 2 == 0) say++;Cevap
Gövde 1 + 2 + 3 + 4 + 5 = 15 kez çalışır. Toplamı çift olan çiftler
i= 1 … 5 için sırasıyla 1, 1, 2, 2, 3 tanedir:say = 9. Örneğini = 5'tej= 1, 3, 5 için toplam 6, 8, 10'dur.Aşağıdaki kod ne yazar?
int s = 0; for (int i = 1; i <= 10; i++) { if (i % 4 == 0) continue; if (i > 7) break; s += i; } System.out.println(s);Cevap
24. 1, 2, 3 eklenir (6). 4,
continueile atlanır. 5, 6, 7 eklenir (24). 8, 4'e bölündüğü için yinecontinueile atlanır;breaksatırına ulaşmaz. 9'da9 > 7doğrudur vebreakdöngüyü bitirir; 9 ve 10 eklenmez.Hangisi derlenir? (a)
switch (3.0) { default: }(b)final int K = 2; switch (x) { case K: break; }(c)if (x = 0) { }(d)while (true) { } System.out.println();Cevap
Yalnız (b). (a)
selector type double is not allowed. (b)Ksabit değerli birfinaldeğişkendir ve etiket olabilir. (c)incompatible types: int cannot be converted to boolean. (d)while (true)döngüsünün içindebreakolmadığı için hiçbir zaman bitmez ve sonraki satır içinunreachable statementhatası verilir.
3.1.14İleri okuma
- Oracle, The Java Tutorials, “Control Flow Statements”:
if,switch, döngüler vebreak/continueiçin kısa, örnekli resmî anlatım; switch ifadeleri için ayrıca “Switch Expressions” sayfası bulunur. - A. B. Downey ve C. Mayfield, Think Java (2. baskı, 2019), Bölüm 5–6: koşullar, mantıksal operatörler ve döngüler sade bir dille, çok sayıda kısa alıştırmayla işlenir. Kitap çevrimiçi ücretsiz okunabilir.
- J. Gosling ve diğ., The Java Language Specification, Java SE 21 Edition, Bölüm 14: her deyimin kesin kuralı, aşağı düşme, etiketler ve “ulaşılamaz deyim” denetiminin tam tanımı buradadır.
- C. S. Horstmann, Core Java, Volume I: Fundamentals (12. baskı), Kesim 3.8: kontrol akışını C/C++ ile karşılaştırarak anlatır; deneyimli okur için kısa bir başvuru kaynağıdır.
3.1.15Kaynaklar
- C. Böhm, G. Jacopini, “Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules”, Communications of the ACM, 9(5), 366–371, 1966.
- P. Deitel, H. Deitel, Java: How to Program (Early Objects), 11. baskı, Pearson, 2017 (Bölüm 4–5: Control Statements).
- A. B. Downey, C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (Bölüm 5: Conditionals and Logic; Bölüm 6: Loops).
- J. Gosling, B. Joy, G. Steele, G. Bracha, A. Buckley, D. Smith, G. Bierman, The Java Language Specification, Java SE 21 Edition, Oracle, 2023 (Bölüm 14: Blocks, Statements, and Patterns).
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson, 2022 (3.8: Control Flow).
- Oracle, The Java Tutorials: Control Flow Statements, docs.oracle.com/javase/tutorial/java/nutsandbolts/flow.html.
- K. Sierra, B. Bates, T. Gee, Head First Java, 3. baskı, O'Reilly, 2022.
İlgili sayfalar: Bölüm 3.1 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.