Bilgisayar ve Programlamaya Giriş · Ders Notu

Koşullu İfadeler

Şimdiye kadar yazılan Java programları her çalıştırmada aynı satırları aynı sırayla yürütür. Gerçek programlar ise duruma göre davranır. Not 60'tan küçükse “Kaldı”, değilse “Geçti” yazılır. Bölen sıfırsa bölme yapılmaz. Kullanıcı 13. ay girerse uyarı verilir. Bu not, programın bir koşula bakarak farklı yollardan birini seçmesini sağlayan yapıları anlatır. Önce sıralı yürütme ve koşul kavramı ele alınır. Ardından if, if-else, else-if zinciri ve iç içe kararlar gelir. Mantıksal operatörler, kısa devre değerlendirme, koşul operatörü ve dizgi karşılaştırma kararları daha kısa ve güvenli yazmayı sağlar. switch yapısı tek bir değerin ayrık seçeneklerine göre seçim yapar. Son kesimdeki örnekler bütün bu yapıları gerçek problemlerde bir araya getirir. Her program JDK 21 ile derlenip çalıştırılmıştır; verilen çıktılar programların gerçek çıktılarıdır.

Öğrenme çıktıları

Akış
Sıralı, karar ve döngü yapılarını ayırt etmek; bir programın satırlarının hangi sırayla ve hangi koşulda çalıştığını izleme tablosuyla göstermek.
Koşul
Karşılaştırma ve mantıksal operatörlerle boolean değer üreten koşullar yazmak; kısa devre değerlendirmeyi, De Morgan kurallarını ve işlem önceliğini doğru kullanmak.
Karar
if, if-else, else-if zinciri ve iç içe kararları doğru sırada kurmak; bağımsız if ile zincir arasındaki farkı, sarkan else sorununu ve süslü parantezin rolünü açıklamak.
switch
Tek bir değerin ayrık seçeneklerinde switch ifadesi ve deyimi kullanmak; default, yield, break ve düşme davranışını açıklamak; if-else ile switch arasında seçim yapmak.

7.1Akış yapıları

Bir program, bilgisayara verilen deyimlerin listesidir. Deyim (statement), programın tek bir işi yapan en küçük tam parçasıdır: bir değişken bildirmek, bir değer atamak, ekrana bir satır yazmak. Java'da her basit deyim noktalı virgülle biter. Bilgisayarın bu deyimleri hangi sırayla çalıştıracağına akış (control flow) denir. Akışı belirleyen dil yapılarının genel adı da kontrol yapısıdır (control structure).

7.1.1Üç temel akış

Ne kadar büyük olursa olsun her algoritma yalnızca üç akış biçimiyle yazılabilir. Şekil 7.1 bu üç biçimi yan yana gösterir.

SıralıKarar (seçme)Döngü (tekrar)deyim 1deyim 2deyim 3koşul?truefalseA yoluB yoluyalnızca bir yol çalışırkoşul?truegövdefalsetrue oldukça tekrar eder
Şekil 7.1: Üç temel akış. Solda sıralı yapı: deyimler yazıldıkları sırayla birer kez çalışır. Ortada karar yapısı: koşulun değerine göre iki yoldan yalnızca biri çalışır. Sağda döngü yapısı: koşul doğru kaldıkça gövde tekrar eder.
Tanım 7.1

Sıralı yapı (sequence structure), deyimlerin yazıldıkları sırayla ve her birinin tam bir kez çalıştığı akıştır. Karar yapısı (selection structure), bir koşulun değerine göre birden çok yoldan birinin seçildiği akıştır; koşullu yapı adı da kullanılır. Döngü yapısı (repetition structure), bir kod bloğunun bir koşul sağlandığı sürece tekrar tekrar çalıştığı akıştır.

Bu üç yapının her birinin tek bir girişi ve tek bir çıkışı vardır. Bu yüzden yapılar birbirinin içine yerleştirilebilir. Bir kararın dalında sıralı deyimler bulunabilir, bir döngünün gövdesinde bir karar bulunabilir. Bu not karar yapısını ayrıntılı olarak ele alır. Sıralı yapı kararın yapı taşıdır; ilk önce o incelenir.

7.1.2Sıralı yapı

Aşağıdaki dört satır bir üçgenin alanını hesaplar. Taban 8, yükseklik 5 birimdir.

double taban = 8;
double yukseklik = 5;
double alan = taban * yukseklik / 2;
System.out.println(alan);

Satırlar yukarıdan aşağıya çalışır. İlk satır taban değişkenini oluşturur ve içine 8 koyar. Değişkenin tipi double olduğu için tam sayı 8 kendiliğinden 8.0'a dönüşür. Küçük bir tipten büyük bir tipe yapılan bu dönüşüme genişletme (widening conversion) denir. İkinci satır yukseklik için aynı işi yapar. Üçüncü satır önceki iki değişkenin değerini kullanır ve alanı hesaplar. Dördüncü satır sonucu ekrana yazar.

1taban8.02yukseklik5.03alan = 8.0 * 5.0 / 2 = 20.0420.0ekranalan, taban ve yukseklik değer almadan hesaplanamaz
Şekil 7.2: Sıralı işlemler. 1 ve 2 numaralı satırlar taban ve yukseklik değişkenlerine değer verir. 3 numaralı satır bu iki değeri kullanarak alanı 20.0 olarak hesaplar. 4 numaralı satır sonucu ekrana yazar.

Şekil 7.2 satırlar arasındaki bağımlılığı oklarla gösterir. Üçüncü satır, birinci ve ikinci satırın ürettiği değerlere muhtaçtır. Her deyim, kendinden önceki deyimlerin bıraktığı durumu kullanabilir. Sıralı yapının özü budur.

7.1.3Sıralı işlemleri adım adım izlemek

Bir programı elle yürütmenin en güvenilir yolu izleme tablosudur. İzleme tablosunda (trace table) her satır bir adımı, her sütun bir değişkeni gösterir. Bir hücre, o adım bittiğinde değişkenin değerini taşır. Henüz var olmayan değişken için “—” yazılır.

Tablo 7.1: Alan hesabının izleme tablosu. Her satır ilgili deyim bittiğindeki durumu gösterir.
AdımÇalışan satırtabanyukseklikalanEkran
1double taban = 8;8.0——
2double yukseklik = 5;8.05.0—
3ataban * yukseklik8.05.0— (ara sonuç 40.0)
3b40.0 / 2 → alan8.05.020.0
4System.out.println(alan);8.05.020.020.0

Üçüncü satır iki adımda incelenmiştir. * ve / aynı önceliktedir ve soldan sağa hesaplanır. Önce 8.0 × 5.0 = 40.0 bulunur, sonra 40.0 / 2 = 20.0 olur. Ancak bu iki adım bittiğinde alan kutusuna değer yazılır. İfadenin ortasında ara sonuç yalnızca işlemcinin içinde bulunur; hiçbir değişkende saklanmaz. Program çalıştırıldığında şu çıktıyı verir:

20.0

Sıralı yapıda satırların yeri sonucu belirler. Üçüncü satır en başa taşınırsa taban ve yukseklik o noktada henüz yoktur. Java bu hatayı program çalışmadan, derleme sırasında yakalar:

public class SiraHatasi {
    public static void main(String[] args) {
        double alan = taban * yukseklik / 2;
        double taban = 8;
        double yukseklik = 5;
        System.out.println(alan);
    }
}
SiraHatasi.java:3: error: cannot find symbol
        double alan = taban * yukseklik / 2;
                      ^
  symbol:   variable taban
  location: class SiraHatasi
SiraHatasi.java:3: error: cannot find symbol
        double alan = taban * yukseklik / 2;
                              ^
  symbol:   variable yukseklik
  location: class SiraHatasi
2 errors

İleti “sembol bulunamadı” anlamına gelir. Derleyici 3. satırda taban adında bir değişken arar ve bulamaz. Aynı ileti yukseklik için de verilir. Java bir değişkenin, bildirildiği satırdan önce kullanılmasına izin vermez.

Sık yapılan hata

Matematikteki gibi “alan = taban × yükseklik / 2” formülünü bir tanım sanmak. Java'da = bir eşitlik bildirimi değil, bir atamadır: sağdaki ifade o anda hesaplanır ve sonuç soldaki kutuya yazılır. Atamadan sonra taban değişse bile alan kendiliğinden güncellenmez. Yeni alan için atamanın yeniden çalışması gerekir.

7.2Koşul ve if

Karar yapısının merkezinde bir soru vardır: “Not 60 ya da daha büyük mü?”, “Sayı negatif mi?”, “Bölen sıfır mı?” Programın hangi yoldan gideceği bu sorunun cevabına bağlıdır. Java'da bu soru bir koşul olarak yazılır.

7.2.1Koşul: boolean değer üreten ifade

Tanım 7.2

Koşul (condition), değeri true (doğru) ya da false (yanlış) olan ifadedir. Bu iki değeri taşıyan Java tipine boolean (mantıksal tip) denir. Bir if yapısının parantezi içinde yalnızca boolean değer üreten bir ifade bulunabilir.

Koşulların çoğu iki değeri karşılaştırarak kurulur. Java'nın altı karşılaştırma operatörü (comparison operator) Tablo 7.2'te verilmiştir. Her biri iki sayıyı alır ve bir boolean değer üretir.

Tablo 7.2: Karşılaştırma operatörleri. Örnek sütununda x = 7'dir.
OperatörAnlamıÖrnekDeğeriTersi
==eşitx == 5false!=
!=eşit değilx != 5true==
<küçükx < 7false>=
<=küçük ya da eşitx <= 7true>
>büyükx > 5true<=
>=büyük ya da eşitx >= 9false<

Tablonun son sütunu her operatörün tersini verir. >'ün tersi < değil, <='dir. Çünkü “7'den büyük değil” demek “7 ya da daha küçük” demektir; 7'nin kendisi de bu kümeye girer. Bu ayrıntı, koşulları tersine çevirirken sık karşılaşılan bir yanılgının kaynağıdır.

Eşitlik sınaması iki eşittir işaretiyle (==) yapılır. Tek eşittir (=) atamadır. İkisini karıştırmak yeni başlayanların en sık yaptığı hatalardan biridir. Neyse ki Java bu karışıklığı derleme sırasında yakalar:

