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 if zinciri, 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; klasik switch ile ok biçimli switch ifadesini ayırt etmek.
Döngüler
for, while ve do-while dö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; break ve continue'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.

Tanım 3.1.1

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.

Adım 1Adım 2Adım 3 koşul ?PQEH koşul ?gövdeEH
Şekil 3.1.1: Üç temel kontrol yapısı. Solda sıralı akış: adımlar art arda çalışır. Ortada karar: koşul doğruysa (E) P, yanlışsa (H) Q yapılır ve iki yol yeniden birleşir. Sağda döngü: koşul doğru oldukça gövde çalışır ve akış koşula geri döner; koşul yanlış olunca döngüden çıkılır.

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.

Tablo 3.1.1: Sıralı programın satır satır izlenmesi.
SatırDeyimxysonucEkran
1int x = 5;5
2int y = 10;510
3int sonuc = x + y;51015
4System.out.println(sonuc);5101515
x5y10sonuc15
Şekil 3.1.2: Üçüncü satırdan sonra bellekteki üç değişken. 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.

Sık yapılan hata

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

Tanım 3.1.2

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.

Tablo 3.1.2: Karşılaştırma operatörleri (x = 7).
OperatörAnlamıÖrnekSonuç
==eşit mix == 7true
!=eşit değil mix != 7false
<küçük müx < 10true
>büyük müx > 10false
<=küçük ya da eşit mix <= 7true
>=büyük ya da eşit mix >= 8false

İ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ı

Tanım 3.1.3

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.

önceki deyimkoşul ?if bloğuelse bloğusonraki deyimEH
Şekil 3.1.3: 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.

Sık yapılan hata

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 = 18yas ≥ 18 ?Yaz alabilirYaz alamazSonEH
Şekil 3.1.4: Yaş kontrolünün akış diyagramı. yas = 18 için koşul doğrudur ve akış kalın çizilen E koluna gider.
Tablo 3.1.3: Yaş kontrolünün üç değer için sonucu.
yasyas >= 18Çalışan blokEkran
16falseelseEhliyet alamazsınız.
18trueifEhliyet alabilirsiniz.
25trueifEhliyet 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.

Oku sayisayi < 0 ?mutlak = −sayimutlak = sayiYaz mutlakEH
Şekil 3.1.5: Mutlak değer akış diyagramı. −7,5 girildiğinde 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.

Tablo 3.1.4: Mutlak değer programı, Türkçe bölge ayarlı bir bilgisayarda.
Klavyedensayisayi < 0Ekran
-7,5−7.5trueMutlak değer: 7.5
7,57.5falseMutlak değer: 7.5
00.0falseMutlak 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

Tanım 3.1.4

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).

not = 75not ≥ 90 ?Yaz Anot ≥ 80 ?Yaz Bnot ≥ 70 ?Yaz Cnot ≥ 60 ?Yaz DYaz FSonEHEHEHEH
Ş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.
Tablo 3.1.5: Harf notu zincirinde sınanan koşullar.
not>= 90>= 80>= 70>= 60Sınanan koşulEkran
95doğru———1A
80yanlışdoğru——2B
75yanlışyanlışdoğru—3C
60yanlışyanlışyanlışdoğru4D
45yanlışyanlışyanlışyanlış4F

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.

Sık yapılan hata

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

Tanım 3.1.5

İç 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?truefalsekoşul2?else bloğutruefalseikisi de doğruhiçbir şey
Şekil 3.1.7: İç içe karar ağacı. 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 = 85not ≥ 60 ?Yaz Fnot ≥ 90 ?Yaz AYaz BSonHEEH
Şekil 3.1.8: İç içe kararın akış diyagramı, 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.

Tablo 3.1.6: Mantıksal operatörlerin doğruluk tablosu.
aba && ba || b!a
truetruetruetruefalse
truefalsefalsetruefalse
falsetruefalsetruetrue
falsefalsefalsefalsetrue

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: int

Doğ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.

Tanım 3.1.6

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 = 1

Baş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 != 0 yanlıştır. Sol taraf yanlışsa &&'nin sonucu sağ taraf ne olursa olsun yanlıştır. a / b > 2 hiç hesaplanmaz ve hata oluşmaz. s1 = false.
  • s2: || için sol taraf b == 0 doğrudur. Sonuç zaten doğrudur; ++sayac çalışmaz ve sayac 0 kalır. s2 = true.
  • s3: yine &&; sol taraf b == 0 doğrudur. Sonuç henüz belli değildir, sağ tarafın da hesaplanması gerekir. ++sayac çalışır ve sayac 1 olur; 1 > 0 doğrudur. s3 = true.