public class AtamaKosul {
    public static void main(String[] args) {
        int x = 7;
        if (x = 5) {
            System.out.println("beş");
        }
    }
}
AtamaKosul.java:4: error: incompatible types: int cannot be converted to boolean
        if (x = 5) {
              ^
1 error

x = 5 bir atamadır. Atamanın değeri atanan sayıdır, yani 5. Bu değerin tipi int'tir. if ise boolean bekler. Derleyici bu yüzden “int, boolean'a dönüştürülemez” iletisini verir. C gibi bazı dillerde sıfır olmayan her sayı “doğru” sayılır ve bu satır sessizce derlenir. Java'da sayı ile boolean ayrı tiplerdir; bu katılık bir hata sınıfını tümüyle ortadan kaldırır.

Matematikteki 10 < x < 20 yazımı da Java'da çalışmaz. İfade soldan sağa okunur: önce 10 < sayi hesaplanır ve bir boolean değer çıkar. Ardından bu boolean değer 20 ile karşılaştırılmaya çalışılır. Boolean bir değer için “küçüktür” anlamsızdır:

public class ZincirKarsilastirma {
    public static void main(String[] args) {
        int sayi = 15;
        if (10 < sayi < 20) {
            System.out.println("arada");
        }
    }
}
ZincirKarsilastirma.java:4: error: bad operand types for binary operator '<'
        if (10 < sayi < 20) {
                      ^
  first type:  boolean
  second type: int
1 error

İleti, < operatörünün iki yanında bir boolean ile bir int bulunduğunu söyler. Doğru yazım iki karşılaştırmayı mantıksal VE ile birleştirir: sayi > 10 && sayi < 20. Mantıksal operatörler Tablo 7.9'te ayrıntılı olarak ele alınır.

7.2.2Tek yollu if

En basit karar, koşul doğruysa bir işi yapmak, yanlışsa hiçbir şey yapmamaktır. Örneğin negatif bir sayıyı pozitife çevirmek için yalnızca sayı negatifken bir işlem gerekir:

if (sayi < 0) {
    sayi = -sayi;
}

Yürütme if satırına geldiğinde önce parantezin içi hesaplanır. Sonuç true ise süslü parantezlerin arasındaki deyimler çalışır. Sonuç false ise bu deyimler atlanır ve yürütme kapanan süslü parantezden sonraki satırdan devam eder. Her iki durumda da program bir sonraki satıra ulaşır; yalnızca aradaki iş yapılır ya da yapılmaz. Bu kalıp mutlak değer örneğinde ayrıntılı olarak izlenir.

7.2.3if-else yapısı

Çoğu kararda iki durumun her biri için ayrı bir iş vardır. Not 60 ya da daha büyükse “Geçti”, değilse “Kaldı” yazılmalıdır. Bunun için if'e bir else dalı eklenir.

Tanım 7.3

if-else yapısı (if-else statement) iki daldan oluşur. Koşul true ise if'in gövdesi, false ise else'in gövdesi çalışır. Her çalıştırmada dallardan tam olarak biri çalışır; ikisi birden ya da hiçbiri çalışmaz. Dal (branch), karar sonrasında izlenebilecek yollardan her biridir.

if (not >= 60) {
    System.out.println("Geçti");
} else {
    System.out.println("Kaldı");
}
truefalsenot okunot >= 60yaz "Geçti"yaz "Kaldı"Son
Şekil 7.3: if-else yapısının akış diyagramı. Not okunduktan sonra not >= 60 koşulu sorulur. true dalı “Geçti”, false dalı “Kaldı” yazar. İki dal Son'da birleşir.

Şekil 7.3'te karar kutusundan iki ok çıkar. Biri true, öteki false diye etiketlenmiştir. Bir not için ikisinden yalnızca biri izlenebilir. Okların sonunda iki yol yeniden birleşir; program kararın ardından aynı noktadan devam eder.

Koşuldaki sınır değeri dikkat ister. not >= 60 yazıldığında 60 “Geçti” tarafına düşer. not > 60 yazılsaydı tam 60 alan öğrenci kalırdı. Bir kararın en sık hatası sınırın yanlış tarafa konmasıdır. Bu yüzden bir karar her zaman sınır değeriyle ve sınırın iki komşusuyla (59 ve 61 gibi) denenir. Her olası değer yalnızca bir dala gider: 0'dan 59'a kadar olan notlar else dalına, 60 ve üstü if dalına.

7.2.4if-else'i adım adım izlemek

Aşağıdaki program notu klavyeden okur. Scanner sınıfı java.util paketindedir; kullanılabilmesi için dosyanın başında import java.util.Scanner; satırı bulunur. Notta bundan sonra kod parçaları kısa tutulmak için yalnızca main'in gövdesi verilir. Tam program ise ayrıca gösterilir.

Scanner klavye = new Scanner(System.in);
int not = klavye.nextInt();
if (not >= 60) {
    System.out.println("Geçti");
} else {
    System.out.println("Kaldı");
}

Program üç kez çalıştırılsın: kullanıcı sırasıyla 75, 60 ve 45 girsin. Tablo 7.3 her çalıştırmanın adımlarını verir.

Tablo 7.3: Geçti/Kaldı programının üç çalıştırması. Satır numaraları yukarıdaki kodun satırlarıdır.
GirdiSatır 2: notSatır 3: koşulÇalışan dalAtlanan dalÇıktı
757575 >= 60 → truesatır 4satır 6Geçti
606060 >= 60 → truesatır 4satır 6Geçti
454545 >= 60 → falsesatır 6satır 4Kaldı
truefalsenot okunot >= 60yaz "Geçti"yaz "Kaldı"Son truefalsenot okunot >= 60yaz "Geçti"yaz "Kaldı"Son
Şekil 7.4: İki çalıştırmada izlenen yol. (a) not = 75: koşul true, “Geçti” kutusu çalışır, “Kaldı” kutusu soluk kalır. (b) not = 45: koşul false, yalnızca “Kaldı” kutusu çalışır.

İkinci çalıştırma sınır değerini dener. 60 >= 60 doğrudur, çünkü >= eşitliği de kapsar. Şekil 7.4 iki çalıştırmada izlenen yolu renkle gösterir. Her çalıştırmada dallardan biri hiç çalışmaz. Tam program ve 75, 60, 45 girdileriyle elde edilen çıktılar şöyledir:

import java.util.Scanner;

public class GectiKaldi {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int not = klavye.nextInt();
        if (not >= 60) {
            System.out.println("Geçti");
        } else {
            System.out.println("Kaldı");
        }
    }
}
Geçti
Geçti
Kaldı

7.2.5Blok, süslü parantez ve boş deyim

Süslü parantezlerin arasındaki deyim grubuna blok (block) denir. Java'nın kuralı şudur: if'in ve else'in gövdesi tek bir deyimdir. Bir blok, içinde kaç deyim olursa olsun tek bir deyim sayılır. Gövde tek bir basit deyimse süslü parantez yazılmayabilir:

if (not >= 60)
    System.out.println("Geçti");
else
    System.out.println("Kaldı");

Bu yazım kısadır ama iki tuzağı vardır. Birincisi, gövdeye ikinci bir satır eklendiğinde ortaya çıkar. Girinti, eklenen satırın da if'e ait olduğunu düşündürür. Oysa derleyici girintiye bakmaz. Süslü parantez yoksa if yalnızca hemen ardından gelen tek deyimi denetler; ikinci satır her durumda çalışır.

public class SusluParantezsiz {
    public static void main(String[] args) {
        int x = 2;
        if (x > 5)
            System.out.println("A");
            System.out.println("B");
        System.out.println("C");
    }
}
B
C

x = 2 için 2 > 5 yanlıştır. Yalnızca println("A") atlanır. “B” satırı girintili yazılmış olsa da if'in dışındadır ve çalışır. Programcının niyeti A ile B'yi birlikte denetlemekse iki satır süslü parantez içine alınmalıdır.

İkinci tuzak, if satırının sonuna yanlışlıkla konan noktalı virgüldür. Tek başına duran noktalı virgül de Java'da bir deyimdir: hiçbir iş yapmayan boş deyim (empty statement). if (x > 0); yazıldığında if'in gövdesi bu boş deyim olur. Alttaki satır artık koşula bağlı değildir.

public class BosDeyim {
    public static void main(String[] args) {
        int x = -3;
        if (x > 0);
            System.out.println("Pozitif");
        System.out.println("Bitti");
    }
}
Pozitif
Bitti

x = −3 olmasına karşın “Pozitif” yazılır. −3 > 0 yanlıştır ve boş deyim atlanır; ama atlanacak başka bir şey yoktur. Derleyici bu satırı hata saymaz, çünkü kural dışı bir şey yoktur. Hata yalnızca programın çıktısında görülür.

Sık yapılan hata

Gövdesi tek satır olan bir if'e sonradan ikinci bir satır eklemek ve süslü parantezi unutmak. Girinti doğru göründüğü için hata gözden kaçar. Bu yüzden pek çok kodlama kılavuzu gövde tek satır olsa bile süslü parantezi zorunlu tutar. Süslü parantez ayrıca if (...); hatasını da görünür kılar: if (x > 0); { satırı okunurken göze batar.

7.3Çok yollu karar

İki seçenekli kararlar if-else ile kurulur. Seçenek sayısı ikiden fazla olduğunda ne olur? Bir notu harf notuna çevirmek dört seçenek ister: 90 ve üstü A, 80–89 B, 70–79 C, 70'in altı F. Bu tür bir karara çok yollu karar (multi-way decision) denir.

7.3.1else-if zinciri

Tanım 7.4

else-if zinciri (else-if chain), her else'in gövdesine yeni bir if konarak kurulan çok yollu karardır. Koşullar yukarıdan aşağıya sırayla denenir. İlk true çıkan koşulun dalı çalışır; zincirin geri kalanı denetlenmeden atlanır. Hiçbir koşul tutmazsa, varsa son else çalışır.

if (not >= 90) {
    harf = 'A';
} else if (not >= 80) {
    harf = 'B';
} else if (not >= 70) {
    harf = 'C';
} else {
    harf = 'F';
}

Zincirin aslında iç içe if-else'lerden oluştuğunu görmek kuralı anlamayı kolaylaştırır. İlk else'in gövdesi ikinci if'tir; onun else'inin gövdesi üçüncü if'tir. Kod her seviyede biraz daha sağa kayacağı için Java programcıları else if'i aynı satıra yazar ve bütün dalları aynı hizada tutar. Dil açısından else if diye ayrı bir sözcük yoktur; yalnızca bir yazım alışkanlığıdır.

truefalsetruefalsetruefalsenot >= 90harf = 'A'not >= 80harf = 'B'not >= 70harf = 'C'harf = 'F'yaz harf
Şekil 7.5: else-if zincirinin akış diyagramı. 90, 80 ve 70 eşikleri soldan sağa sırayla denenir. Bir koşul true olduğunda o dalın ataması yapılır ve yürütme doğrudan “yaz harf” kutusuna gider. Üç koşul da false ise harf = 'F' olur.

Şekil 7.5 zincirin biçimini gösterir. Her karar kutusunun false oku bir sonraki karara gider. true okları ise aşağıya, atama kutularına iner. Bütün yollar sonunda “yaz harf” kutusunda birleşir. Bir çalıştırmada en fazla bir atama kutusu çalışır.

7.3.2Zinciri adım adım izlemek

Harf notu programının kısa yazımı aşağıdadır. Dallar tek deyimli olduğu için süslü parantez kullanılmamıştır; koşullar ve atamalar hizalanarak okunurluk korunmuştur.

int not = klavye.nextInt();
char harf;
if (not >= 90)      harf = 'A';
else if (not >= 80) harf = 'B';
else if (not >= 70) harf = 'C';
else                harf = 'F';
System.out.println(harf);

İkinci satırdaki char harf; değişkeni bildirir ama değer vermez. Değeri, zincirin dallarından biri atar. Program 75, 95 ve 50 için yürütülsün.

Tablo 7.4: else-if zincirinin üç çalıştırması. “atlandı”, koşulun hiç hesaplanmadığını gösterir.
notnot >= 90not >= 80not >= 70Çalışan dalDenenen koşulÇıktı
75falsefalsetrueharf = 'C'3C
95trueatlandıatlandıharf = 'A'1A
50falsefalsefalseharf = 'F' (else)3F

75 için ilk iki koşul yanlıştır: 75 >= 90 ve 75 >= 80. Üçüncü koşul 75 >= 70 doğrudur; harf = 'C' yapılır. Zincirde başka koşul kalmasa bile kural aynıdır: seçilen daldan sonra yürütme doğrudan zincirin sonuna atlar. 95 için ilk koşul doğrudur; öteki iki koşul hiç hesaplanmaz. 50 için üç koşul da yanlıştır ve else çalışır.

truefalsetruefalsetruefalsenot >= 90harf = 'A'not >= 80harf = 'B'not >= 70harf = 'C'harf = 'F'yaz harf truefalsetruefalsetruefalsenot >= 90harf = 'A'not >= 80harf = 'B'not >= 70harf = 'C'harf = 'F'yaz harf
Şekil 7.6: Zincirde izlenen yol. (a) not = 75: iki false okundan sonra üçüncü koşul true olur ve harf = 'C' kutusu çalışır. (b) not = 50: üç koşul da false; yol en sağdaki harf = 'F' kutusuna ulaşır.

Zincirin sınırları da denenmelidir. Program 90, 89, 80, 70 ve 69 için de çalıştırıldı. Sırasıyla A, B, B, C ve F yazdı. 90 ile 89 arasındaki, 70 ile 69 arasındaki tek birimlik fark harfi değiştirir; her eşik kendi değerini kapsar.

7.3.3Eşiklerin sırası

Zincirdeki koşulların her biri tek başına bir alt sınır koyar: “en az 80”, “en az 70”. Üst sınır yazılmaz. B dalının koşulu not >= 80 && not < 90 olmak zorunda değildir. Çünkü yürütme B'nin koşuluna ancak A'nın koşulu yanlışsa ulaşır; o noktada notun 90'dan küçük olduğu zaten bellidir. Zincirde her koşul, önceki koşulların yanlış olduğu bilgisini kendiliğinden taşır.

Bu kolaylık bir sorumluluk getirir: eşikler büyükten küçüğe yazılmalıdır. Sıra ters çevrildiğinde ne olduğunu aşağıdaki program gösterir.

import java.util.Scanner;

public class TersEsik {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int not = klavye.nextInt();
        if (not >= 60) System.out.println("D");
        else if (not >= 70) System.out.println("C");
        else if (not >= 80) System.out.println("B");
        else if (not >= 90) System.out.println("A");
        else System.out.println("F");
    }
}
D

85 için ilk koşul 85 >= 60 doğrudur ve D yazılır. Zincirin geri kalanı atlanır. Program 95 için de D yazar. 60 ve üstündeki her not D alır. C, B ve A dallarına hiçbir girdiyle ulaşılamaz. Böyle bir kod derleme hatası vermez; hata ancak doğru çıktı bilinen bir girdiyle denendiğinde görülür.

Sık yapılan hata

Alt sınırla kurulan bir zincirde eşikleri küçükten büyüğe dizmek. Genel kural şudur: daha dar koşul daha önce sorulur. “En az 90” koşulunu sağlayan her not “en az 70” koşulunu da sağlar. Geniş koşul önce sorulursa dar koşula sıra gelmez.

7.3.4Bağımsız if ile else-if zinciri

Aynı üç koşul, else sözcükleri olmadan alt alta yazılırsa bambaşka bir program ortaya çıkar. Bu yapıya bağımsız if'ler denir: hiçbiri bir öncekinin else dalında değildir ve her biri ötekilerden bağımsız olarak denetlenir.

int not = 95;
if (not >= 90) System.out.println("A");
if (not >= 80) System.out.println("B");
if (not >= 70) System.out.println("C");
A
B
C

not = 95 için üç koşul da doğrudur ve üç satır yazılır. İlk if'in A yazması ikinci if'i etkilemez; ikinci if yine denenir. Bir öğrenciye tek harf verilmesi beklenirken üç harf çıkar. Aynı koşullar else if ile bağlandığında ise yalnızca ilk doğru dal çalışır:

int not = 95;
if (not >= 90) System.out.println("A");
else if (not >= 80) System.out.println("B");
else if (not >= 70) System.out.println("C");
A
Tablo 7.5: not = 95 için iki yazımın karşılaştırması.
KoşulBağımsız ifelse-if zinciri
not >= 90denenir: true → Adenenir: true → A
not >= 80denenir: true → Bdenenmez
not >= 70denenir: true → Cdenenmez
Çıktı3 satır: A, B, C1 satır: A