değişkensolsağsonuçs1b != 0 → false&&atlandıfalses2b == 0||++sayac > 0s3b == 0&&++sayac > 0sayac = 0
değişkensolsağsonuçs1b != 0 → false&&atlandıfalses2b == 0 → true||atlandıtrues3b == 0 → true&&++sayac > 0 → truetruesayac = 1
Şekil 3.1.9: Kısa devre değerlendirmenin iki anı. Üstte 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ş

Tanım 3.1.7

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 (değişken)= değer1 ?işlemler 1= değer2 ?işlemler 2default işlemleriswitch'ten sonraEHEHbreak
Ş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: İlkbahar

Birinci 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.

3 4 5→ İlkbahar6 7 8→ Yaz9 10 11→ Sonbahar12 1 2→ Kışdefault→ Bilinmiyor
Şekil 3.1.11: Mevsim örneğinde 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

Tanım 3.1.8

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 bitti

gun = 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 1:Pazartesicase 2:Salıcase 3:Çarşamba · breakdefault:Diğer gün
Şekil 3.1.12: Aşağı düşme. case 2 çalışmış (yeşil), break olmadığı için akış case 3'e düşer (işaretli). case 1 atlanmıştır.
Tablo 3.1.7: Aynı switch'in üç değerle çıktısı.
gunGiriş noktasıÇalışan kodEkran
1case 1case 1, case 2, case 3, breakPazartesi, Salı, Çarşamba
2case 2case 2, case 3, breakSalı, Çarşamba
5defaultdefaultDiğ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.

Örnek 3.1.1

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 = 48

char 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 durdu

Etiketler ç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), String ve enum olabilir. long, float, double ve boolean ile klasik switch yazılamaz.
  • Etiketler sabittir. case değerleri derleme sırasında bilinen sabitler olmalıdır: 5, 'a', "dur" ya da değeri sabit olan bir final değ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şul case'e yazılamaz. Bunlar için if-else gerekir.
Tablo 3.1.8: switch kurallarının ihlali ve JDK 21 derleyicisinin iletileri.
KodDerleyici 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.

Tablo 3.1.9: if-else zinciri ile switch-case'in karşılaştırması.
if-else / else ifswitch-case
Ne sınanır?Her türlü boolean koşul: aralık, birleşik koşul, farklı değişkenlerTek bir ifadenin sabit değerlerle eşitliği
EsneklikDaha esnekYalnız belirli tipler ve sabit case değerleri
Çok seçenekteUzun zincirler karmaşık görünebilirDüzenli ve okunaklı bir liste
TuzakKoşul sırası yanlışsa yanlış dal seçilirbreak unutulursa alttaki case'lere düşülür
Örnekif (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);
Sonbahar
Tanım 3.1.9

switch 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 values

Bir 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çi

Ok 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.

Tablo 3.1.10: Sıcaklık ölçekleri arasında karşılık gelen değerler.
Celsius (°C)Fahrenheit (°F)Kelvin (K)Anlamı
−40−40233,15İki ölçeğin kesiştiği nokta
032273,15Suyun donma noktası
2577298,15Oda sıcaklığı
100212373,15Suyun kaynama noktası (deniz seviyesinde)
°C−40025100°F−403277212K233,15273,15298,15373,15
Şekil 3.1.13: Üç sıcaklık ölçeği aynı eksende. Kırmızı noktalar 0 °C'nin, yani suyun donma noktasının üç ölçekteki karşılığıdır.

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 K

Program 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.

Sık yapılan hata

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

Tanım 3.1.10

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 / whilekoşulgövdedo-whilegövdekoşulen az 0 turen az 1 tur
Şekil 3.1.14: Koşulun yeri. 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ü