Dallar ekrana yazmak yerine bir değişkene atama yaptığında hata daha sinsi olur. Şekil 7.7'te her dal harf'e bir değer atar. Bağımsız yazımda sonraki atama öncekinin üzerine yazar. Ekranda tek bir harf görüldüğü için çıktı “doğru biçimde” görünür, ama harf yanlıştır.

bağımsız ifnot >= 90 → trueharf='A'not >= 80 → trueharf='B'sonuç: harf = 'B'✗ yanlışelse ifnot >= 90 → trueharf='A'not >= 80atlandısonuç: harf = 'A'✓ doğru
Şekil 7.7: not = 95 için iki yazım. Solda bağımsız if'ler: iki koşul da true, ikinci atama birincinin üzerine yazar ve harf = 'B' kalır. Sağda else-if: ilk koşul true olunca ikinci koşul atlanır ve harf = 'A' kalır.
public class BagimsizHarf {
    public static void main(String[] args) {
        int not = 95;
        char harf = 'F';
        if (not >= 90) harf = 'A';
        if (not >= 80) harf = 'B';
        System.out.println(harf);
    }
}
B

Bağımsız if her zaman yanlış değildir. Birbirinden bağımsız soruların her biri ayrı ayrı sorulacaksa doğru yapı odur. Örneğin bir başvuru formunda “ad boş mu?”, “e-posta geçersiz mi?”, “yaş 18'den küçük mü?” soruları için ayrı ayrı uyarı yazılır. Birinin doğru olması ötekilerin sorulmasını engellememelidir. Seçim kuralı şöyledir: seçeneklerden yalnızca biri geçerliyse else if, her biri ayrı ayrı geçerli olabilirse bağımsız if.

7.3.5Son else ve değer almamış değişken

Zincirin sonundaki else hiçbir koşulun tutmadığı durumu yakalar. Bu dal zorunlu değildir. Ama harf notu programında silinirse Java programı derlemez:

public class AtanmamisHarf {
    public static void main(String[] args) {
        int not = 75;
        char harf;
        if (not >= 90)      harf = 'A';
        else if (not >= 80) harf = 'B';
        else if (not >= 70) harf = 'C';
        System.out.println(harf);
    }
}
AtanmamisHarf.java:8: error: variable harf might not have been initialized
        System.out.println(harf);
                           ^
1 error

İleti “harf değişkenine değer verilmemiş olabilir” anlamına gelir. Derleyici bütün yolları inceler. Üç koşul da yanlış olduğunda harf'e hiçbir değer atanmaz. O yoldan println(harf) satırına gelinirse yazılacak bir değer yoktur. Java, değer almamış bir yerel değişkenin okunmasına izin vermez. Bu kural, “değişkende ne olduğu belli olmayan” programları daha derleme aşamasında engeller.

Sorun iki yolla çözülür. Ya son else geri konur, ya da değişkene bildirim sırasında bir başlangıç değeri verilir (char harf = 'F';). İlk yol daha açıktır: her olası notun hangi harfe gittiği kodda tek tek görülür.

7.4İç içe kararlar

Bazı sorular ancak başka bir sorunun cevabı belli olduktan sonra anlam kazanır. Araç kullanma izni için önce yaşa bakılır. Kişi 18 yaşından küçükse ehliyetinin olup olmadığını sormanın anlamı yoktur. Yaş yeterliyse ikinci soru sorulur: ehliyeti var mı?

7.4.1Dal içinde karar

Tanım 7.5

İç içe karar (nested decision), bir karar yapısının dallarından birinin içinde başka bir karar yapısının bulunmasıdır. İçteki koşul yalnızca dıştaki koşul o dalı seçtiğinde denetlenir.

int yas = klavye.nextInt();
boolean ehliyet = klavye.nextBoolean();
if (yas >= 18) {
  if (ehliyet)
    System.out.println("Araç kullanabilir");
  else
    System.out.println("Önce ehliyet almalı");
} else {
  System.out.println("Yaş yetersiz");
}
truefalsetruefalseyas >= 18ehliyetyaz "Araç kullanabilir"yaz "Önce ehliyet almalı"yaz "Yaş yetersiz"
Şekil 7.8: İç içe karar. Önce yas >= 18 sorulur. true dalında ikinci bir karar, ehliyet, bulunur ve iki ayrı ileti yazar. false dalı doğrudan “Yaş yetersiz” yazar.

Şekil 7.8 yapıyı gösterir. Dış kararın true dalında ikinci bir karar kutusu vardır. Bu kutuya yalnızca yaş koşulu sağlandığında ulaşılır. Programın üç olası çıktısı vardır ve her biri ayrı bir yolun sonundadır.

Koddaki nextBoolean(), klavyeden true ya da false sözcüğünü okur ve boolean değere çevirir. if (ehliyet) yazımı da dikkat çeker. ehliyet zaten boolean bir değişkendir; if (ehliyet == true) yazmaya gerek yoktur. Değişkenin kendisi koşuldur.

7.4.2İç içe kararı adım adım izlemek

Program üç girdi çiftiyle çalıştırılsın: (20, true), (20, false) ve (15, true).

Tablo 7.6: İç içe kararın üç çalıştırması.
yasehliyetDış koşul yas >= 18İç koşul ehliyetÇıktı
20true20 >= 18 → truetrueAraç kullanabilir
20false20 >= 18 → truefalseÖnce ehliyet almalı
15true15 >= 18 → falsedenetlenmezYaş yetersiz
truefalsetruefalseyas >= 18ehliyetyaz "Araç kullanabilir"yaz "Önce ehliyet almalı"yaz "Yaş yetersiz" truefalsetruefalseyas >= 18ehliyetyaz "Araç kullanabilir"yaz "Önce ehliyet almalı"yaz "Yaş yetersiz"
Şekil 7.9: İç içe kararda izlenen yol. (a) yas = 20, ehliyet = true: iki koşul da true, “Araç kullanabilir” yazılır. (b) yas = 15: dış koşul false; iç karar kutusu soluk kalır, çünkü hiç denetlenmez.

Üçüncü çalıştırma iç içeliğin anlamını gösterir. 15 yaşındaki kişinin ehliyeti true olarak girilmiştir; gerçek hayatta bu imkânsızdır ama program için önemsizdir. Dış koşul yanlış olduğunda iç if satırına hiç gelinmez. Tam programın gerçek çıktıları:

import java.util.Scanner;

public class Ehliyet {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int yas = klavye.nextInt();
        boolean ehliyet = klavye.nextBoolean();
        if (yas >= 18) {
          if (ehliyet)
            System.out.println("Araç kullanabilir");
          else
            System.out.println("Önce ehliyet almalı");
        } else {
          System.out.println("Yaş yetersiz");
        }
    }
}
Araç kullanabilir
Önce ehliyet almalı
Yaş yetersiz

7.4.3İç içelik yerine &&

İç içe yapı her seviyede kodu biraz daha sağa kaydırır. Üç dört seviye derinlikte hangi else'in hangi if'e ait olduğu zor izlenir. Pek çok durumda iç içe kararlar mantıksal VE (&&) ile birleştirilerek düz bir zincire çevrilebilir:

import java.util.Scanner;

public class EhliyetVe {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int yas = klavye.nextInt();
        boolean ehliyet = klavye.nextBoolean();
        if (yas >= 18 && ehliyet)
            System.out.println("Araç kullanabilir");
        else if (yas >= 18)
            System.out.println("Önce ehliyet almalı");
        else
            System.out.println("Yaş yetersiz");
    }
}

İlk koşul “18 yaşında ya da büyük ve ehliyetli” durumunu yakalar. İkinci koşula yalnızca ilki yanlışsa gelinir. O noktada yas >= 18 doğruysa ehliyet yok demektir. Kalan her durum yaş yetersizliğidir. Program dört girdi çifti için iç içe sürümle aynı çıktıyı verdi:

Tablo 7.7: İç içe sürüm ile && sürümünün dört girdi için çıktıları.
GirdiEhliyet (iç içe)EhliyetVe (&&)
20 trueAraç kullanabilirAraç kullanabilir
20 falseÖnce ehliyet almalıÖnce ehliyet almalı
15 trueYaş yetersizYaş yetersiz
15 falseYaş yetersizYaş yetersiz

Hangi yazımın seçileceği okunabilirliğe bağlıdır. İkinci soru gerçekten ilkine bağlıysa, yani ilk koşul yanlışken ikinci soru anlamsızsa, iç içe yapı bu bağımlılığı açıkça gösterir. Koşullar eşit ağırlıktaysa && ile tek satırda birleştirmek daha kısa ve daha düzdür.

7.4.4Sarkan else

İç içe if'lerde süslü parantez kullanılmadığında bir belirsizlik ortaya çıkar. Aşağıdaki kodda tek bir else ama iki if vardır. else hangisine aittir?

int a = -1, b = 5, x = 0;
if (a > 0)
    if (b > 0)
        x = 1;
    else
        x = 2;
System.out.println(x);
Tanım 7.6

Sarkan else (dangling else), süslü parantezsiz iç içe if'lerde bir else'in hangi if'e bağlandığının yazımdan açıkça anlaşılmaması durumudur. Java'nın kuralı kesindir: else, kendisinden önce gelen ve henüz bir else'i olmayan en yakın if'e bağlanır. Girinti bu kuralı değiştirmez.

Kodda else iç if'in hizasında yazılmıştır; burada girinti kuralla uyumludur. Yine de programcı else'i dış if'e ait sanabilir. Başlangıç değerleri a = −1, b = 5, x = 0 olsun. Adımlar şöyledir:

  1. a > 0 hesaplanır: −1 > 0 yanlıştır.
  2. Dış if'in gövdesi atlanır. Bu gövde, iç if ile onun else'inden oluşan tek bir deyimdir. Dolayısıyla else da atlanır.
  3. x hiç değişmez; ekrana 0 yazılır.
0

Programcı else'i dış if'e ait sanıyorsa 2 bekler. Gerçekte else iç if'e bağlıdır ve iç if'e hiç gelinmediği için o da çalışmaz. Aynı kod farklı girdilerle denendiğinde kural daha da açık görünür:

Tablo 7.8: Sarkan else kodunun dört girdiyle çalıştırılması. Program a, b ve x'in başlangıç değerlerini klavyeden okuyacak biçimde değiştirilmiştir.
abx (başta)a > 0b > 0Çalışan atamaÇıktı
−150falsedenetlenmezyok0
−469falsedenetlenmezyok9
350truetruex = 11
3−20truefalsex = 2 (iç else)2

x = 2 ataması yalnızca a pozitif, b pozitif değilken çalışır. Bu, else'in iç if'e ait olduğunun kanıtıdır. Aynı sonuç, else satırı en sola, dış if'in hizasına kaydırılsa da değişmez. Derleyici boşlukları okumaz.

Programcının niyeti else'i dış if'e bağlamaksa iç if süslü parantezle kapatılır. Kapanan süslü parantez iç if'i bitirir; ardından gelen else artık ona bağlanamaz.

int a = -1, b = 5, x = 0;
if (a > 0) {
    if (b > 0) x = 1;
} else {
    x = 2;
}
System.out.println(x);
2

Aynı başlangıç değerleriyle −1 > 0 yanlıştır. Bu kez else dış if'e aittir ve çalışır: x = 2 olur, 2 yazılır. İki kod arasındaki tek fark bir çift süslü parantezdir; çıktılar ise 0 ve 2'dir.

Sık yapılan hata

Kodun ne yaptığını girintiye bakarak çıkarmak. Girinti yalnızca okura yöneliktir; Java derleyicisi için boşluk ve satır başı anlamsızdır. İç içe if'lerde her gövdeyi süslü parantezle yazmak belirsizliği tümüyle ortadan kaldırır. Geliştirme ortamlarının “kodu biçimlendir” komutu da girintiyi derleyicinin gördüğü yapıya göre yeniden düzenler; sarkan bir else biçimlendirmeden sonra gerçek yerine kayar.

7.5Mantıksal operatörlerle karar

Gerçek kararlar çoğu zaman birden çok koşula bağlıdır. Bir başvuru, kişi 18 yaşında ya da büyükse ve belgesi varsa kabul edilir. Bir gün, cumartesi ya da pazarsa hafta sonudur. Bu “ve”, “ya da” ve “değil” bağlaçlarının Java'daki karşılıkları mantıksal operatörlerdir.

7.5.1&&, || ve !

Tanım 7.7

Mantıksal operatörler (logical operators) boolean değerleri birleştirir ve yeni bir boolean değer üretir. a && b (VE) yalnızca iki taraf da true iken true'dur. a || b (VEYA) en az bir taraf true iken true'dur. !a (DEĞİL) değeri tersine çevirir.

Tablo 7.9: Mantıksal operatörlerin doğruluk tablosu. Dört satır, iki boolean değerin bütün birleşimleridir.
aba && ba || b!a
falsefalsefalsefalsetrue
falsetruefalsetruetrue
truefalsefalsetruefalse
truetruetruetruefalse

||'nin anlamı günlük dildeki “ya … ya …”dan farklıdır. Günlük dilde “ya çay ya kahve” çoğu zaman ikisinden yalnızca birini anlatır. || ise iki taraf da doğruyken de true verir. Başvuru örneği && ile şöyle yazılır:

boolean uygun = yas >= 18 && belgeVar;
if (uygun) {
    System.out.println("Başvuru alınabilir");
}
yas >= 18belgeVar&&uygunikisi de true olmalıbelgeVartruefalseyas >= 18 ↓truefalsetruefalsefalsefalseuygun değeri
Şekil 7.10: yas >= 18 ve belgeVar değerleri && ile birleşerek uygun değerini oluşturur. Sağdaki tabloda dört birleşimden yalnızca ikisi de true olan hücre true verir.