Tanım 3.1.11

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 (int i = 0① başlangıçen başta, bir kez;i < 5② koşulher turdan önce;i++③ güncellemeher turun sonunda) {// ④ gövde: tekrarlanan işlemler}
Şekil 3.1.15: 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ış.

başlangıçkoşul ?gövdegüncellemedöngüden sonraEH
Şekil 3.1.16: 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:

  1. Başlangıç: döngü değişkeni ilk değerini alır. Bu adım bir kez yapılır.
  2. Koşul kontrol edilir.
  3. Koşul doğruysa gövde çalışır.
  4. Güncelleme: döngü değişkeni değişir.
  5. Koşula geri dönülür; koşul doğru oldukça 3–4–5 tekrarlanır.
  6. 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.

Tablo 3.1.11: Farklı for başlıklarında gövdenin gördüğü i değerleri.
Başlıki değerleriTur
for (int i = 1; i <= 5; i++)1, 2, 3, 4, 55
for (int i = 10; i >= 1; i -= 2)10, 8, 6, 4, 25
for (int i = 1; i <= 100; i *= 2)1, 2, 4, 8, 16, 32, 647
for (int i = 10; i <= 1; i++)—0
for (int i = 1; i != 9; i += 2)1, 3, 5, 74
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: 55
Tanım 3.1.12

Biriktirici (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).

Tablo 3.1.12: 1'den 10'a toplam döngüsünün izlenmesi.
TursayacKoşul sayac <= 10toplam (gövdeden sonra)
11doğru0 + 1 = 1
22doğru1 + 2 = 3
33doğru3 + 3 = 6
44doğru6 + 4 = 10
55doğru10 + 5 = 15
66doğru15 + 6 = 21
77doğru21 + 7 = 28
88doğru28 + 8 = 36
99doğru36 + 9 = 45
1010doğru45 + 10 = 55
—11yanlış55 (döngü biter)
toplam = 0sayac = 1sayac ≤ 10 ?toplam += sayacsayac++Yaz toplamEH
Şekil 3.1.17: Toplam döngüsünün akış diyagramı, ikinci turun koşul sınaması (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.

Sık yapılan hata

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ı: 25

Girdiler 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ü

Tanım 3.1.13

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”.

başlangıçkoşul ?gövdegüncellemedöngüden sonraEH gövdegüncellemekoşul ?EH
Şekil 3.1.18: Solda 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.

Tablo 3.1.13: n = 5 için faktöriyel döngüsünün izlenmesi.
TurKoşulfaktoriyel *= sayacsayac (turdan sonra)
11 <= 5 doğru1 × 1 = 12
22 <= 5 doğru1 × 2 = 23
33 <= 5 doğru2 × 3 = 64
44 <= 5 doğru6 × 4 = 245
55 <= 5 doğru24 × 5 = 1206
—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.0

sonuc 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ü

Tanım 3.1.14

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ı: 7

15 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.

Tablo 3.1.14: Girdi doğrulama döngüsünün 12, −3, 4 girdileriyle izlenmesi.
TurOkunan sayisayi < 1sayi > 10KoşulSonuç
112yanlışdoğrudoğrusoru tekrarlanır
2−3doğrusınanmazdoğrusoru tekrarlanır
34yanlışyanlışyanlışdöngüden çıkılır
klavye girdileri12-34sayi = -3-3 < 1 || -3 > 10 → truetekrar sor
Şekil 3.1.19: Girdi kuyruğu, ikinci tur. 12 okunmuş ve reddedilmiştir; şimdi −3 okunmuştur. -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 = 11

i = 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ı

Tablo 3.1.15: for, while ve do-while döngülerinin karşılaştırması.
forwhiledo-while
Koşul ne zaman sınanır?Her turdan önceHer turdan önceHer turdan sonra
En az kaç tur?001
Başlangıç / güncellemeBaşlıkta, tek satırdaDöngü dışında / gövdedeDö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 dizimifor (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.

Tanım 3.1.15

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

Tanım 3.1.16

İç 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).

j = 1j = 2i = 11,11,2i = 22,12,2i = 33,13,2
Şekil 3.1.20: 3 satır, 2 sütunluk ızgara. Her hücre gövdenin bir çalışmasıdır: toplam 6.

Ç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.

Tablo 3.1.16: İç içe döngünün izlenmesi.
AdımijOlayEkran
111iç döngü başlari = 1, j = 1
212i = 1, j = 2
31 → 233 <= 2 yanlış: iç döngü biter, i++
421iç döngü yeniden başlari = 2, j = 1
522i = 2, j = 2
62 → 33iç döngü biter, i++
731iç döngü yeniden başlari = 3, j = 1
832i = 3, j = 2
93 → 43iç 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.

j = 1j = 2j = 3j = 4i = 1*i = 2**i = 3***i = 4****
Şekil 3.1.21: Yıldız deseninin son hâli. 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.

Sık yapılan hata

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 --> 49

sayi = 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.

j=1j=2j=3j=4i=11234i=22468i=336912i=4481216iç döngü: j = 1…10
Şekil 3.1.22: Çarpım tablosunun 4 × 4 köşesi. İşaretli satır 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.

Tanım 3.1.17

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.

1✓2✓3✓4✓5dur678910i
1✓2atla3✓4atla5✓6atla7✓8atla9✓10atlai
Şekil 3.1.23: Üstte 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.

Tablo 3.1.17: Asal sayı programının birkaç girdiyle izlenmesi.
sayiDenenen bölenlerDenemeasalMi
212 (kalan 1), 3 (kalan 0) → break2false
972, 3, …, 48; hiçbiri bölmez47true
912, 3, 4, 5, 6, 7 (kalan 0) → break6false
2yok (2 <= 1 yanlış)0true
1yok; bayrak sayi < 2 ile indirilir0false

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ı: 9

i = 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.

i = 1i ≤ 10 ?i == 5 ?breakYaz ii++sonraEHHE i = 1i ≤ 10 ?i çift mi?continueYaz ii++sonraEHHE
Şekil 3.1.24: Solda 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 1

Her 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
bitti

dis: 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.

j = 1j = 2j = 3i = 11 × 11 × 21 × 3i = 22 × 12 × 22 × 3i = 33 × 13 × 23 × 3break dis → iki döngüden çıkış
Şekil 3.1.25: Etiketli 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 1

Etiketler 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.

Tablo 3.1.18: Karar yapılarında sık yapılan hatalar.
HataÖrnekSonuçDoğrusu
= ile karşılaştırmaif (x = 5)incompatible types: int cannot be converted to booleanif (x == 5)
if'ten sonra noktalı virgülif (x > 5); { … }Derlenir; blok her zaman çalışırNoktalı virgülü silmek
Parantezsiz blokif (k) a(); b(); else c();'else' without 'if'Birden çok deyim için { }
Parantezsiz blok, else'sizif (k) a(); b();Derlenir; b() her zaman çalışırif (k) { a(); b(); }
Aralığı matematikteki gibi yazmak60 <= not < 90bad operand types for binary operator '<'not >= 60 && not < 90
Zincirde yanlış sıraif (not >= 60) … else if (not >= 90) …Derlenir; 90 dalına hiç ulaşılmazEşikleri büyükten küçüğe dizmek
break'i unutmakcase 2: … case 3: …Derlenir; alttaki case'ler de çalışırHer case'i break ile bitirmek
default olmadan atamaDeğişken yalnız case'lerde atanıyorvariable mevsim might not have been initializeddefault eklemek
Değişken case etiketiint b = 2; … case b:constant expression requiredSabit ya da final değişken
Ondalık girişte noktaTürkçe ayarda nextDouble() için -7.5InputMismatchException-7,5 yazmak ya da useLocale
Tablo 3.1.19: Döngülerde sık yapılan hatalar.
HataÖrnekSonuçDoğrusu
Güncellemeyi unutmakwhile (i < 5) { println(i); }Sonsuz döngü; derleyici yakalamazGövdede i++
Yanlış yönde güncellemefor (int i = 0; i <= 10; i--)Pratikte sonsuz döngüi++
Döngü başlığında noktalı virgülwhile (i < 5); { i++; }Gövdesi boş sonsuz döngüNoktalı virgülü silmek
Sınır hatası“1'den 10'a” için i < 10Derlenir; 9 tur döneri <= 10; ilk ve son turu denetlemek
Döngü değişkenini dışarıda kullanmakfor (int i …) { } sonra println(i)cannot find symboli'yi döngüden önce tanımlamak
break'ten sonra kodbreak; System.out.println(i);unreachable statementSatırı break'in önüne almak
Yanlış başlangıç değeriint faktoriyel = 0;Derlenir; sonuç hep 0Toplam 0, çarpım 1 ile başlar
Biriktiriciyi döngüde sıfırlamakGövdede int toplam = 0;Her turda sıfırlanır; döngü dışından erişilemezDöngüden önce tanımlamak
while'da continueif (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 yazarlong

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 true ya da false olan boolean ifadedir. = atama, == karşılaştırmadır; Java if (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 yoksa if'e yalnız ilk deyim bağlıdır; girinti anlamsızdır.
  • else if zinciri: 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üz else'i olmayan en yakın if'e bağlanır. Derin yapılar && ile düzleştirilir.
  • Kısa devre: && sol taraf false, || sol taraf true ise sağ tarafı hesaplamaz. b != 0 && a / b > 2 güvenlidir; sağ tarafa yan etkili işlem yazılmaz. ?: iki değerden birini seçen ifadedir.
  • switch: bir değeri sabit case etiketleriyle karşılaştırır. case bir giriş noktasıdır; break yoksa akış alttaki case'lere düşer. Seçici byte, short, char, int, sarmalayıcıları, String ya da enum olabilir; etiketler sabit ve tektir.
  • switch ifadesi (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 (int için default zorunlu).
  • 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.
  • while ve do-while: while koşulu önce sınar, 0 tur dönebilir; do-while sonra sınar, en az 1 tur döner ve sonunda ; ister. Girdi doğrulama do-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. int 12!'ye, long 20!'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.
  • break ve continue: break en yakın döngüyü bitirir, continue o turun kalanını atlar. Etiketli break dis; dıştaki adlandırılmış döngüden de çıkar. Asal sayı aramasında bayrak ve break birlikte kullanılır; sınır bolen * bolen <= sayi ile kısaltılır.
  • Hatalar: sonsuz döngü, sınır hatası, yanlış koşul sırası ve unutulan break derlenir ama yanlış çalışır; bunlar elle izlemeyle bulunur.

3.1.12Kavram listesi

3.1.13Kendini Dene

  1. 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 < 15 yanlış olduğu için “soğuk” halkasını geçer ve “ılık”a düşer. 0 da 0 < 0 yanlış olduğu için “donuyor” olmaz.

  2. int a = 0, b = 5; için boolean s = (a != 0) && (b / a > 1) || b > 3; satırı çalıştırılıyor. Program sıfıra bölme hatası verir mi? s nedir?

    Cevap

    Hata vermez; s = true. &&, ||'den önce gelir; ifade ((a != 0) && (b / a > 1)) || (b > 3) diye okunur. a != 0 yanlış olduğu için b / a kısa devreyle hiç hesaplanmaz ve parantez içi false olur. Ardından b > 3 doğru çıkar ve sonuç true'dur.

  3. Aşağıdaki switch k = 3, 1, 5 ve 7 için t'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 iki case'te break olmadığı için akış case 5'e kadar düşer; case 5'teki break default'a geçişi engeller.

  4. int i; for (i = 3; i <= 30; i += 4) { … } döngüsü kaç tur döner? Gövde i'nin hangi değerlerini görür? Döngüden sonra i kaçtır?

    Cevap

    7 tur: 3, 7, 11, 15, 19, 23, 27. Sonraki değer 31'dir ve 31 <= 30 yanlıştır; döngüden sonra i = 31. Tur sayısı ⌊(30 − 3) / 4⌋ + 1 = 6 + 1 = 7 diye de hesaplanabilir.

  5. int n = 100, adim = 0; while (n > 1) { n /= 2; adim++; } kodundan sonra adim ve n kaç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.

  6. int x = 0; do { x += 3; } while (x < 0); ve int y = 0; while (y < 0) { y += 3; } kodlarından sonra x ve y kaçtır?

    Cevap

    x = 3, y = 0. Koşul iki döngüde de ilk sınamada yanlıştır. do-while gövdeyi koşula bakmadan bir kez çalıştırır; while ise koşulu önce sınar ve gövdeye hiç girmez.

  7. Aşağıdaki kodda gövde kaç kez çalışır ve say kaç 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ğin i = 5'te j = 1, 3, 5 için toplam 6, 8, 10'dur.

  8. 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, continue ile atlanır. 5, 6, 7 eklenir (24). 8, 4'e bölündüğü için yine continue ile atlanır; break satırına ulaşmaz. 9'da 9 > 7 doğrudur ve break döngüyü bitirir; 9 ve 10 eklenmez.

  9. 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) K sabit değerli bir final değişkendir ve etiket olabilir. (c) incompatible types: int cannot be converted to boolean. (d) while (true) döngüsünün içinde break olmadığı için hiçbir zaman bitmez ve sonraki satır için unreachable statement hatası verilir.

3.1.14İleri okuma

  • Oracle, The Java Tutorials, “Control Flow Statements”: if, switch, döngüler ve break/continue iç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

  1. 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.
  2. P. Deitel, H. Deitel, Java: How to Program (Early Objects), 11. baskı, Pearson, 2017 (Bölüm 4–5: Control Statements).
  3. A. B. Downey, C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (Bölüm 5: Conditionals and Logic; Bölüm 6: Loops).
  4. 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).
  5. C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson, 2022 (3.8: Control Flow).
  6. Oracle, The Java Tutorials: Control Flow Statements, docs.oracle.com/javase/tutorial/java/nutsandbolts/flow.html.
  7. 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.