Koşulun sonucu önce uygun adlı boolean bir değişkene atanmıştır. Bu bir zorunluluk değildir; koşul doğrudan if'in parantezine de yazılabilirdi. Ama koşula bir ad vermek kodu okunur kılar: if (uygun) satırı kendini açıklar.

7.5.2Kısa devre değerlendirme

&& ve || önce sol tarafı hesaplar. Sol taraf sonucu tek başına belirliyorsa sağ taraf hiç hesaplanmaz.

Tanım 7.8

Kısa devre değerlendirme (short-circuit evaluation): a && b ifadesinde a false ise sonuç ne olursa olsun false'tur ve b hesaplanmaz. a || b ifadesinde a true ise sonuç true'dur ve b hesaplanmaz. Sağ taraf yalnızca sonucu belirlemek için gerekiyorsa hesaplanır.

Başvuru programı üç girdi için izlensin:

int yas = klavye.nextInt();
boolean belgeVar = klavye.nextBoolean();
boolean uygun = yas >= 18 && belgeVar;
if (uygun) {
    System.out.println("Başvuru alınabilir");
}
Tablo 7.10: uygun = yas >= 18 && belgeVar ifadesinin üç girdi için değerlendirilmesi.
yasbelgeVarSol: yas >= 18Sağ: belgeVaruygunÇıktı
20truetrueokunur: truetrueBaşvuru alınabilir
20falsetrueokunur: falsefalse(çıktı yok)
16truefalseokunmazfalse(çıktı yok)

Üçüncü satırda sol taraf yanlıştır. Sağ tarafın değeri ne olursa olsun VE'nin sonucu yanlış olacaktır; Java belgeVar'a hiç bakmaz. Bu örnekte kısa devre yalnızca küçük bir zaman kazancıdır. Asıl önemi, sağ tarafın ancak sol taraf doğruyken güvenle hesaplanabildiği durumlarda ortaya çıkar. Bölmede bölenin sıfır olmaması böyle bir koşuldur:

if (b != 0 && a / b > 1) { … }

b = 0 ise sol taraf yanlıştır ve a / b hiç hesaplanmaz. Tam sayılarda sıfıra bölme programı durduran bir hatadır. Kısa devre, koşulun sağ tarafını sol tarafın koruması altına alır. Sıra önemlidir: a / b > 1 && b != 0 yazılsaydı bölme denetimden önce yapılırdı.

7.5.3De Morgan kuralları

Başvurunun reddedileceği durumu yazmak gerekirse en doğrudan yol kabul koşulunun başına ! koymaktır: !(yas >= 18 && belgeVar). Bu yazım doğrudur ama okunması zordur. Okur önce parantezin içini, sonra tersini düşünmek zorunda kalır. De Morgan kuralları !'i parantezin içine dağıtarak daha düz bir koşul elde etmeyi sağlar.

Teorem 7.1 (De Morgan kuralları)

Her a ve b boolean değeri için

!(a && b) == (!a || !b) ve !(a || b) == (!a && !b)

eşitlikleri sağlanır. ! parantezin içine dağıtılırken && ile || yer değiştirir ve her terim tersine döner.

Kanıt doğruluk tablosuyla yapılır. a ve b'nin alabileceği yalnızca dört değer birleşimi vardır; dördünde de iki taraf aynı değeri verirse eşitlik her durumda doğrudur. Tablo 7.11 dört satırı verir. Tablo, bir Java programının dört birleşimi tek tek hesaplamasıyla üretilmiştir.

Tablo 7.11: De Morgan kurallarının doğruluk tablosu. Üçüncü ve dördüncü sütun her satırda aynıdır; beşinci ve altıncı sütun da öyle.
ab!(a && b)!a || !b!(a || b)!a && !b
falsefalsetruetruetruetrue
falsetruetruetruefalsefalse
truefalsetruetruefalsefalse
truetruefalsefalsefalsefalse

Kural başvuru koşuluna uygulandığında !(yas >= 18) terimi de sadeleşir. “18'den büyük ya da eşit değil” demek “18'den küçük” demektir. Böylece ret koşulu şu biçimi alır:

// Başvuru reddedilsin: 18 yaşından küçük ya da belge yok
if (!(yas >= 18 && belgeVar)) { … }
// De Morgan ile aynı koşul:
if (yas < 18 || !belgeVar) { … }

İki koşulun eşdeğerliği de bir programla sınandı: yaş 0'dan 40'a kadar, belgeVar iki değeriyle, toplam 41 × 2 = 82 birleşim denendi. 82 birleşimin hepsinde iki koşul aynı sonucu verdi. Karşılaştırmaların tersleri Tablo 7.2'in son sütunundadır: >= ↔ <, > ↔ <=, == ↔ !=.

Örnek 7.1

Bir sıcaklık değerinin 15 ile 30 arasında (iki sınır dahil) olmadığı durum, ! kullanmadan nasıl yazılır?

Çözüm. Aralığın içi s >= 15 && s <= 30'dur. Dışı bunun tersidir: !(s >= 15 && s <= 30). De Morgan kuralıyla &&, ||'ye döner ve her terim tersine çevrilir: !(s >= 15) → s < 15, !(s <= 30) → s > 30. Sonuç: s < 15 || s > 30. Bir sıcaklık aynı anda 15'ten küçük ve 30'dan büyük olamayacağı için koşulda && kalsaydı hiçbir değer onu sağlamazdı.

7.5.4Kısa devresiz & ve |

Java'da tek karakterli & ve | operatörleri de boolean değerlerle kullanılabilir. Mantık sonuçları && ve || ile aynıdır. Fark değerlendirme biçimindedir: tek karakterli operatörler iki tarafı da her zaman hesaplar. Bu fark, sağ taraf bir hata ya da bir yan etki taşıdığında görünür olur.

public class KisaDevre {
    public static void main(String[] args) {
        int a = 10, b = 0;
        if (b != 0 && a / b > 1) {
            System.out.println("büyük");
        }
        System.out.println("&& güvenli");
        if (b != 0 &  a / b > 1) {
            System.out.println("büyük");
        }
        System.out.println("buraya gelinmez");
    }
}
&& güvenli
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at KisaDevre.main(KisaDevre.java:8)

&&'li satır güvenle geçilir ve “&& güvenli” yazılır. &'li satırda ise b = 0 olduğu halde a / b hesaplanır. Java bir istisna (exception) fırlatır: ArithmeticException: / by zero, yani “sıfıra bölme”. Program 8. satırda durur ve son println'e ulaşılmaz.

Yan etkiyi görmenin bir yolu, her çağrıldığında bir sayacı artıran küçük bir metot yazmaktır:

static int sayac = 0;
static boolean kontrol(boolean v) { sayac++; return v; }
Tablo 7.12: Her ifadeden önce sayac = 0 yapılır. Mantık sonucu aynı kalırken sayaç, kaç tarafın hesaplandığını gösterir.
İfadeSonuçsayacAçıklama
kontrol(false) && kontrol(true)false1Sol false; sağ çağrılmaz.
kontrol(false) & kontrol(true)false2İki taraf da çağrılır.
kontrol(true) || kontrol(true)true1Sol true; sağ çağrılmaz.
kontrol(true) | kontrol(true)true2İki taraf da çağrılır.

Tablodaki değerler aşağıdaki programın çıktısıdır. Her satırda önce sonuç, sonra sayaç yazılır.

public class Sayac {
    static int sayac = 0;
    static boolean kontrol(boolean v) { sayac++; return v; }

    public static void main(String[] args) {
        boolean r;
        sayac = 0; r = kontrol(false) && kontrol(true); System.out.println(r + " " + sayac);
        sayac = 0; r = kontrol(false) &  kontrol(true); System.out.println(r + " " + sayac);
        sayac = 0; r = kontrol(true)  || kontrol(true); System.out.println(r + " " + sayac);
        sayac = 0; r = kontrol(true)  |  kontrol(true); System.out.println(r + " " + sayac);
    }
}
false 1
false 2
true 1
true 2

Koşullarda neredeyse her zaman && ve || kullanılır. & ve |'nin asıl kullanım alanı tam sayılar üzerindeki bit işlemleridir.

Sık yapılan hata

Koşulun içine değişken değiştiren bir işlem (++k gibi) koymak. Kısa devre yüzünden bu işlemin çalışıp çalışmadığı sol tarafın değerine bağlı hale gelir. if (k > 0 && ++k > 5) satırında k yalnızca pozitifken artar. Böyle bir kodun davranışını izlemek zordur; artırma koşuldan önce ayrı bir satırda yapılmalıdır.

7.5.5İşlem önceliği

Bir koşulda aritmetik, karşılaştırma ve mantıksal operatörler bir arada bulunabilir. Hangisinin önce hesaplanacağını işlem önceliği (operator precedence) belirler. Koşullarda sık geçen operatörlerin önceliği yüksekten düşüğe Tablo 7.13'te verilmiştir.

Tablo 7.13: Koşullarda sık geçen operatörlerin önceliği (üstteki önce hesaplanır).
SıraOperatörlerTür
1!, tekli -, ++, --tekli
2* / %çarpma türü
3+ -toplama türü
4< <= > >=sıralama karşılaştırması
5== !=eşitlik
6&kısa devresiz VE
7|kısa devresiz VEYA
8&&VE
9||VEYA
10? :koşul operatörü
11= += …atama

Bu sıralama sayesinde yil % 4 == 0 && yil % 100 != 0 parantezsiz yazılabilir. Önce % işlemleri, sonra == ve != karşılaştırmaları, en son && hesaplanır. &&'nin ||'den önce gelmesi de önemlidir: a || b && c ifadesi a || (b && c) olarak okunur. !'in önceliği en yüksektir; bu yüzden bir karşılaştırmanın tersi alınırken parantez gerekir: !(x > 5). Parantezsiz !x > 5 yazımı (!x) > 5 demektir ve x bir sayıysa derlenmez. Öncelikten emin olunamayan her durumda parantez kullanmak hem hatayı önler hem de okura yardım eder.

7.6Koşul operatörü ?:

Bir kararın tek işi bir değişkene iki değerden birini vermekse if-else dört satır tutar:

int enBuyuk;
if (a > b) enBuyuk = a;
else       enBuyuk = b;

Java bu kalıp için daha kısa bir yazım sunar.

Tanım 7.9

Koşul operatörü (conditional operator, üç terimli olduğu için ternary operator da denir) koşul ? değer1 : değer2 biçiminde yazılır. Koşul true ise ifadenin değeri değer1, false ise değer2'dir. Yalnızca seçilen değer hesaplanır.

if-else bir deyimdir: bir iş yapar ama bir değer üretmez. Koşul operatörü ise bir ifadedir (expression): bir değer üretir. Bu yüzden atamanın sağ tarafında, bir metodun argümanında ya da başka bir ifadenin içinde kullanılabilir.

int a = 7, b = 12;
int enBuyuk = (a > b) ? a : b;
System.out.println(enBuyuk);
a7intb12intenBuyuk12inta > bfalse?abakılmadı:btrue→ 12
Şekil 7.11: (a > b) ? a : b ifadesinin a = 7, b = 12 için değerlendirilmesi. Koşul false olduğu için iki noktadan sonraki b seçilir; a'ya bakılmaz. enBuyuk = 12 olur.

Adımlar şöyledir. Önce koşul hesaplanır: 7 > 12 yanlıştır. Koşul yanlış olduğu için iki noktadan (:) sonraki değer, yani b seçilir. İfadenin değeri 12'dir ve enBuyuk'a atanır. Program 12 yazar:

12

Koşul operatörü iç içe de yazılabilir. Bir sayının işaretini “+”, “-” ya da “0” olarak bulan ifade şöyledir:

String isaret = n > 0 ? "+" : n < 0 ? "-" : "0";

Koşul operatörü sağdan gruplanır. İfade n > 0 ? "+" : (n < 0 ? "-" : "0") olarak okunur. n = −5 için ilk koşul yanlıştır; parantezin içi hesaplanır ve −5 < 0 doğru olduğundan “-” seçilir. Kısa ve doğru bir ifadedir; ama üç dört seviye iç içe koşul operatörü okunmaz hale gelir. Koşul operatörü en çok iki seçenekli, kısa değer seçimlerinde işe yarar. Seçenekler çoğaldığında else-if zinciri ya da switch daha açıktır.

Koşul operatörünün iki değeri uyumlu tipte olmalıdır. int m = a < b ? a : "yok"; derlenmez, çünkü ifadenin bir dalı int, öteki dizgidir ve sonuç bir int değişkene konamaz.

7.7Dizgileri karşılaştırmak: == ve equals

Sayılar == ile karşılaştırılır. Dizgiler (String) için durum farklıdır ve Java'ya yeni başlayanların en sık takıldığı noktalardan biridir. Bir dizgi değişkeni metnin kendisini değil, bellekte metni tutan nesnenin yerini, yani bir referansı (reference) saklar. == iki referansı karşılaştırır: iki değişken aynı nesneyi mi gösteriyor? İçerik karşılaştırması için equals metodu kullanılır.

String s1 = "java";
String s2 = "java";
String s3 = new String("java");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s3));
true
false
true
değişkenlerdizgi havuzuheap (new ile)s1"java"s2s3"java"
Şekil 7.12: s1 ve s2, dizgi havuzundaki aynı "java" nesnesini gösterir. s3, new ile heap'te oluşturulan ayrı bir "java" nesnesini gösterir. İki nesnenin içeriği aynı, yerleri farklıdır.

Şekil 7.12 üç değişkenin bellekteki durumunu gösterir. Kodda tırnak içinde yazılan bir metne dizgi sabiti (string literal) denir. Java, dizgi sabitlerini dizgi havuzu (string pool) adı verilen ortak bir bölgede tutar. Aynı sabit ikinci kez yazıldığında yeni nesne oluşturulmaz; havuzdaki nesne paylaşılır. Bu yüzden s1 ve s2 aynı nesneyi gösterir. new String("java") ise her zaman yeni bir nesne oluşturur. Bu nesne, new ile oluşturulan nesnelerin tutulduğu heap bölgesindedir.

Tablo 7.14: Üç karşılaştırmanın açıklaması.
İfadeNeyi karşılaştırır?SonuçNeden?
s1 == s2referanstrueİkisi de havuzdaki aynı nesneyi gösterir.
s1 == s3referansfalses3 heap'teki ayrı nesneyi gösterir.
s1.equals(s3)içeriktrueİki nesnenin karakterleri aynıdır: j, a, v, a.

İlk satırın true çıkması tehlikelidir, çünkü =='nin içerik karşılaştırdığı izlenimini verir. Oysa bu sonuç yalnızca havuzun bir yan ürünüdür. Kullanıcıdan okunan bir dizgi havuza girmez; Scanner onu yeni bir nesne olarak oluşturur. Aşağıdaki program bunu gösterir:

import java.util.Scanner;

public class DizgiGirdi {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        String cevap = klavye.next();
        System.out.println(cevap == "evet");
        System.out.println(cevap.equals("evet"));
        System.out.println(cevap.equalsIgnoreCase("EVET"));
    }
}

Kullanıcı “evet” yazdığında çıktı şudur:

false
true
true

Kullanıcı tam olarak “evet” yazmış olsa da cevap == "evet" yanlıştır: biri klavyeden okunan yeni nesne, öteki havuzdaki sabittir. equals doğru sonucu verir. Kullanıcı “Evet” yazdığında ise equals de yanlış verir, çünkü büyük-küçük harfe duyarlıdır: “E” ile “e” farklı karakterlerdir. Harf büyüklüğünü önemsemeyen karşılaştırma equalsIgnoreCase ile yapılır:

false
false
true
Sık yapılan hata

Dizgileri == ile karşılaştırmak. Sabitlerle yapılan küçük denemelerde program doğru çalışıyor gibi görünür; kullanıcıdan okunan ya da program içinde birleştirilen dizgilerde ise sonuç yanlış çıkar. Kural basittir: dizgiler her zaman equals ile karşılaştırılır. Dizgi değişkeni null olabiliyorsa sabit başa yazılır: "evet".equals(cevap). Bu yazım, cevap null olduğunda hata vermez, yalnızca false döndürür.

7.8switch

else-if zinciri her türlü çok yollu kararı kurabilir. Ancak bazı kararlarda bütün koşullar aynı değişkenin aynı biçimde sınanmasıdır: ay == 1, ay == 2, … ay == 12. On iki ayı mevsimlere ayırmak için uzun bir || zinciri gerekir. switch yapısı bu tür kararları daha kısa ve okunur kılar.

7.8.1switch ne zaman kullanılır?

switch (ay)case 12, 1, 2→ "Kış"case 3, 4, 5→ "İlkbahar"case 6, 7, 8→ "Yaz"case 9, 10, 11→ "Sonbahar"default→ "Geçersiz"
Şekil 7.13: switch ifadesi ay değerine göre doğrudan eşleşen case'e gider. Her case bir ya da birkaç sabit değer listeler; hiçbiri eşleşmezse default seçilir.

switch, tek bir ifadenin değerini bir dizi sabitle karşılaştırır ve eşleşen seçeneğe gider. Şekil 7.13'te ay değeri beş seçenekten birine yönlenir. Seçenekler case etiketleriyle (case label) yazılır. Her etiket bir ya da birkaç sabit değer taşır. Hiçbir etiket eşleşmezse default seçilir.

else-if zincirinde koşullar sırayla denenir. switch'te ise program, değerin eşleştiği case'e doğrudan gider; arada kalan etiketler “denenip geçilmez”. Bu yüzden switch'in uygun olduğu yer, tek bir değişkenin ayrık değerlerine göre yapılan seçimlerdir: ay numarası, gün numarası, menü seçeneği, işlem işareti. Aralık koşulları (“not 85'ten büyükse”) ve birden çok değişkene bağlı karmaşık koşullar if-else ile yazılır.

Bir case etiketindeki değer derleme sırasında bilinen bir sabit olmalıdır: 4, 'A', "+" gibi. Bir değişken etiket olamaz:

public class DegiskenCase {
    public static void main(String[] args) {
        int ay = 4, sinir = 4;
        switch (ay) {
            case sinir -> System.out.println("eşit");
            default -> System.out.println("farklı");
        }
    }
}
DegiskenCase.java:5: error: constant expression required
            case sinir -> System.out.println("eşit");
                 ^
1 error

switch'in sınadığı ifade int, char, String gibi tiplerden olabilir. double ve boolean değerler switch ile sınanamaz; bunlar için if-else kullanılır.

7.8.2switch ifadesi

Java 14 ile dile giren yeni yazımda her case bir ok (->) ile yazılır ve switch bir değer üretebilir.

Tanım 7.10

switch ifadesi (switch expression), seçilen case'in değerini sonuç olarak üreten switch'tir: x = switch (…) { … };. switch deyimi (switch statement) ise bir değer üretmez; yalnızca seçilen case'in kodunu çalıştırır. Okla yazılan her case'e ok biçimi denir; ok biçiminde yalnızca seçilen case çalışır.

String mevsim = switch (ay) {
    case 12, 1, 2 -> "Kış";
    case 3, 4, 5 -> "İlkbahar";
    case 6, 7, 8 -> "Yaz";
    case 9, 10, 11 -> "Sonbahar";
    default -> "Geçersiz";
};
123456789101112KışİlkbaharYazSonbaharay1..1213 gibi değer → default: "Geçersiz"
Şekil 7.14: Mevsim çarkı. 12, 1 ve 2 Kış; 3, 4 ve 5 İlkbahar; 6, 7 ve 8 Yaz; 9, 10 ve 11 Sonbahar'dır. 1–12 dışındaki değerler default'a, “Geçersiz”e gider.

Koddaki her satır bir case'tir. case 12, 1, 2 -> "Kış"; satırı “ay 12, 1 ya da 2 ise değer "Kış"tır” demektir. Bir case'te virgülle ayrılmış birden çok değer listelenebilir. Bu sayede on iki ay beş satırda karşılanır (Şekil 7.14). switch ifadesinin bütünü bir değerdir; bu değer mevsim değişkenine atanır. Atama deyimi olduğu için kapanan süslü parantezden sonra noktalı virgül gelir: };.

Bir switch ifadesi her olası değer için bir sonuç üretmek zorundadır. int 4 milyardan fazla değer alabilir; case'ler bunların yalnızca on ikisini listeler. Bu yüzden default zorunludur. Silindiğinde Java programı derlemez:

DefaultYok.java:4: error: the switch expression does not cover all possible input values
        String mevsim = switch (ay) {
                        ^
1 error

İleti “switch ifadesi bütün olası girdi değerlerini kapsamıyor” anlamına gelir. switch deyiminde ise default isteğe bağlıdır; hiçbir case eşleşmezse switch hiçbir şey yapmadan biter. Aynı değer iki case'te birden geçerse de derleyici hata verir, çünkü değerin hangi dala gideceği belirsiz kalır:

YinelenenCase.java:6: error: duplicate case label
            case 2, 3, 4, 5 -> "İlkbahar";
            ^
1 error

7.8.3switch ifadesini adım adım izlemek

int ay = klavye.nextInt();
String mevsim = switch (ay) {
    case 12, 1, 2 -> "Kış";
    case 3, 4, 5 -> "İlkbahar";
    case 6, 7, 8 -> "Yaz";
    case 9, 10, 11 -> "Sonbahar";
    default -> "Geçersiz";
};
System.out.println(mevsim);
Tablo 7.15: Mevsim programının üç çalıştırması.
ayEşleşen caseswitch'in değerimevsimÇıktı
4case 3, 4, 5"İlkbahar""İlkbahar"İlkbahar
12case 12, 1, 2"Kış""Kış"Kış
13yok → default"Geçersiz""Geçersiz"Geçersiz

ay = 4 için program, 4'ün listede bulunduğu case 3, 4, 5 satırına gider. Bu case'in değeri "İlkbahar"dır. switch ifadesinin değeri olarak mevsim'e atanır ve yazılır. Başka hiçbir case'in kodu çalışmaz. ay = 13 hiçbir listede yoktur; default seçilir. Program ayrıca 1, 7, 10, 0 ve −3 için denendi: Kış, Yaz, Sonbahar, Geçersiz ve Geçersiz yazdı.

İlkbahar
Kış
Geçersiz

7.8.4Bloklu dal ve yield

Ok biçiminde bir case'in sağ tarafı tek bir ifadedir. Bazen bir dal, değeri üretmeden önce başka işler de yapmak zorundadır: bir ileti yazmak, bir ara değer hesaplamak. Böyle bir dal süslü parantezle bir bloğa alınır ve değer yield sözcüğüyle verilir.

Tanım 7.11

yield, bir switch ifadesinin bloklu dalında o dalın ürettiği değeri belirten deyimdir. yield çalıştığında blok biter ve verilen değer switch ifadesinin değeri olur.

int yil = 2024, ay = 2;
int gun = switch (ay) {
    case 4, 6, 9, 11 -> 30;
    case 2 -> {
        System.out.println("Şubat");
        yield yil % 4 == 0 ? 29 : 28;
    }
    default -> 31;
};
System.out.println(gun);
Şubat
29

ay = 2 olduğu için bloklu dal seçilir. Blok önce “Şubat” yazar. Ardından yield satırı çalışır. Buradaki değer bir koşul operatörüyle hesaplanır: 2024 % 4 = 0 olduğundan yil % 4 == 0 doğrudur ve 29 seçilir. switch ifadesinin değeri 29 olur, gun'e atanır ve yazılır. Çıktı iki satırdır: “Şubat” ve “29”.

Aynı program farklı girdilerle de çalıştırıldı. 2023 yılının şubatı için “Şubat” ve 28 yazdı (2023 % 4 = 3). Nisan için yalnızca 30, ocak için yalnızca 31 yazdı; bu iki çalıştırmada blok seçilmediği için “Şubat” satırı da yazılmadı. Bu örnekte artık yıl kuralı basitleştirilmiştir: yalnızca 4'e bölünebilmeye bakılır. Bu yüzden program 1900 için de 29 yazar; oysa 1900 artık yıl değildir. Tam kural artık yıl örneğinde verilmiştir.

Bloklu dal yield olmadan biterse dalın bir değeri olmaz. Java bunu derleme sırasında yakalar:

public class YieldYok {
    public static void main(String[] args) {
        int yil = 2024, ay = 2;
        int gun = switch (ay) {
            case 4, 6, 9, 11 -> 30;
            case 2 -> {
                System.out.println("Şubat");
            }
            default -> 31;
        };
        System.out.println(gun);
    }
}
YieldYok.java:8: error: switch rule completes without providing a value
            }
            ^
  (switch rules in switch expressions must either provide a value or throw)
1 error

İleti “switch kuralı bir değer vermeden tamamlanıyor” anlamına gelir; ek satır, switch ifadelerindeki kuralların ya bir değer vermesi ya da bir istisna fırlatması gerektiğini hatırlatır.

7.8.5Klasik switch, break ve düşme

Ok biçiminden önce Java'da switch yalnızca iki nokta (:) ile yazılırdı. Bu klasik yazım bugün de geçerlidir ve eski kodlarda sıkça görülür. Klasik switch'te case etiketleri yalnızca birer giriş noktasıdır. Program eşleşen etiketten içeri girer ve break görene ya da switch'in sonuna gelene kadar alttaki bütün kodu çalıştırır.

Tanım 7.12

Düşme (fall-through), klasik switch'te bir case'in kodu break olmadan bittiğinde yürütmenin bir sonraki case'in koduna geçmesidir. Bu sırada sonraki case'in etiketi denetlenmez. break deyimi switch'ten çıkar.

int gun = 2;
switch (gun) {
  case 1: System.out.println("Pzt"); break;
  case 2: System.out.println("Salı");
  case 3: System.out.println("Çar"); break;
  default: System.out.println("Yok");
}
Salı
Çar
case 1:"Pzt"breakcase 2:"Salı"break yok!case 3:"Çar"breakdefault:"Yok"break yok!
Şekil 7.15: gun = 2 için klasik switch. Yürütme doğrudan case 2'ye girer ve “Salı” yazar. case 2'de break olmadığı için case 3'ün koduna düşer ve “Çar” yazar. case 3'teki break switch'ten çıkarır; default çalışmaz.

Adımlar şöyledir. gun = 2'dir. Program doğrudan case 2: etiketine gider; case 1 denetlenmez. “Salı” yazılır. Bu satırda break yoktur. Yürütme alttaki satıra, case 3:'ün koduna geçer. Etiketin 3 olması önemsizdir; etiket yalnızca giriş noktasıdır. “Çar” yazılır. Bu satırdaki break switch'ten çıkar ve default çalışmaz (Şekil 7.15). Aynı program gun = 1 için yalnızca “Pzt”, 3 için yalnızca “Çar”, 9 için “Yok” yazdı.

Düşme bazen bilerek kullanılır: birkaç case'in aynı işi yapması için etiketler alt alta yazılır ve kodu yalnızca sonuncusuna konur. Ok biçiminde bu ihtiyaç virgüllü liste ile karşılanır. Aşağıdaki program hafta içi ve hafta sonu günlerini düşmeye gerek kalmadan ayırır:

import java.util.Scanner;

public class GunTuru {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int gun = klavye.nextInt();
        switch (gun) {
            case 1, 2, 3, 4, 5 -> System.out.println("Hafta içi");
            case 6, 7 -> System.out.println("Hafta sonu");
            default -> System.out.println("Geçersiz gün");
        }
    }
}
Hafta içi
Hafta sonu
Geçersiz gün

Ok biçiminde düşme hiçbir zaman olmaz: seçilen case'in kodu çalışır ve switch biter. break yazmaya gerek kalmaz. Yeni kodda ok biçimini kullanmak, unutulan bir break'in yol açtığı hatayı baştan önler.

Sık yapılan hata

Klasik switch'te break'i unutmak. Program derlenir, çoğu girdide doğru da çalışabilir; hata yalnızca break'i eksik case seçildiğinde, fazladan yazılan satırlarla ortaya çıkar. Bir başka yanılgı da default'un her zaman en son çalıştığını sanmaktır. default switch'in ortasında da yazılabilir ve düşme kuralı onun için de geçerlidir: break'i yoksa altındaki case'in koduna düşülür.

7.8.6if-else mi, switch mi?

if-else: aralık koşuluKaldıGeçtiPekiyi05085100not < 50   ·   not < 85   ·   elseher dal bir sayı aralığını kapsarswitch: ayrık değerler1234567case 1, 2, 3, 4, 5"Hafta içi"case 6, 7"Hafta sonu"tek bir değişkenin sabit değerleri
Şekil 7.16: Solda if-else ile sayı aralıkları: 50'nin altı Kaldı, 50–85 Geçti, 85 ve üstü Pekiyi. Sağda switch ile tek değişkenin ayrık değerleri: 1–5 hafta içi, 6–7 hafta sonu.

Şekil 7.16 iki yapının doğal kullanım alanlarını yan yana koyar. Not aralıkları süreklidir: 50 ile 85 arasında sonsuz sayıda ondalık değer vardır ve hepsini case olarak listelemek imkânsızdır. Günler ise ayrıktır: yalnızca yedi değer vardır ve her biri adıyla listelenebilir. Tablo 7.16 iki yapıyı birkaç ölçüte göre karşılaştırır.

Tablo 7.16: if-else ile switch'in karşılaştırılması.
Ölçütif-elseswitch
Aralık koşuluUygunDoğrudan uygun değil
Tek değere göre çok seçenekKullanılabilirGenellikle daha açık
Karmaşık mantıkUygunSınırlı
Her dal sonuç üretirAtama gerekirswitch ifadesi değer döndürebilir

Son satır önemli bir avantajı gösterir. else-if zincirinde her dal aynı değişkene ayrı ayrı atama yapar; bir dalda atamanın unutulması kolaydır. switch ifadesinde ise değer tek bir yerde atanır ve derleyici her olası değerin bir sonucu olduğunu denetler.

7.9Örnekler

Bu kesimdeki örnekler, karar yapılarını gerçek problemlerde bir araya getirir. Her örnekte önce problem ve çözüm fikri, sonra kod, ardından birkaç girdi için izleme ve programın gerçek çıktısı verilir. Örneklerin çoğunda kodun kendisi kısadır; asıl iş, hangi girdilerin özel davrandığını bulmaktır.

7.9.1Tek mi çift mi?

Bir tam sayı 2'ye kalansız bölünüyorsa çifttir. Java'da kalan % operatörüyle bulunur. Kalan (remainder), bölme işleminden sonra artan miktardır: 17'yi 2'ye bölünce bölüm 8, kalan 1'dir, çünkü 17 = 2 × 8 + 1.

int sayi = klavye.nextInt();
if (sayi % 2 == 0)
    System.out.println("Çift");
else
    System.out.println("Tek");

Koşulda önce % hesaplanır, sonra sonuç 0 ile karşılaştırılır; %'nin önceliği =='den yüksektir. Program 17, −4 ve −7 için izlensin.

Tablo 7.17: Tek/çift programının üç çalıştırması.
sayisayi % 2sayi % 2 == 0Çıktı
171falseTek
−40trueÇift
−7−1falseTek
Tek
Çift
Tek

Üçüncü satır dikkat ister: -7 % 2 değeri 1 değil, −1'dir. Java'da tam sayı bölmesi sıfıra doğru yuvarlar: −7 / 2 = −3'tür, −4 değil. Kalan da bu bölüme göre hesaplanır: −7 = 2 × (−3) + (−1). Sonuçta kalanın işareti her zaman bölünen sayının işaretiyle aynıdır. Şekil 7.17 −4'ten 4'e kadar sayıların 2'ye göre kalanlarını gösterir.

sayisayi % 2-40-3-1-20-1-100112031400 → Çift1 veya -1 → Tek (negatif tek sayıda kalan -1: == 1 testi yanılır)
Şekil 7.17: −4'ten 4'e kadar sayıların sayi % 2 değerleri. Çift sayılarda kalan 0'dır. Pozitif tek sayılarda kalan 1, negatif tek sayılarda −1'dir.

Şekildeki değerler bir programla da üretildi. İkinci satır −7 için üç koşulu ve 7 % -2'yi yazar:

public class KalanTablosu {
    public static void main(String[] args) {
        for (int sayi = -4; sayi <= 4; sayi++) {
            System.out.print(sayi % 2 + " ");
        }
        System.out.println();
        System.out.println((-7 % 2 == 1) + " " + (-7 % 2 != 0) + " " + (-7 / 2) + " " + (7 % -2));
    }
}
0 -1 0 -1 0 1 0 1 0 
false true -3 1

-7 % 2 == 1 yanlış, -7 % 2 != 0 doğrudur. 7 % -2'nin 1 çıkması da kuralı doğrular: bölen negatif olsa da kalan, bölünen 7'nin işaretini alır.

Sık yapılan hata

Tek sayıyı sayi % 2 == 1 koşuluyla sınamak. Bu koşul pozitif tek sayılarda doğru çalışır, negatif tek sayılarda ise yanlış sonuç verir: −7 “tek değil” sayılır. Güvenli koşullar kalanı 0 ile karşılaştırır: çift için sayi % 2 == 0, tek için sayi % 2 != 0. Bu iki koşul her int için doğrudur.

7.9.2Mutlak değer ve taşma

Bir sayının mutlak değeri (absolute value), sayının sıfıra uzaklığıdır: |−8| = 8, |5| = 5. Sayı negatifse işareti değiştirilir, değilse olduğu gibi bırakılır. Bu, tek yollu if'in tipik örneğidir: negatif olmayan sayılar için yapılacak bir iş yoktur.

int sayi = klavye.nextInt();
if (sayi < 0) {
    sayi = -sayi;
}
System.out.println(sayi);
-10-8-505810sayi < 0 → sayi = -sayi-8 (önce)8 (sonra)0 ve pozitifler değişmez
Şekil 7.18: Mutlak değer sayı doğrusunda. −8, sıfıra göre yansıtılarak 8 olur. 0 ve pozitif değerler değişmez.
Tablo 7.18: Mutlak değer programının üç çalıştırması. Üçüncü girdi Integer.MIN_VALUE'dur.
Girdisayi < 0sayi = -sayiÇıktı
−8true−(−8) = 88
5falseatlanır5
−2147483648true−(−2147483648) → taşma → −2147483648−2147483648
8
5
-2147483648

İlk iki satır beklenen sonucu verir. Üçüncü satırdaki sonuç ise şaşırtıcıdır: bir sayının mutlak değeri negatif çıkmıştır. Nedeni int tipinin sınırlarıdır. Bir int 32 bitle saklanır ve −231 = −2.147.483.648 ile 231 − 1 = 2.147.483.647 arasındaki değerleri alabilir. En küçük değerin, Integer.MIN_VALUE'nun, pozitif karşılığı 2.147.483.648'dir. Bu sayı en büyük int'ten bir fazladır ve int'e sığmaz.

Tanım 7.13

Taşma (overflow), bir işlemin sonucunun, sonucu saklayacak tipin alabileceği değer aralığının dışına çıkmasıdır. Java'da int ve long işlemlerinde taşma bir hata vermez; sonuç aralığın öbür ucuna “sarar”. En büyük int'e 1 eklenince en küçük int elde edilir.

−(−2.147.483.648) işleminin doğru sonucu 2.147.483.648'dir. Bu değer en büyük int'i 1 aşar ve aralığın öbür ucuna sarar: sonuç yine −2.147.483.648 olur. Java'nın hazır Math.abs metodu da aynı davranır. Bu özel durumu doğru ele almanın iki yolu vardır. Birincisi, hesabı daha geniş bir tipte, long'da yapmaktır. İkincisi, Java 15'ten beri bulunan Math.absExact metodunu kullanmaktır; bu metot sessizce yanlış sonuç vermek yerine bir istisna fırlatır.

public class MutlakOzel {
    public static void main(String[] args) {
        int sayi = Integer.MIN_VALUE;
        System.out.println(Integer.MAX_VALUE);
        System.out.println(Math.abs(sayi));
        long uzun = sayi;
        if (uzun < 0) uzun = -uzun;
        System.out.println(uzun);
        System.out.println(Math.absExact(sayi));
    }
}
2147483647
-2147483648
2147483648
Exception in thread "main" java.lang.ArithmeticException: Overflow to represent absolute value of Integer.MIN_VALUE
	at java.base/java.lang.Math.absExact(Math.java:1903)
	at MutlakOzel.main(MutlakOzel.java:9)

İlk satır en büyük int'tir. İkinci satır Math.abs'ın da negatif sonuç verdiğini gösterir. Üçüncü satırda değer long'a kopyalanmıştır; long'un aralığı çok daha geniştir ve doğru sonuç, 2147483648, elde edilir. Son satırda Math.absExact taşmayı fark eder ve ArithmeticException fırlatır. İleti “Integer.MIN_VALUE'nun mutlak değerini göstermek taşmaya yol açar” anlamındadır.

7.9.3Artık yıl

Dünya Güneş'in çevresindeki bir turu yaklaşık 365,2425 günde tamamlar. Takvim bu fazlalığı artık yıllarla, yani 366 günlük yıllarla dengeler. Gregoryen takvimin kuralı üç basamaklıdır:

  1. 4'e bölünebilen yıllar artık yıldır;
  2. ama 100'e bölünebilen yıllar artık yıl değildir;
  3. ama 400'e bölünebilen yıllar yine artık yıldır.

Kural tek bir boolean ifadede toplanabilir: “4'e bölünür ve 100'e bölünmez” ya da “400'e bölünür”.

int yil = klavye.nextInt();
boolean artik =
    (yil % 4 == 0 && yil % 100 != 0)
    || yil % 400 == 0;
if (artik)
    System.out.println(yil + " artık yıl");
else
    System.out.println(yil + " normal yıl");

Parantez, &&'li kısmın bir bütün olduğunu gösterir. &&'nin önceliği ||'den yüksek olduğu için parantez olmasa da sonuç değişmezdi; ama parantez kuralın yapısını okura açıkça gösterir. Program dört yıl için izlensin.

Tablo 7.19: Artık yıl koşulunun dört yıl için değerlendirilmesi. “bakılmadı”, kısa devre yüzünden hesaplanmayan terimdir.
yilyil % 4 == 0yil % 100 != 0Parantezyil % 400 == 0artik
20240 → true24 → truetruebakılmadıtrue
20233 → falsebakılmadıfalse23 → falsefalse
19000 → true0 → falsefalse300 → falsefalse
20000 → true0 → falsefalse0 → truetrue
yil2024int(yil % 4 == 0true&&yil % 100 != 0true)||yil % 400 == 0bakılmadı→ true
Şekil 7.19: yil = 2024 için koşulun değerlendirilmesi. Parantezin içindeki iki terim de true'dur. Sol taraf true olduğu için || kısa devre yapar; yil % 400 == 0 terimine bakılmaz.

Her satırda iki kısa devre fırsatı vardır. 2024'te parantezin içi doğru çıkar ve ||'nin sağına bakılmaz (Şekil 7.19). 2023'te ilk terim yanlıştır; && ikinci terimi hesaplamaz. Parantezin içi yanlış olduğu için bu kez ||'nin sağı hesaplanır. 1900 ile 2000 kuralın ince noktalarıdır. İkisi de 100'e bölünür ve parantezin içi yanlış çıkar. Fark son terimdedir: 1900 % 400 = 300, 2000 % 400 = 0. Bu yüzden 2000 artık yıldır, 1900 değildir. Program 2100 için “normal yıl”, 1600 için “artık yıl” yazdı.

2024 artık yıl
2023 normal yıl
1900 normal yıl
2000 artık yıl

7.9.4İkinci dereceden denklem

ax² + bx + c = 0 denkleminin reel köklerinin sayısı diskriminanta bağlıdır: Δ = b² − 4ac. Δ pozitifse iki farklı reel kök, sıfırsa tek (çakışık) kök vardır; negatifse reel kök yoktur. Kökler şu formülle bulunur:

x1,2 = (−b ± √Δ) / (2a)

Üç durum üç dallı bir else-if zinciriyle ayrılır. Burada a'nın 0 olmadığı varsayılır; a = 0 olsaydı denklem ikinci dereceden olmazdı.

double a = klavye.nextDouble();
double b = klavye.nextDouble();
double c = klavye.nextDouble();
double d = b * b - 4 * a * c;
if (d > 0) {
  double x1 = (-b + Math.sqrt(d)) / (2 * a);
  double x2 = (-b - Math.sqrt(d)) / (2 * a);
  System.out.println(x1 + " ve " + x2);
} else if (d == 0) {
  System.out.println(-b / (2 * a));
} else {
  System.out.println("Reel kök yok");
}
Tablo 7.20: Denklem programının üç çalıştırması. Her satırda farklı bir dal çalışır.
a, b, cd = b² − 4acÇalışan dalHesapÇıktı
1.0, −3.0, 2.09.0 − 8.0 = 1.0d > 0x1 = (3.0 + 1.0) / 2.0 = 2.0, x2 = (3.0 − 1.0) / 2.0 = 1.02.0 ve 1.0
1.0, −2.0, 1.04.0 − 4.0 = 0.0d == 0−b / 2a = 2.0 / 2.0 = 1.01.0
1.0, 0.0, 1.00.0 − 4.0 = −4.0else—Reel kök yok
2.01.01.0x² + (-3.0)x + 2.0iki kök 1.01.0x² + (-2.0)x + 1.0tek kök 1.0x² + (0.0)x + 1.0kök yok
Şekil 7.20: Üç çalıştırmanın parabolleri. (a) Δ = 1.0: parabol x eksenini 1.0 ve 2.0'de keser. (b) Δ = 0.0: parabol eksene 1.0'de teğettir. (c) Δ = −4.0: parabol ekseni kesmez.
2.0 ve 1.0
1.0
Reel kök yok

Şekil 7.20 her çalıştırmanın geometrik anlamını verir. Program 2x² − 7x + 3 = 0 için de denendi: Δ = 49 − 24 = 25, √Δ = 5; kökler (7 + 5) / 4 = 3.0 ve (7 − 5) / 4 = 0.5'tir. Program “3.0 ve 0.5” yazdı.

else dalı yalnızca bir ileti yazmakla kalmaz; programı bir hatadan da korur. Negatif bir sayının karekökü reel sayılarda tanımsızdır. Java'da Math.sqrt(-4.0) bir istisna fırlatmaz, NaN (Not a Number, “sayı değil”) değerini döndürür. Ondalık sayılarda sıfıra bölme de hata vermez; sonsuzluk (Infinity) üretir:

public class OndalikOzel {
    public static void main(String[] args) {
        System.out.println(Math.sqrt(-4.0));
        System.out.println(7.0 / 0);
        System.out.println(-7.0 / 0);
        System.out.println(0.0 / 0);
        System.out.println(0.1 + 0.2 == 0.3);
        System.out.println(0.1 + 0.2);
    }
}
NaN
Infinity
-Infinity
NaN
false
0.30000000000000004

NaN ile yapılan her hesap yine NaN verir ve hata, sonuçlar ekrana yazılana kadar fark edilmez. Kararların sırası bu yüzden önemlidir: Math.sqrt(d) yalnızca d > 0 olduğu bilinen dalda çağrılır.

Son iki satır ondalık sayıların başka bir inceliğini gösterir. 0.1 + 0.2 tam olarak 0.3 değildir; ikili sistemde 0.1 ve 0.2 tam gösterilemez ve küçük bir yuvarlama farkı kalır. d == 0 karşılaştırması bu örnekte güvenlidir, çünkü girdiler tam sayılardır ve b² − 4ac tam olarak hesaplanır. Ölçümden gelen ondalık değerlerde ise == yerine küçük bir tolerans kullanılır: Math.abs(d) < 1e-9.

7.9.5Üç sayının en büyüğü

Üç sayının en büyüğünü bulmanın bir yolu her olasılığı ayrı bir koşulla yazmaktır: “a, b'den ve c'den büyükse …”. Bu yaklaşım üç sayıda bile karmaşıklaşır, beş sayıda içinden çıkılmaz hale gelir. Daha iyi bir yol “aday ve karşılaştır” kalıbıdır: ilk sayı aday kabul edilir, sonra her sayı adayla karşılaştırılır ve adaydan büyükse aday olur.

int a = klavye.nextInt();
int b = klavye.nextInt();
int c = klavye.nextInt();
int enBuyuk = a;
if (b > enBuyuk)
    enBuyuk = b;
if (c > enBuyuk)
    enBuyuk = c;
System.out.println("En büyük: " + enBuyuk);
Tablo 7.21: “Aday ve karşılaştır” kalıbının üç çalıştırması. Son sütun her adımdan sonraki aday değeridir.
Girdi (a, b, c)AdımKoşulenBuyuk
7, 12, 5enBuyuk = a—7
b > enBuyuk12 > 7 → true12
c > enBuyuk5 > 12 → false12
3, 3, 9enBuyuk = a—3
b > enBuyuk3 > 3 → false3
c > enBuyuk9 > 3 → true9
8, 2, 4enBuyuk = a—8
b > enBuyuk2 > 8 → false8
c > enBuyuk4 > 8 → false8
7a12b5cenBuyuk12int
Şekil 7.21: (7, 12, 5) girdisinde b ile karşılaştırmadan sonraki durum. Çubuklar a, b ve c'nin büyüklüğünü gösterir; aday enBuyuk = 12 olmuştur.

İkinci girdide 3 > 3 yanlıştır; eşit bir değer adayı değiştirmez. Sonuç yine doğrudur, çünkü eşit değerlerden hangisinin aday olduğu fark etmez. Kalıbın gücü genellenebilmesindedir: n sayı için tam olarak n − 1 karşılaştırma gerekir ve her karşılaştırma aynı biçimdedir. Bu kalıp bir döngünün gövdesine konduğunda istenen sayıda sayının en büyüğünü bulur. Java'nın hazır metoduyla aynı iş tek satırda da yapılabilir: Math.max(a, Math.max(b, c)). Tam program iki yöntemin sonucunu da yazar:

import java.util.Scanner;

public class EnBuyuk {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int a = klavye.nextInt();
        int b = klavye.nextInt();
        int c = klavye.nextInt();
        int enBuyuk = a;
        if (b > enBuyuk)
            enBuyuk = b;
        if (c > enBuyuk)
            enBuyuk = c;
        System.out.println("En büyük: " + enBuyuk);
        System.out.println(Math.max(a, Math.max(b, c)));
    }
}
En büyük: 12
12
En büyük: -1
-1
Sık yapılan hata

Adayı ilk sayı yerine 0 ile başlatmak: int enBuyuk = 0;. Girdiler pozitifken bu da doğru sonuç verir. Ama (−2, −7, −1) girdisinde hiçbir sayı 0'dan büyük değildir; aday hiç değişmez ve girdilerden biri bile olmayan 0 yazılır. Doğru program bu girdide −1 yazar. Aday her zaman girdilerden biriyle başlatılır.

7.9.6switch ile hesap makinesi

Kullanıcı iki sayı ile aralarına bir işlem işareti girsin: 8.0 * 2.5. Program işareti dizgi olarak okur ve switch ile ilgili işlemi seçer. Scanner'ın next() metodu boşluğa kadar olan sözcüğü dizgi olarak okur; bu yüzden girdide sayılar ile işaret arasında boşluk bulunur.

double x = klavye.nextDouble();
String op = klavye.next();
double y = klavye.nextDouble();
switch (op) {
  case "+" -> System.out.println(x + y);
  case "-" -> System.out.println(x - y);
  case "*" -> System.out.println(x * y);
  case "/" -> {
    if (y == 0) System.out.println("Hata!");
    else System.out.println(x / y);
  }
  default -> System.out.println("Geçersiz");
}
Tablo 7.22: Hesap makinesinin üç çalıştırması.
GirdiopEşleşen caseEk koşulÇıktı
8.0 * 2.5"*"case "*"—20.0
7.0 / 0.0"/"case "/"y == 0 → trueHata!
5.0 % 3.0"%"yok → default—Geçersiz
8.0 * 2.5"+""-""*""/"default20.0
Şekil 7.22: 8.0 * 2.5 girdisinde switch, op = "*" değerini taşıyan case'e gider ve 20.0 yazar.
20.0
Hata!
Geçersiz

Bölme dalı tek bir ifade değil, içinde if-else bulunan bir bloktur. Bu bir switch deyimidir, değer üretmez; bu yüzden blokta yield gerekmez. Bloktaki denetim gereklidir, çünkü ondalık sayılarda sıfıra bölme bir hata vermez: 7.0 / 0 ifadesi Infinity üretir. Kullanıcıya “Infinity” yazmak yerine anlaşılır bir ileti vermek için bölmeden önce bölen sınanır. Üçüncü çalıştırmada “%” işareti hiçbir case'le eşleşmez; default beklenmeyen girdiyi yakalar. String üzerinde switch, eşleşmeyi equals ile yapar; dizgilerde == kullanmanın yol açtığı referans sorunu burada ortaya çıkmaz.

Program ayrıca 9 - 4, 7 / 2 ve 3 + 4.5 için denendi; 5.0, 3.5 ve 7.5 yazdı. x ve y double olduğu için 7 / 2 tam sayı bölmesi değildir ve 3.5 verir.

7.9.7Alıştırmalardan çözümlü örnekler

Aşağıdaki örnekler sunumun alıştırmalarından seçilmiştir. Her biri bu notta anlatılan bir kalıbı uygular.

Örnek 7.2

Girilen tam sayının pozitif, negatif ya da sıfır olduğunu yazan program.

Çözüm. Üç seçenek birbirini dışlar; bir sayı yalnızca birine girer. Bu, else-if zincirinin işidir. Üçüncü durum için koşul yazmaya gerek yoktur: sayı ne pozitif ne negatifse sıfırdır.

import java.util.Scanner;

public class Isaret {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int sayi = klavye.nextInt();
        if (sayi > 0)
            System.out.println("Pozitif");
        else if (sayi < 0)
            System.out.println("Negatif");
        else
            System.out.println("Sıfır");
    }
}

12 için “Pozitif”, −3 için “Negatif”, 0 için “Sıfır” yazar. 0 bir sınır değeridir ve ayrıca denenmiştir: 0 > 0 ve 0 < 0 yanlıştır, else çalışır.

Örnek 7.3

Boy (metre) ve kilodan vücut kitle indeksini, VKİ = kilo / boy², hesaplayıp sınıfını yazan program. Sınırlar: 18,5'in altı zayıf, 18,5 ile 25 arası normal, 25 ve üstü fazla kilolu.

Çözüm. Sınıflar sürekli bir aralığı böler; switch uygun değildir. Eşikler küçükten büyüğe sıralandığı için koşullar < ile yazılır ve zincir en küçük eşikten başlar.

import java.util.Scanner;

public class Vki {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        double boy = klavye.nextDouble();
        double kilo = klavye.nextDouble();
        double vki = kilo / (boy * boy);
        if (vki < 18.5)
            System.out.println("Zayıf");
        else if (vki < 25)
            System.out.println("Normal");
        else
            System.out.println("Fazla kilolu");
    }
}

Boy 1,75 m ve kilo 70 kg için VKİ = 70 / 3,0625 ≈ 22,86'dır ve “Normal” yazılır. 1,60 m ve 45 kg için 45 / 2,56 ≈ 17,58, “Zayıf”; 1,80 m ve 90 kg için 90 / 3,24 ≈ 27,78, “Fazla kilolu” yazılır. Programın girdisi, Java kaynak kodundaki gibi ondalık noktayla verilmiştir: 1.75. Bilgisayarın bölge ayarı Türkçe ise Scanner ondalık virgül bekleyebilir; bu durumda 1,75 yazılır.

Örnek 7.4

Üç kenar uzunluğu bir üçgen oluşturuyor mu? Oluşturuyorsa üçgen eşkenar mı, ikizkenar mı, çeşitkenar mı?

Çözüm. Üç kenarın bir üçgen oluşturması için her iki kenarın toplamı üçüncüden büyük olmalıdır (üçgen eşitsizliği). Önce bu koşulun tersi denetlenir: herhangi bir toplam üçüncü kenara eşit ya da ondan küçükse üçgen yoktur. De Morgan kuralına göre “üç eşitsizliğin hepsi doğru” koşulunun tersi “en az biri yanlış”tır; bu yüzden terimler || ile bağlanır. Ardından üçgen türü dar koşuldan geniş koşula doğru sınanır: önce üç kenarın eşitliği, sonra herhangi iki kenarın eşitliği.

import java.util.Scanner;

public class Ucgen {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int a = klavye.nextInt(), b = klavye.nextInt(), c = klavye.nextInt();
        if (a + b <= c || a + c <= b || b + c <= a)
            System.out.println("Üçgen değil");
        else if (a == b && b == c)
            System.out.println("Eşkenar");
        else if (a == b || b == c || a == c)
            System.out.println("İkizkenar");
        else
            System.out.println("Çeşitkenar");
    }
}

Program (3, 3, 3) için Eşkenar, (3, 3, 5) ve (5, 3, 5) için İkizkenar, (3, 4, 5) için Çeşitkenar yazdı. (1, 2, 3) için 1 + 2 = 3 olduğundan “Üçgen değil” yazdı; bu kenarlar düz bir çizgi oluşturur. (2, 9, 4) için de 2 + 4 = 6 < 9 olduğundan aynı sonuç çıktı. Eşkenar koşulu ikizkenardan önce sorulmalıdır, çünkü her eşkenar üçgen ikizkenar koşulunu da sağlar.

Örnek 7.5

1–7 arasındaki gün numarasını gün adına çeviren, geçersiz girdide uyarı veren program.

Çözüm. Tek bir tam sayının yedi ayrık değeri vardır; switch ifadesi tam bu iş içindir. Geçersiz girdi default ile yakalanır. switch ifadesi olduğu için default zaten zorunludur.

import java.util.Scanner;

public class GunAdi {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int gun = klavye.nextInt();
        String ad = switch (gun) {
            case 1 -> "Pazartesi";
            case 2 -> "Salı";
            case 3 -> "Çarşamba";
            case 4 -> "Perşembe";
            case 5 -> "Cuma";
            case 6 -> "Cumartesi";
            case 7 -> "Pazar";
            default -> "Geçersiz gün numarası";
        };
        System.out.println(ad);
    }
}

1 için Pazartesi, 5 için Cuma, 7 için Pazar yazdı; 8 ve 0 için “Geçersiz gün numarası” yazdı.

Örnek 7.6

İki sayının küçüğünü koşul operatörüyle bulan, sonra bunu üç sayıya genişleten program.

Çözüm. İki sayının küçüğü a < b ? a : b'dir. Üç sayı için önce ilk ikisinin küçüğü bulunur, sonra bu değer üçüncüyle karşılaştırılır. İki ayrı değişken kullanmak tek satırlık iç içe bir koşul operatöründen daha okunurdur.

import java.util.Scanner;

public class EnKucuk {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int a = klavye.nextInt(), b = klavye.nextInt(), c = klavye.nextInt();
        int ikili = a < b ? a : b;
        int uclu = ikili < c ? ikili : c;
        System.out.println(ikili + " " + uclu);
    }
}

(7, 12, 5) için ilk ikisinin küçüğü 7, üçünün küçüğü 5'tir; program “7 5” yazdı. (4, 4, 9) için “4 4”, (−1, 8, −6) için “−1 −6” yazdı.

Örnek 7.7

Harf notu programına “not 0–100 aralığının dışında” denetimi eklenecek. Bu denetim zincirin neresine yazılmalıdır?

Çözüm. Zincirin en başına. Denetim sona konsaydı geçersiz bir not, örneğin 105, ilk koşulu (105 >= 90) sağlar ve A alırdı; denetime hiç sıra gelmezdi. −5 de benzer biçimde F alırdı. Geçersiz durumları zincirin başında ayıklamak, sonraki koşulların yalnızca geçerli notlarla çalışmasını sağlar.

import java.util.Scanner;

public class HarfNotuSinir {
    public static void main(String[] args) {
        Scanner klavye = new Scanner(System.in);
        int not = klavye.nextInt();
        if (not < 0 || not > 100)
            System.out.println("Geçersiz not");
        else if (not >= 90)
            System.out.println('A');
        else if (not >= 80)
            System.out.println('B');
        else if (not >= 70)
            System.out.println('C');
        else
            System.out.println('F');
    }
}

Program 105 ve −5 için “Geçersiz not”, 100 ve 95 için A, 75 için C, 0 için F yazdı. Sınır değerleri 0 ve 100 geçerli kabul edilir; koşulda < ve > kullanıldığı için bu iki değer “geçersiz” dalına düşmez.

7.10Özet

  • Her algoritma üç akışla yazılabilir: sıralı yapı, karar yapısı ve döngü yapısı. Sıralı yapıda deyimler yazıldıkları sırayla birer kez çalışır; her deyim öncekilerin bıraktığı değerleri kullanır.
  • Koşul, true ya da false değeri üreten boolean bir ifadedir. Eşitlik == ile sınanır; = atamadır ve if (x = 5) derlenmez. 10 < x < 20 yazımı da derlenmez; doğrusu x > 10 && x < 20'dir.
  • if koşul doğruysa gövdesini çalıştırır; if-else iki daldan tam olarak birini çalıştırır. Süslü parantez yoksa gövde tek deyimdir; derleyici girintiye bakmaz. if (…); satırındaki noktalı virgül boş deyimdir.
  • else-if zincirinde koşullar yukarıdan aşağıya denenir; ilk doğru dal çalışır, gerisi atlanır. Alt sınırla kurulan zincirde eşikler büyükten küçüğe yazılır. Bağımsız if'lerin ise hepsi denetlenir.
  • Son else silinirse, dallarda değer alan bir değişken “değer verilmemiş olabilir” hatasına yol açar. Java değer almamış yerel değişkenin okunmasına izin vermez.
  • İç içe kararda iç koşul yalnızca dış koşul o dalı seçtiğinde denetlenir. Derin iç içelik && ile düzleştirilebilir. Sarkan else en yakın, else'i olmayan if'e bağlanır; belirsizlik süslü parantezle giderilir.
  • && iki tarafın da, || en az bir tarafın doğru olmasını ister; ! tersine çevirir. İkisi de kısa devre yapar: sonuç sol taraftan belliyse sağ taraf hesaplanmaz. Bu sayede b != 0 && a / b > 1 güvenlidir. Tek karakterli & ve | iki tarafı da her zaman hesaplar.
  • De Morgan kuralları: !(a && b) = !a || !b, !(a || b) = !a && !b. Karşılaştırmaların tersleri: >= ↔ <, > ↔ <=, == ↔ !=.
  • Koşul operatörü koşul ? d1 : d2 bir değer üreten ifadedir; kısa, iki seçenekli değer seçimlerinde kullanılır ve sağdan gruplanır.
  • Dizgilerde == referansı, equals içeriği karşılaştırır. Klavyeden okunan bir dizgi, aynı metni taşıyan sabitle == karşılaştırmasında false verir. Dizgiler her zaman equals ile karşılaştırılır.
  • switch tek bir değerin ayrık seçeneklerine göre seçim yapar; case etiketleri sabittir. switch ifadesi bir değer üretir ve bütün değerleri kapsamak zorundadır; int ve String için default zorunludur. Bloklu dalın değeri yield ile verilir.
  • Klasik switch'te break yoksa yürütme sonraki case'in koduna düşer. Ok biçiminde (->) düşme olmaz. Aralık ve karmaşık koşullarda if-else, tek değerin ayrık seçeneklerinde switch uygundur.
  • Örneklerin dersleri: çift sınaması % 2 == 0 ile yapılır, çünkü Java'da -7 % 2 = -1'dir. Integer.MIN_VALUE'nun mutlak değeri int'te taşar. Math.sqrt negatif sayıda NaN, ondalık sıfıra bölme Infinity verir. “Aday ve karşılaştır” kalıbında aday girdilerden biriyle başlatılır.

7.11Kavram listesi

Terimlerin İngilizce karşılıkları ve öteki sunumlardaki kullanımları terim sözlüğünde bulunur.

7.12Kendini Dene

  1. p = 4 ve q = 9 iken if (p > 3 || ++q > 0) p = p * 2; satırı çalışıyor. Ardından p ve q kaçtır?

    Cevap

    Sol taraf 4 > 3 doğrudur. || kısa devre yapar ve ++q hiç çalışmaz; q 9 kalır. Koşul doğru olduğu için p = 4 × 2 = 8 olur. Sonuç: p = 8, q = 9.

  2. t = 15 iken t % 3 == 0 && t % 5 == 0 ? "FizzBuzz" : t % 3 == 0 ? "Fizz" : "diğer" ifadesinin değeri nedir? t = 9 ve t = 7 için ne olur?

    Cevap

    Koşul operatörü sağdan gruplanır; ifade “ilk koşul doğruysa FizzBuzz, değilse (ikinci koşul doğruysa Fizz, değilse diğer)” biçiminde okunur. 15 hem 3'e hem 5'e bölünür: “FizzBuzz”. 9, 3'e bölünür ama 5'e bölünmez: ilk koşul yanlış, ikinci doğru, “Fizz”. 7 ikisine de bölünmez: “diğer”.

  3. Aşağıdaki kod puan = 72 için ne atar? puan = 90 ve puan = 40 için ne olur?

    if (puan >= 50) {
        if (puan >= 85) d = "Pekiyi";
        else d = "Geçti";
    } else d = "Kaldı";
    Cevap

    72 için dış koşul 72 >= 50 doğrudur, iç koşul 72 >= 85 yanlıştır: d = "Geçti". 90 için iki koşul da doğrudur: "Pekiyi". 40 için dış koşul yanlıştır, iç if hiç denetlenmez: "Kaldı". Süslü parantez iç else'i iç if'e, dıştaki else'i dış if'e bağlar.

  4. if (!(sicaklik < 15 || yagmur)) koşulu, ! operatörü yalnızca boolean değişkenin önünde kalacak biçimde nasıl yazılır? sicaklik = 30, yagmur = false için koşul doğru mudur?

    Cevap

    De Morgan kuralıyla ||, &&'ye döner ve her terim tersine çevrilir: sicaklik >= 15 && !yagmur. <'nin tersi >='dir. 30 ve false için 30 >= 15 doğru, !false doğrudur; koşul doğrudur. Özgün yazımda da 30 < 15 yanlış, yagmur yanlış, VEYA'ları yanlış, tersi doğrudur.

  5. Klasik bir switch'te case 1: puan += 10;, case 2: puan += 20; break;, case 3: puan += 30;, default: puan += 5; satırları var. puan her seferinde 0'dan başlıyor. k = 1, 2, 3 ve 4 için puan kaç olur?

    Cevap

    k = 1: case 1'de break yoktur, case 2'ye düşülür: 10 + 20 = 30, sonra break. k = 2: yalnızca 20. k = 3: case 3'te break yoktur, default'a düşülür: 30 + 5 = 35. k = 4: hiçbir case eşleşmez, default: 5. Sonuç: 30, 20, 35, 5.

  6. char not = 'B'; iken switch (not) { case 'A', 'B' -> "iyi"; case 'C' -> "orta"; default -> "zayıf"; } ifadesinin değeri nedir? Bu ifadeden default satırı silinirse ne olur?

    Cevap

    'B', ilk case'in listesindedir: değer "iyi"dir. Ok biçiminde düşme olmaz; ikinci case çalışmaz. default silinirse program derlenmez: char 65 binden fazla değer alabilir ve switch ifadesi bütün değerleri kapsamalıdır. Derleyici “the switch expression does not cover all possible input values” iletisini verir.

  7. a = 5, b = 0 iken if (b == 0 || a / b > 2) ve if (b == 0 | a / b > 2) satırları ne yapar?

    Cevap

    İlk satırda sol taraf doğrudur; || kısa devre yapar ve bölme hiç hesaplanmaz. Koşul doğrudur ve gövde çalışır. İkinci satırda | iki tarafı da hesaplar: 5 / 0 tam sayı bölmesi ArithmeticException: / by zero istisnasını fırlatır ve program durur.

  8. Kullanıcı klavyeden “Ada” yazıyor ve bu metin ad değişkenine okunuyor. ad == "Ada", ad.equals("ada") ve ad.equalsIgnoreCase("ada") ifadelerinin değerleri nedir?

    Cevap

    ad == "Ada" false'tur: klavyeden okunan dizgi yeni bir nesnedir, sabit ise havuzdadır; referanslar farklıdır. ad.equals("ada") false'tur: içerikler büyük-küçük harf açısından farklıdır (“A” ≠ “a”). ad.equalsIgnoreCase("ada") true'dur: harf büyüklüğü göz ardı edilir.

7.13İleri okuma

  • Oracle, The Java Tutorials, “The if-then and if-then-else Statements” ve “The switch Statement” sayfaları: Java'nın karar yapıları resmî örneklerle ve kısa açıklamalarla anlatılır.
  • Oracle, Java Language Specification, Java SE 21 Edition, Bölüm 14 (“Blocks, Statements, and Patterns”) ve 15.28 (“switch Expressions”): sarkan else kuralının, switch ifadesinin kapsama zorunluluğunun ve yield'in kesin tanımları.
  • R. Sedgewick, K. Wayne, Computer Science: An Interdisciplinary Approach (Addison-Wesley, 2016), Kesim 1.3: koşullar ve döngüler kısa, bilimsel hesap örnekleriyle anlatılır; ikinci dereceden denklem örneği burada da işlenir.
  • A. B. Downey, C. Mayfield, Think Java (2. baskı, O'Reilly, 2019), “Conditionals and Logic” bölümü: koşullar, mantıksal operatörler ve iç içe kararlar programlamaya yeni başlayanlar için sade bir dille anlatılır; kitabın çevrim içi sürümü ücretsizdir.

7.14Kaynaklar

  1. P. Deitel, H. Deitel, Java: How to Program, Early Objects, 11. baskı, Pearson, 2018.
  2. Y. D. Liang, Introduction to Java Programming and Data Structures, 13. baskı, Pearson, 2024.
  3. Oracle, The Java Tutorials: The if-then and if-then-else Statements; The switch Statement.
  4. Oracle, Java Language Specification, Java SE 21 Edition, Bölüm 14: Blocks, Statements, and Patterns.
  5. R. Sedgewick, K. Wayne, Computer Science: An Interdisciplinary Approach, Addison-Wesley, 2016: 1.3 Conditionals and Loops.
  6. A. B. Downey, C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (ücretsiz çevrim içi sürüm).

İlgili sayfalar: Koşullu İfadeler sunumu · Çalışma soruları · Karar İzleyici simülatörü · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.