Java ile Nesne Yönelimli Programlama · Ders Notu

Bölüm 2.2: Söz Dizimi Kuralları

Bir Java programı derleyiciye bir metin olarak verilir. Derleyici bu metni harf harf okur ve dilin kurallarına uyup uymadığına bakar. Tek bir noktalı virgül eksikse ya da bir harf büyük yerine küçük yazılmışsa program çalışmaz. Bu not, Java'nın yazım kurallarını ve bu kuralların arkasındaki çalışma düzenini anlatır. İlk kesim bir .java dosyasının javac ile bytecode'a, java komutuyla da çalışan bir programa dönüşmesini izler. JVM'in yığın üzerinde nasıl çalıştığı ve aynı .class dosyasının neden her işletim sisteminde çalıştığı burada açıklanır. Ardından değişkenler, sekiz ilkel tip ve bu tiplerin sınırları gelir. Tanımlayıcılar, anahtar kelimeler ve isimlendirme kuralları adların nasıl seçileceğini belirler. Atama, sabit değerler ve tip dönüşümü kesimi, sayıların bir tipten ötekine geçerken nasıl değiştiğini gösterir. Son kesimler operatörleri, işlem önceliğini, kısa devre değerlendirmeyi, üçlü operatörü ve kontrol deyimlerinin söz dizimini işler. Her kesimde sunumdaki kodlar satır satır anlatılır ve derleyicinin gerçek hata iletileri verilir.

Öğrenme çıktıları

Derleme ve çalıştırma
Bir .java dosyasının javac ile bytecode'a, java ile çalışan programa dönüşmesini anlatmak; soyutlama seviyelerini ve JVM yığınında bytecode'un yürüyüşünü izlemek; JVM'in platform bağımsızlığını nasıl sağladığını açıklamak.
Değişkenler ve tipler
Sekiz ilkel veri tipini boyut ve aralıklarıyla tanımak; değişken bildirmek, değer atamak, taşma ve ondalık hassasiyet sorunlarını fark etmek.
Kurallar ve adlandırma
Geçerli tanımlayıcıyı geçersizden ayırmak, anahtar kelimeleri tanımak, camelCase, PascalCase ve BÜYÜK_HARF kurallarına uymak.
İfadeler ve akış
Operatör önceliğine göre ifadeleri hesaplamak, tip dönüşümü yapmak; ?: ile koşula göre değer seçmek; if, switch, for, while, do-while, break ve continue deyimlerinin söz dizimini tanımak.

2.2.1Derleme ve çalıştırma

Java ile yazılan bir program, ekranda bir sonuç görünene kadar birkaç aşamadan geçer. Programcı kodu bir metin dosyasına yazar. Derleyici bu metni denetler ve ara bir koda çevirir. Java Sanal Makinesi ara kodu alır ve bilgisayarın işlemcisinde yürütür. Bu kesim aşamaları sırayla ele alır. Hata iletilerini doğru okumak ve “Çalıştır” düğmesinin arkasında ne olduğunu bilmek, ancak bu zinciri tanıyınca mümkündür.

2.2.1.1Kaynak kod dosyası

Tanım 2.2.1

Kaynak kod (source code), programcının bir programlama dilinin kurallarına göre yazdığı, insanın okuyabildiği program metnidir. Java kaynak kodu düz metin (plain text) dosyalarında durur ve bu dosyaların uzantısı .java'dır.

Düz metin, yalnız karakterlerden oluşan bir dosyadır. İçinde yazı tipi, renk, kalın yazı ya da sayfa düzeni bilgisi yoktur. Word gibi bir kelime işlemcinin kaydettiği belge ise metnin yanında bu biçim bilgilerini de saklar. Derleyici bu ek bilgiyi anlamaz. Bu yüzden Java kodu Not Defteri, Notepad++ ya da VS Code gibi bir metin düzenleyiciyle ya da bir geliştirme ortamıyla yazılır.

Düz metin üç açıdan elverişlidir:

  • Okunaklıdır. İnsan dosyayı açınca doğrudan kodu görür. Kod kolayca anlaşılır, e-postayla gönderilir, bir sürüm denetim sisteminde saklanır.
  • Taşınabilirdir. Aynı .java dosyası her işletim sisteminde ve her geliştirme ortamında açılır.
  • Kolay düzeltilir. Bir hatayı düzeltmek ya da kodu güncellemek için herhangi bir metin düzenleyici yeterlidir.

Java bir dosya adı kuralı da koyar. Bir dosyada public bir sınıf varsa dosyanın adı sınıfın adıyla birebir aynı olmalıdır. public class MerhabaDunya diye başlayan bir kod MerhabaDunya.java adlı dosyaya kaydedilir. Bu eşleşmede büyük ve küçük harf de önemlidir. Aynı kod merhabadunya.java adıyla kaydedilirse derleyici şu iletiyi verir:

merhabadunya.java:1: error: class MerhabaDunya is public, should be declared in a file named MerhabaDunya.java
public class MerhabaDunya {
       ^
1 error

Windows'un dosya sistemi büyük ve küçük harfi ayırmasa da javac ayırır. İleti tam olarak neyin yanlış olduğunu söyler: MerhabaDunya sınıfı public'tir ve MerhabaDunya.java adlı bir dosyada bildirilmelidir.

2.2.1.2Programlama araçları

Java programı yazmak için birkaç araç birlikte kullanılır. Tablo 2.2.1 bu araçları ve görevlerini özetler.

Tablo 2.2.1: Java ile program geliştirmede kullanılan araçlar.
AraçNe işe yarar?Örnekler
Metin düzenleyiciKaynak kodu düz metin olarak yazmakNotepad++, VS Code
JDK (Java Development Kit)Derleyici, çalıştırıcı ve öteki geliştirme araçlarıjavac, java, javap, jshell
IDE (tümleşik geliştirme ortamı)Düzenleme, derleme, çalıştırma ve hata ayıklama tek pencerede; hatalar yazılırken altı çizilerek gösterilirEclipse, IntelliJ IDEA, NetBeans, Java eklentileriyle VS Code
JShell (Java 9 ve sonrası)Tek satırlık Java ifadelerini sınıf yazmadan denemekjshell> 7 / 2 → 3

JDK (Java Development Kit, Java geliştirme kiti), Java ile program geliştirmek için gereken her şeyi içeren pakettir. İçinde derleyici javac, programı başlatan java, bir .class dosyasının içini gösteren javap ve etkileşimli deneme ortamı jshell bulunur. IDE (Integrated Development Environment, tümleşik geliştirme ortamı) ise bu araçları bir düzenleyiciyle birleştirir. IDE'deki “Çalıştır” düğmesine basılınca arka planda yine javac ve java çalışır.

JShell küçük denemeler için çok kullanışlıdır. 7 / 2 ifadesinin neden 3.5 değil de 3 verdiğini merak eden biri, bir sınıf ve main metodu yazmadan ifadeyi JShell'e yazıp sonucu hemen görür. Başlangıçta bir kez komut satırında javac ve java kullanmak da yararlıdır. IDE'nin gizlediği adımlar böylece görünür hâle gelir ve bir hata çıktığında hangi aşamada çıktığı anlaşılır.

2.2.1.3İlk program: MerhabaDunya

Aşağıdaki program, Java'da yazılabilecek en kısa tam programlardan biridir. Ekrana tek bir satır yazar.

public class MerhabaDunya {
    public static void main(String[] args) {
        System.out.println("Merhaba, Dünya!");
    }
}

Kısa olmasına karşın programın her parçası bir kurala bağlıdır:

  • public class MerhabaDunya bir sınıf tanımlar. Her Java programı en az bir sınıf içerir. Sınıfın gövdesi, bu satırdaki { ile son satırdaki } arasında kalan kısımdır. Sınıf public olduğu için dosyanın adı MerhabaDunya.java olmalıdır.
  • public static void main(String[] args) programın giriş noktasıdır. JVM programı başlatınca çalışmaya bu metottan başlar. Başlığın yazımı birebir böyle olmalıdır. Main ya da main(String args) yazılırsa program derlenir, ama JVM giriş noktasını bulamaz ve çalıştırmayı reddeder.
  • System.out.println(...) parantez içindeki değeri konsola yazar ve alt satıra geçer. Metinler çift tırnak içinde yazılır: "Merhaba, Dünya!".
  • Satırın sonundaki noktalı virgül, deyimin bittiğini gösterir. Her deyim (statement) ; ile biter. Süslü parantezle kapanan bloklardan sonra ; gerekmez; bu yüzden sınıfın ve metodun kapanış parantezinden sonra noktalı virgül yoktur.

Programın gövdesi iç içe iki bloktan oluşur. Dıştaki blok sınıfın, içteki blok main metodunun gövdesidir. Girinti, yani satır başlarındaki boşluklar, bu iç içeliği göze görünür kılar. Derleyici girintiye bakmaz; yalnız süslü parantezleri sayar.

2.2.1.4Temel söz dizimi kuralları

Tanım 2.2.2

Söz dizimi (syntax), bir dilin yazım kurallarıdır: hangi sözcüklerin, işaretlerin ve sıralamaların geçerli olduğunu belirler. Söz dizimi kurallarına uymayan Java kodu derlenmez.

Türkçede “kitabı okudum ben” cümlesi alışılmadık bir sıradadır ama anlaşılır. Derleyici bu kadar hoşgörülü değildir. Kurala uymayan bir satırı tahmin yoluyla düzeltmez, hata verip durur. Java'nın temel yazım kuralları şunlardır:

  • Her deyim ; ile biter.
  • Bloklar (block) { ile açılır, } ile kapanır. Açılan her blok kapanmalıdır.
  • Java büyük-küçük harfe duyarlıdır (case-sensitive): sayi ile Sayi iki ayrı addır; System yerine system yazılamaz.
  • Boşluk, sekme ve satır sonu derleyici için önemsizdir. Bir deyim birkaç satıra bölünebilir. Okunabilirlik için girinti kullanılır.
  • Açıklamalar (comments) iki biçimde yazılır: // satırın sonuna kadar, /* … */ ise birden çok satır boyunca sürer. Derleyici açıklamaları yok sayar.

Aşağıdaki program bu kuralların hepsini bir arada gösterir.

/* Çok satırlı açıklama:
   derleyici bunu yok sayar. */
public class Kurallar {
    // Tek satırlık açıklama
    public static void main(String[] args) {
        int sayi = 5;  // ; ile biter
        int Sayi = 7;  // sayi ≠ Sayi
        System.out.println(sayi
                + Sayi);   // satır bölünebilir
    }
}

Program şu çıktıyı verir:

12

İlk üç satır çok satırlı bir açıklamadır ve derlenen koda hiçbir şey katmaz. sayi ile Sayi büyük-küçük harf farkı yüzünden iki ayrı değişkendir: biri 5, öteki 7 değerini tutar. println çağrısı iki satıra bölünmüştür. Derleyici için bu bölünme önemsizdir, çünkü deyim ancak ; görülünce biter. Çıktıdaki 12, iki değişkenin toplamıdır. İki ayrı değişkenin yan yana bulunabilmesi, adlardaki tek bir harf farkının ne kadar önemli olduğunu gösterir. Gerçek programlarda böyle benzer adlar karışıklığa yol açar ve kaçınılması gerekir; buradaki amaç yalnız kuralı göstermektir.

Sık yapılan hata

Büyük-küçük harfi karıştırmak. Aşağıdaki üç satır, Türkçe yazarken alışılan küçük harf kullanımından kaynaklanan tipik hatalardır. Derleyici her biri için ayrı bir ileti verir:

Harf.java:3: error: package system does not exist
        system.out.println("a");
              ^
Harf.java:4: error: cannot find symbol
        string s = "b";
        ^
  symbol:   class string
  location: class Harf
Harf.java:5: error: cannot find symbol
        Int n = 3;
        ^
  symbol:   class Int
  location: class Harf
3 errors

system.out yazılınca derleyici system'i bir paket adı sanar ve bu paketi bulamaz. string ve Int ise tanımlı olmayan sınıf adları gibi görülür. Doğru yazımlar System, String ve int'tir. String bir sınıf olduğu için büyük harfle, int bir anahtar kelime olduğu için küçük harfle başlar.

2.2.1.5Soyutlama seviyeleri

Bir işlemci yalnız ikili sayılarla kodlanmış komutları anlar. İnsan ise “a ile b'yi topla, sonucu toplama yaz” gibi düşünür. Bu iki uç arasında üç seviye vardır ve her seviye bir altındakinin ayrıntılarını gizler.

  • Yüksek seviyeli dil (high-level language) problem alanına yakındır. Java, C ve Python bu seviyededir. Kod kısa, okunur ve anlaşılırdır; programcı daha üretken çalışır.
  • Assembly dili (assembly language) işlemci komutlarını MOV ve ADD gibi kısa adlarla yazar. Her satır çoğu zaman tek bir makine komutuna karşılık gelir. Daha düşük seviyeli bir soyutlamadır.
  • Donanım temsili (hardware representation) yalnız bitlerden oluşur. Komutlar ve veriler bu biçimde doğrudan donanıma gider.
Yüksek seviye dil (Java)toplam = a + b;derleyiciAssembly dili (x86)mov eax, ebx · add eax, ecxçevirici (assembler)Donanım temsili (makine kodu)10001001 11011000 00000001 11001000
Şekil 2.2.1: Aynı toplama işleminin üç seviyesi. Java'daki tek satır, assembly dilinde iki komuta, donanımda iki baytlık iki makine koduna karşılık gelir.

Şekil 2.2.1 toplam = a + b satırının üç seviyedeki karşılığını gösterir. Örnekte a, b ve toplam değişkenlerinin sırasıyla ebx, ecx ve eax yazmaçlarında durduğu varsayılmıştır. Assembly dilinde önce a'nın değeri eax'e kopyalanır (mov eax, ebx), sonra b bunun üstüne eklenir (add eax, ecx). Makine kodu x86 işlemcilerinin gerçek kodlamasıdır: birinci komut onaltılık 89 D8, ikinci komut 01 C8 baytlarıdır. Soyutlama arttıkça kod kısalır ve okunur hâle gelir. Alt seviyeye çevirme işini bir derleyici ya da bir assembler yapar.

Java bu tabloya bir ara katman ekler. Java derleyicisi kaynak kodu doğrudan x86 makine koduna çevirmez. Önce bytecode denilen ve hiçbir gerçek işlemciye ait olmayan bir koda çevirir. Bu ara kod, aynı derlenmiş programın farklı işlemcilerde çalışabilmesini sağlar.

2.2.1.6Derleme

Tanım 2.2.3

Derleme (compilation), kaynak kodun bütünüyle başka bir dile çevrilmesidir. Bu işi yapan programa derleyici (compiler) denir. Java derleyicisi javac, kaynak kodu işlemcinin makine diline değil, Java Sanal Makinesi'nin anladığı bytecode'a çevirir ve sonucu .class uzantılı bir dosyaya yazar.

Bilgisayar .java metnini doğrudan çalıştıramaz. Metnin önce derlenmesi gerekir. Kaynak koddan ekrandaki çıktıya giden yol dört adımda tamamlanır:

  1. Programcı kaynak kodu düz metin olarak yazar ve MerhabaDunya.java adıyla kaydeder.
  2. javac MerhabaDunya.java komutu derleyiciyi çalıştırır. Derleyici söz dizimini ve tipleri denetler. Hata yoksa aynı klasörde MerhabaDunya.class dosyası oluşur.
  3. java MerhabaDunya komutu başlatıcıyı (launcher) çalıştırır. Başlatıcı JVM'i açar ve sınıfı yükler. JVM bytecode'u yorumlar, sık çalışan kısımları JIT derleyicisiyle makine koduna çevirir.
  4. İşletim sistemi ve işlemci asıl komutları yürütür; ekranda Merhaba, Dünya! görünür.
MerhabaDunya.javakaynak kod (düz metin)javacderleyiciMerhabaDunya.classbytecodeJVM (java)yorumlayıcı + JITİşletim sistemive işlemci1. yaz2. derle3. çalıştır4. çıktıderleme zamanı: bir kez, geliştiricinin bilgisayarındaçalışma zamanı: her çalıştırmada
Şekil 2.2.2: Derleme ve çalıştırma hattı. .java dosyası javac ile .class dosyasına, .class dosyası da JVM ile çalışan programa dönüşür.

Şekil 2.2.2'de iki ayrı komut ve iki ayrı zaman dilimi vardır. Derleme zamanı, javac'ın çalıştığı süredir; bu sürede program henüz çalışmaz, yalnız denetlenir ve çevrilir. Çalışma zamanı ise java komutuyla başlar. Bir hatanın hangi zamanda ortaya çıktığı önemlidir. Derleme hatası programın hiç çalışmamasına yol açar. Çalışma zamanı hatası ise program çalışırken belirir; sıfıra bölme bunun örneğidir.

Derleme aşamasının en büyük yararı, hataların program çalışmadan önce yakalanmasıdır. Derleyici eksik noktalı virgülü, kapanmamış parantezi, yanlış yazılmış bir adı ve uyumsuz tipleri bulur. Hata varsa .class dosyası oluşmaz. İletideki dosya adı:satır bilgisi hatanın yerini gösterir. MerhabaDunya programının 3. satırındaki noktalı virgül unutulursa satır şöyle olur:

System.out.println("Merhaba, Dünya!")

Derleyici bu satır için şu iletiyi verir:

MerhabaDunya.java:3: error: ';' expected
        System.out.println("Merhaba, Dünya!")
                                             ^
1 error

İletinin ilk satırı dosyayı (MerhabaDunya.java), satır numarasını (3) ve hatanın türünü (';' expected, yani noktalı virgül bekleniyordu) verir. İkinci satır hatalı kodu yineler. Üçüncü satırdaki ^ işareti, derleyicinin noktalı virgülü beklediği yeri gösterir: kapanış parantezinin hemen sağı. Son satır toplam hata sayısını bildirir. Derleme aşaması programın hızına da katkı sağlar. Derleyici tip denetimi gibi işleri bir kez yapar; çalışma sırasında bu işlerin tekrarlanmasına gerek kalmaz. JVM'in çalışma sırasındaki iyileştirmeleri de buna eklenir.

Sık yapılan hata

Hatayı iletinin gösterdiği satırdan önce aramamak. Noktalı virgül eksik olduğunda derleyici bunu çoğu zaman bir sonraki simgeyi okuyunca fark eder. Bu yüzden ileti bazen bir alt satırı gösterir. Bir satırda anlamsız görünen bir ';' expected iletisi çıkarsa önce bir önceki satırın sonuna bakılır.

2.2.1.7Komut satırında derleme ve çalıştırma

Java ile temel işlem adımları üç tanedir. Derleme adımında .java dosyası .class dosyasına dönüştürülür: javac MerhabaDunya.java. Çalıştırma adımında java komutuna uzantısız sınıf adı verilir: java MerhabaDunya. Üçüncü adım çıktıdır: program ekrana Merhaba, Dünya! yazar. Bir komut satırı oturumu şöyle görünür:

> javac MerhabaDunya.java
> dir /b
MerhabaDunya.class
MerhabaDunya.java
> java MerhabaDunya
Merhaba, Dünya!

javac hiçbir şey yazmadıysa derleme başarılıdır. Ardından gelen dir /b komutu (Linux ve macOS'ta ls) klasördeki dosyaları listeler. Listede kaynak dosyanın yanında yeni oluşan MerhabaDunya.class görülür. Son komut programı çalıştırır.

MerhabaDunya.javakaynak kodjavacMerhabaDunya.classbytecodejavaJVMçalıştırırMerhaba, Dünya!çıktı
Şekil 2.2.3: İki komut, iki dosya: javac kaynak dosyayı .class dosyasına çevirir; java JVM'i başlatır ve program çıktı verir.
Sık yapılan hata

Çalıştırırken uzantı yazmak: java MerhabaDunya.class. JVM bir dosya adı değil, bir sınıf adı bekler. Bu komut MerhabaDunya.class adlı bir sınıf arar ve “Could not find or load main class” iletisiyle durur. Doğrusu uzantısız yazmaktır: java MerhabaDunya.

Java 11 ile tek dosyalık programlar için bir kısayol geldi. java MerhabaDunya.java komutu dosyayı bellekte derler ve hemen çalıştırır. Bu yolda diske .class dosyası yazılmaz. Kısayol küçük denemeler için pratiktir. Ancak birden çok dosyadan oluşan programlarda iki aşamalı yol kullanılır.

2.2.1.8Bytecode

Tanım 2.2.4

Bytecode (ara kod), javac'ın ürettiği ve Java Sanal Makinesi'nin çalıştırdığı, işlemciden ve işletim sisteminden bağımsız komutlardır. .class dosyaları işlemcinin doğrudan anlayabileceği makine kodu içermez; içlerinde bytecode bulunur. Bytecode, JVM'in “makine dili”dir.

Adı komutların yapısından gelir: her komutun işlem kodu (opcode) tek bir bayttır. Komutun gerektirdiği ek bilgi, örneğin bir sabit ya da bir tablo kaydının numarası, işlem kodundan sonraki baytlarda durur. Tek baytlık işlem kodu en çok 256 farklı komut demektir ve bu sayı JVM için yeterlidir.

Bytecode bir işlemciye ya da işletim sistemine bağlı değildir. Aynı .class dosyası Windows'ta, Linux'ta ve macOS'ta çalışır. Bir benzetme bu ilişkiyi iyi anlatır: bytecode her ülkede aynı okunan bir nota gibidir. Her bilgisayardaki JVM ise o notayı kendi çalgısında çalan müzisyendir. Nota değişmez; çalgı ve müzisyen değişir.

Bir .class dosyası ikili bir dosyadır ve metin düzenleyicide anlamsız karakterler olarak görünür. JDK'daki javap -c Sinif komutu bu dosyadaki bytecode komutlarını okunur biçimde listeler. İki satırlık şu kodu ele alalım: int x = 5; System.out.println(x);. Kod bir main metodunun içine yazılıp JDK 21 ile derlendiğinde javap -c şu listeyi verir:

0: iconst_5
1: istore_1
2: getstatic     #7    // System.out
5: iload_1
6: invokevirtual #13   // println(int)
9: return

Her satırın başındaki sayı, komutun metot içindeki bayt konumudur. Konumlar 0, 1, 2, 5, 6, 9 diye ilerler. iconst_5 ve istore_1 tek baytlık komutlardır. getstatic ve invokevirtual ise işlem kodundan sonra iki baytlık bir tablo numarası taşır; bu yüzden üç bayt yer kaplar.

JVM bir yığın makinesidir. Gerçek işlemciler değerleri yazmaçlarda tutar. JVM'in komutları ise değerleri bir işlenen yığınına (operand stack) iter ve oradan alır. Yığın, üst üste konmuş tabaklar gibidir: en son konan en önce alınır. Komutlar sırayla şunu yapar:

  1. iconst_5: sabit 5 işlenen yığınına itilir. Kaynaktaki karşılığı int x = 5; satırının sağ tarafıdır.
  2. istore_1: yığının üstündeki değer alınır ve 1 numaralı yerel değişkene, yani x'e yazılır. Yığın yeniden boşalır.
  3. getstatic #7: System.out nesnesine bir başvuru yığına itilir. #7, sınıf dosyasındaki sabitler tablosunun 7 numaralı kaydıdır.
  4. iload_1: x'in değeri (5) yığına kopyalanır. Kaynaktaki karşılığı System.out.println(x); satırıdır.
  5. invokevirtual #13: println(int) metodu çağrılır. Metot iki değeri yığından alır: hangi nesne üzerinde çalışacağını (System.out) ve yazacağı sayıyı (5). Ekrana 5 yazılır.
  6. return: main biter, program sonlanır.
işlenen yığını5yerel değişkenler0: args = dizi1: x = — işlenen yığınıyerel değişkenler0: args = dizi1: x = 5 işlenen yığınıout5yerel değişkenler0: args = dizi1: x = 5
Şekil 2.2.4: Bytecode yürürken işlenen yığını ve yerel değişkenler. (a) iconst_5 sonrası yığında 5 vardır, x henüz boştur. (b) istore_1 sonrası yığın boşalmış, x = 5 olmuştur. (c) iload_1 sonrası yığında System.out başvurusu ve 5 durur; println bu ikisini alacaktır.

Şekil 2.2.4 üç anı gösterir. (b) ile (c) arasındaki fark önemlidir: iload_1 değeri yerel değişkenden yığına kopyalar. x yerinde kalır ve değeri değişmez. Bir değişkeni okumak onu tüketmez; bu durum bytecode düzeyinde de geçerlidir.

2.2.1.9JVM yığınında bir toplama

Yığın makinesi fikri bir toplama işleminde daha açık görülür. Şu üç satırı ele alalım:

int a = 5;
int b = 7;
int toplam = a + b;
System.out.println(toplam);

Bu satırların derlenmiş hâli on iki komuttur. Açıklamalar, her komutun kaynak koddaki karşılığını verir:

iconst_5        // 5'i yığına koy
istore_1        // a = 5
bipush 7        // 7'yi yığına koy
istore_2        // b = 7
iload_1         // a'yı yükle
iload_2         // b'yi yükle
iadd            // topla
istore_3        // toplam = 12
getstatic       // System.out
iload_3         // toplam'ı yükle
invokevirtual   // println(int)
return

JVM toplama yapmak için yazmaç kullanmaz. İki sayıyı yığına iter, iadd komutu üstteki iki değeri çıkarıp toplar ve sonucu yeniden yığına koyar. Tablo 2.2.2 her komuttan sonra işlenen yığınını ve yerel değişken tablosunu verir. Yerel değişken tablosunun 0 numaralı yuvasında main'in parametresi args durur; tabloda “…” ile gösterilmiştir. a, b ve toplam sırasıyla 1, 2 ve 3 numaralı yuvalara yerleşir.

Tablo 2.2.2: int toplam = a + b; örneğinde her komuttan sonra JVM'in durumu. Yığının üstü sağdadır.
Komutİşlenen yığını1: a2: b3: toplamAçıklama
iconst_55sabit 5 itilir
istore_1boş5a = 5
bipush 775bir bayta sığan sabit 7 itilir
istore_2boş57b = 7
iload_1557a kopyalanır
iload_25, 757b kopyalanır
iadd1257iki değer çıkar, toplam girer
istore_3boş5712toplam = 12
getstaticout5712System.out başvurusu
iload_3out, 125712println'ın argümanı
invokevirtualboş5712ekrana 12 yazılır
returnboş5712main biter
işlenen yığınıyerel değişkenler570 args…1 a52 b73 toplam işlenen yığınıyerel değişkenler120 args…1 a52 b73 toplam işlenen yığınıyerel değişkenler0 args…1 a52 b73 toplam12
Şekil 2.2.5: Toplamanın üç anı. (a) iload_2 sonrası yığında 5 ve 7 vardır. (b) iadd ikisini çıkarır ve 12'yi iter. (c) istore_3 12'yi toplam'ın yuvasına yazar, yığın boşalır.

Tablodan üç gözlem çıkar. Birincisi, 5 için iconst_5, 7 için ise bipush 7 kullanılır. JVM'de −1 ile 5 arasındaki küçük sabitlerin her birine ayrı, tek baytlık bir komut ayrılmıştır. 7 bu aralıkta olmadığı için “bir bayt it” anlamına gelen bipush ve ardından 7 değerini taşıyan bir bayt yazılır. İkincisi, yığın her deyimin sonunda boşalır. Yığın yalnız bir ifadenin hesaplandığı kısa süre boyunca dolu kalır. Üçüncüsü, iload komutları değişkenleri tüketmez; a ve b toplamadan sonra da 5 ve 7 olarak durur.

Komut adlarındaki i harfi int tipini belirtir. long için ladd, double için dadd gibi ayrı komutlar vardır. JVM her işlemde hangi tiple çalıştığını bilir. Tiplerin derleme sırasında denetlenmesinin bir nedeni de budur: derleyici hangi komutu üreteceğine tipe bakarak karar verir. Aynı bytecode her işletim sistemindeki JVM'de bu sırayla çalışır.

2.2.1.10Java Sanal Makinesi ve JIT

Tanım 2.2.5

Java Sanal Makinesi (Java Virtual Machine, JVM), Java uygulamalarını çalıştıran yazılımdır. Bytecode'u yorumlar ve bulunduğu işlemciye uygun biçimde yürütür. Sık çalışan bytecode parçalarını JIT (just-in-time, tam zamanında) derleyicisiyle çalışma sırasında makine koduna çevirir.

“Sanal makine” adı, JVM'in kendi komut kümesi olan bir bilgisayar gibi davranmasından gelir. Bu bilgisayar donanım olarak yoktur; bir program olarak gerçek bilgisayarın üzerinde çalışır. JVM'in iç yapısı dört ana parçadan oluşur:

  • Sınıf yükleyici, gerekli .class dosyalarını bulur ve belleğe alır.
  • Doğrulayıcı, yüklenen bytecode'un kurallara uyduğunu denetler. Örneğin bir komutun yığında bulunmayan bir değeri almaya çalışmadığından emin olur. Böylece bozuk ya da kötü niyetli bir dosya JVM'i çökertemez.
  • Yorumlayıcı, bytecode komutlarını tek tek okur ve her birinin karşılığı olan işi yapar.
  • JIT derleyicisi, çok kez çalışan kod parçalarını fark eder ve onları doğrudan makine koduna çevirir. Sonraki çalışmalarda yorumlama yerine bu makine kodu kullanılır.
Sinif.classbytecodeJVMsınıf yükleyicidoğrulayıcıyorumlayıcıJIT derleyiciişletim sistemi+ işlemci
Şekil 2.2.6: JVM'in iç yapısı: sınıf yükleyici, doğrulayıcı, yorumlayıcı ve JIT derleyici. Üretilen işler işletim sistemine gider.

Yorumlama esnektir ama yavaştır: her komut her çalıştırılışında yeniden çözümlenir. Makine kodu ise hızlıdır ama üretilmesi zaman alır. JIT bu ikisi arasında bir denge kurar. Bir kez çalışan kod yorumlanır, binlerce kez çalışan bir döngü gövdesi derlenir. Bu yüzden bir Java programı zamanla hızlanır. Uzun süre çalışan sunucu programlarında bu etki belirgindir.

Programı başlatan java komutu, JDK belgelerinde Java başlatıcı aracı (launcher tool) diye geçer. Başlatıcı JVM'i başlatır, adı verilen sınıfı yükler ve o sınıfın main metodunu çağırır. main bitince JVM de kapanır.

2.2.1.11Platform bağımsızlığı ve Java platformu

Tanım 2.2.6

Platform, bir programın çalıştığı donanım ya da yazılım ortamıdır: işlemci ile işletim sisteminin birleşimi. Platform bağımsızlığı (platform independence), aynı derlenmiş programın JVM bulunan her platformda yeniden derlenmeden çalışmasıdır.

Platform bağımsızlığı Java'nın temel özelliklerinden biridir ve “Bir kez yaz, her yerde çalıştır” (Write Once, Run Anywhere) sloganıyla anılır. Program bir kez derlenir; aynı bytecode farklı işletim sistemlerinde çalıştırılır. Bunu mümkün kılan, JVM'in birçok işletim sistemi ve işlemci için ayrı ayrı yazılmış olmasıdır. Windows için yazılmış JVM bytecode'u x86 makine koduna çevirir. ARM işlemcili bir Mac'teki JVM aynı bytecode'u ARM makine koduna çevirir. Farklılık bytecode'da değil, JVM'dedir.

Program.classtek bytecodeWindows için JVMWindows · x86-64Linux için JVMLinux · ARM / x86macOS için JVMmacOS · Apple M
Şekil 2.2.7: Platform bağımsızlığı. Aynı .class dosyası Windows, Linux ve macOS için yazılmış JVM'lerde çalışır.

C gibi dillerde durum farklıdır. C derleyicisi doğrudan makine kodu üretir ve bu kod derlendiği platforma bağlıdır. Windows'ta derlenen bir C programı Linux'ta çalışmaz; her platform için ayrı derleme gerekir. Java'da bu yük JVM'i yazanlara geçer. Programcı tek bir .class dosyası dağıtır, kullanıcının bilgisayarında JVM bulunması yeterlidir.

Java platformunun iki ana bileşeni vardır:

  • JVM uygulamaları çalıştırır ve farklı donanımlara taşınabilirlik sağlar.
  • API (Application Programming Interface, uygulama programlama arayüzü) hazır sınıf kütüphanelerinden oluşur. Grafik arayüz, veri tabanı, ağ iletişimi ve dosya işlemleri gibi birçok işlevi hazır sunar ve geliştirmeyi hızlandırır. System, String, Math ve Scanner bu kütüphanelerdeki sınıflardır.
JDK: geliştirme setijavac · javap · jshell · jar …Çalışma ortamı (JRE)API (hazır kütüphaneler)java.lang · java.util · java.io …JVMbytecode'u çalıştırır
Şekil 2.2.8: Java platformunun katmanları. JDK çalışma ortamını, çalışma ortamı da API kütüphanelerini ve JVM'i içerir.

Şekil 2.2.8 katmanların iç içe olduğunu gösterir. Çalışma ortamı (JRE, Java Runtime Environment) bir Java programını çalıştırmak için gerekenleri, yani JVM'i ve API kütüphanelerini içerir. JDK bunların üstüne derleyiciyi ve öteki geliştirme araçlarını ekler. Program geliştirmek için JDK kurulur. Oracle, Java 11'den beri JRE'yi ayrı bir paket olarak dağıtmaz; JDK hepsini içerir.

2.2.2Değişkenler ve veri tipleri

Bir program veri alır, veriyi işler ve sonuç üretir. Bu sürecin her anında verinin bir yerde durması gerekir. Değişkenler bu işi görür. Java her değişkenin hangi türden veri tuttuğunu baştan bilmek ister; bu bilgiye tip denir. Bu kesim değişkenleri, Java'nın sekiz ilkel tipini ve her tipin sınırlarını ele alır.

2.2.2.1Temel programlama unsurları

Her program, hangi dille yazılmış olursa olsun, birkaç temel unsurdan kurulur. Değişkenler verileri saklar. Her değişkenin bir tipi vardır. Değerler ifadelerle (expressions) hesaplanır: sayi > 0 ve toplam + 1 birer ifadedir. Kontrol akışı (control flow) ise programın hangi sırayla ve nasıl çalışacağını belirler. Kontrol akışının iki temel biçimi vardır:

  • Dallanma (branching), bir koşula göre programın farklı kısımlarını çalıştırır. if-else buna örnektir.
  • Döngüler (loops), bir işlemi tekrarlar. for ve while buna örnektir.

Aşağıdaki kod parçası bir değişkeni, bir koşulu ve çıktıyı bir araya getirir:

int sayi = 5;
if (sayi > 0) {
    System.out.println("Sayı pozitif.");
} else {
    System.out.println("Sayı negatif veya sıfır.");
}
Sayı pozitif.

sayi 5 olduğu için sayi > 0 ifadesi doğrudur ve ilk blok çalışır. else bloğu atlanır. sayi 0 ya da negatif olsaydı ilk blok atlanır, ikinci blok çalışırdı. Burada sayi > 0 ifadesinin değeri bir sayı değil, doğru ya da yanlış bilgisidir. Java bu bilgi için ayrı bir tip kullanır: boolean.

2.2.2.2Değişken ve bildirim

Tanım 2.2.7

Değişken (variable), bellekte bir değeri saklayan, adı ve tipi olan bir alandır. Değişkenin kullanılmadan önce tanıtılmasına bildirim (declaration) denir. Bildirimde önce tip, sonra ad yazılır ve bildirim ; ile biter.

Değişken, üzerinde ad yazan bir kutu gibi düşünülebilir. Kutunun etiketinde iki bilgi vardır: adı ve içine ne tür bir şey konabileceği. int etiketli bir kutuya yalnız tam sayı, double etiketli bir kutuya yalnız ondalıklı sayı konur. Kutunun içindeki değer program çalışırken değişebilir; adı ve tipi ise değişmez.

numara1042intfaizOrani0.45doublecevap'E'char
Şekil 2.2.9: Değişkenler etiketli kutular gibidir: numara (int) 1042, faizOrani (double) 0.45, cevap (char) 'E'.

Java'da her değişken kullanılmadan önce bildirilmelidir. Aynı tipten birden çok değişken virgülle ayrılarak tek satırda bildirilebilir:

int numara, cekSayisi, mevcutSayisi;
double miktar, faizOrani;
char cevap;

Birinci satır üç int değişken, ikinci satır iki double değişken, üçüncü satır bir char değişken bildirir. Bildirimin üç biçimi vardır: tip ad;, tip ad1, ad2, ad3; ve tip ad = ilkDeğer;. Sonuncusu bildirimle birlikte değişkene ilk değerini de verir.

Adlar açıklayıcı ve anlamlı seçilmelidir. x adı değişkenin ne tuttuğu hakkında hiçbir şey söylemez. cekSayisi ise bir hesaptaki çek sayısını tuttuğunu hemen belli eder. Birkaç ay sonra kodu yeniden okuyan kişi, çoğu zaman kodu yazan kişinin kendisidir. Anlamlı adlar bu kişinin işini kolaylaştırır.

2.2.2.3Bildirim, atama, güncelleme

Bir değişkenin yaşamı üç olayla ilerler: bildirilir, bir değer alır ve bu değer güncellenir. Aşağıdaki kod üç olayı da içerir:

int yas;
yas = 25;
double boy = 1.78;
yas = yas + 1;
char harf = 'K';
System.out.println(yas + " " + boy + " " + harf);

Program 26 1.78 K yazar. Tablo 2.2.3 her satırdan sonra bellekteki kutuları gösterir.

Tablo 2.2.3: Bildirim, atama ve güncelleme izlemesi. “?” değer atanmamış kutuyu, boş hücre henüz bildirilmemiş değişkeni gösterir.
SatırKodyasboyharfAçıklama
1int yas;?kutu ayrılır, içi boştur
2yas = 25;25atama: kutuya 25 konur
3double boy = 1.78;251.78bildirim ve ilk değer tek satırda
4yas = yas + 1;261.78önce 25 + 1 = 26 hesaplanır
5char harf = 'K';261.78'K'karakter tek tırnakla yazılır
6println(…)261.78'K'çıktı: 26 1.78 K
yas?int yas25int yas26intboy1.78double
Şekil 2.2.10: Değişkenin yaşamından üç an. (a) int yas; kutuyu ayırır, içi boştur. (b) yas = 25; kutuya 25'i koyar. (c) yas = yas + 1; sonrası kutuda 26 vardır; eski değer kaybolmuştur.

1. satırdan sonra yas adlı kutu vardır ama içinde değer yoktur. Bu durumda yas okunamaz; okumaya çalışan bir kod derlenmez. 4. satır bir güncellemedir. Önce sağ taraf hesaplanır: kutudaki 25 okunur ve 1 eklenir. Sonuç olan 26 aynı kutuya yazılır ve eski değer kaybolur. 6. satırdaki yas + " " + boy + " " + harf ifadesinde + işareti sayıları toplamaz. İşlenenlerden biri metin olduğu için değerleri metne ekler. Bu işleme dizgi birleştirme denir.

2.2.2.4Sekiz ilkel tip

Tanım 2.2.8

Veri tipi (data type), bir değişkenin hangi değerleri alabileceğini, bellekte ne kadar yer kapladığını ve üzerinde hangi işlemlerin yapılabileceğini belirler. Java'nın dile gömülü sekiz temel tipine ilkel tip (primitive type) denir: byte, short, int, long, float, double, char ve boolean.

Sekiz tip dört gruba ayrılır:

  • Tam sayı: byte, short, int, long.
  • Kesirli (ondalıklı) sayı: float, double.
  • Karakter: char.
  • Mantıksal: boolean.
byte1 baytshort2 baytint4 baytlong8 baytfloat4 baytdouble8 baytchar2 baytbooleantrue / false (boyutu JVM'e bağlı)
Şekil 2.2.11: İlkel tiplerin bellekte kapladığı yer: byte 1, short 2, int 4, long 8, float 4, double 8, char 2 bayt. boolean'ın boyutunu JVM belirler.

Her tipin bellekte kendine özgü bir boyutu ve buna bağlı bir değer aralığı vardır. Bit sayısı arttıkça tip daha büyük sayıları tutar, ama bellekte daha çok yer kaplar. C dilinde int'in boyutu derleyiciye ve işlemciye göre değişebilir. Java'da ise boyutlar dilin tanımında sabittir: int her platformda 32 bittir. Bu sabitlik de platform bağımsızlığının bir parçasıdır.

Günlük kullanımda tam sayılar için int, ondalıklı sayılar için double varsayılan seçimdir. byte ve short çoğunlukla büyük dizilerde bellek kazanmak ya da dosyadan bayt okumak gibi özel durumlarda kullanılır. float ise grafik ve bazı gömülü sistemler dışında pek tercih edilmez.

2.2.2.5Tam sayı tipleri

Tablo 2.2.4: Tam sayı tipleri, bellek boyutları ve değer aralıkları.
TipBellekEn küçük değerEn büyük değerÖrnek
byte1 bayt (8 bit)−128127byte b = 100;
short2 bayt (16 bit)−32.76832.767short s = 32000;
int4 bayt (32 bit)−2.147.483.6482.147.483.647int n = 1_000_000;
long8 bayt (64 bit)−9.223.372.036.854.775.8089.223.372.036.854.775.807long l = 3000000000L;
byte8 bitshort16 bitint32 bitlong64 bit
Şekil 2.2.12: Tam sayı tiplerinin bit genişlikleri: byte 8, short 16, int 32, long 64 bit.

Dört tipin hepsi işaretlidir (signed): hem pozitif hem negatif değer alır. n bitlik bir tipin aralığı −2n−1 ile 2n−1 − 1 arasıdır. byte için n = 8'dir ve aralık −27 = −128 ile 27 − 1 = 127 arasında kalır. Pozitif tarafın bir eksik olmasının nedeni, sıfırın da pozitif yarıda yer kaplamasıdır: 8 bit 256 farklı değer verir; bunların 128'i negatif, biri sıfır, 127'si pozitiftir.

Sınırları ezberlemek gerekmez. Java her tip için sabitler sunar: Byte.MIN_VALUE, Byte.MAX_VALUE, Integer.MAX_VALUE, Long.MIN_VALUE gibi. Yine de int'in üst sınırının yaklaşık 2,1 milyar olduğunu bilmek yararlıdır. Dünya nüfusu, milisaniye cinsinden bir ay ya da bir ülkenin bütçesi bu sınırı aşar.

Kodda doğrudan yazılan bir tam sayı, örneğin 42, int kabul edilir. int aralığını aşan bir sayı long olarak yazılacaksa sonuna L eklenir: 3000000000L. Küçük harf l de geçerlidir ama 1 rakamına çok benzediği için büyük harf tercih edilir. L unutulursa derleyici integer number too large iletisi verir: sayı long değişkene atanmadan önce int olarak okunmaya çalışılır ve sığmaz.

2.2.2.6Ondalıklı sayı tipleri

Ondalıklı sayılar bilgisayarda kayan noktalı (floating-point) biçimde saklanır. Bu biçim, bilimsel gösterimin ikili tabandaki karşılığıdır. Sayı bir işaret, bir üs ve bir kesirden oluşur: 6,02 × 1023 sayısında 6,02 kesir, 23 üstür. Kesir anlamlı basamakları, üs ise virgülün yerini belirler. Virgül sayının büyüklüğüne göre “kayar”; ad buradan gelir.

Tablo 2.2.5: Ondalıklı sayı tipleri. “En küçük pozitif”, sıfırdan farklı en küçük pozitif değerdir.
TipBellekEn küçük pozitifEn büyük (±)Anlamlı basamak
float4 bayt (32 bit)≈ 1,4 × 10−45≈ 3,4028235 × 1038≈ 7
double8 bayt (64 bit)≈ 4,9 × 10−324≈ 1,7976931348623157 × 10308≈ 15–16
float±üs 8kesir (anlamlı basamaklar) 23double±üs 11kesir (anlamlı basamaklar) 52
Şekil 2.2.13: Kayan noktalı sayının bitleri. float: 1 işaret, 8 üs, 23 kesir biti. double: 1 işaret, 11 üs, 52 kesir biti.

Şekil 2.2.13 iki tipin farkını gösterir. double'ın kesir kısmı 52 bittir; bu, yaklaşık 15–16 onluk basamağa karşılık gelir. float'ın 23 bitlik kesri ise yaklaşık 7 basamak tutar. double çift duyarlıklıdır (double precision): float'tan hem daha geniş bir aralığı hem daha fazla basamağı vardır. Tablodaki negatif sınırlar, en büyük değerlerin eksi işaretlisidir.

Kodda yazılan ondalıklı bir sayı, örneğin 3.14, double kabul edilir. float için sonuna f eklenir: 3.14f. Aradaki basamak farkı küçük bir hesapta bile görülür:

System.out.println(1.1f * 3);   // 3.3000002
System.out.println(1.1 * 3);    // 3.3000000000000003

İki sonuç da tam 3,3 değildir. float'taki hata yedinci basamakta, double'daki hata on altıncı basamakta görünür. Hatanın kaynağı sayının kendisidir: 1,1 ikili tabanda sonlu bir kesirle yazılamaz ve bellekte en yakın değere yuvarlanmış olarak durur.

2.2.2.7char ve boolean

char tek bir karakter tutar. Bellekte 2 bayt (16 bit) yer kaplar ve işaretsizdir: değer aralığı 0 ile 65.535 arasındadır. Her değer bir Unicode karakter kodudur. Unicode, dünyadaki yazı sistemlerinin karakterlerine birer numara veren uluslararası bir standarttır. char sabitleri tek tırnakla yazılır: 'A', 'ş', '7'. Tek tırnak ile çift tırnak arasındaki fark önemlidir: 'A' bir char, "A" ise tek karakterlik bir String'dir.

Bir karakterin kodunu bilmek, karakterlerle hesap yapmayı mümkün kılar. Üç kod sık kullanılır: 'A' = 65, 'a' = 97, '0' = 48. İngilizce alfabedeki harflerin ve rakamların kodları ASCII tablosundakilerle aynıdır ve ardışık dizilir: 'B' = 66, 'C' = 67, '1' = 49. '7' karakterinin kodu 7 değil, 55'tir. Rakam karakteri ile rakamın sayı değeri ayrı şeylerdir.

boolean mantıksal bir değer tutar. Yalnız iki değeri vardır: true (doğru) ve false (yanlış). Taşıdığı bilgi bir bittir; bellekte kaç bayt tutacağını JVM belirler. C dilinin aksine Java'da boolean bir sayı değildir. boolean bitti = 0; derlenmez, if (1) de yazılamaz. boolean değerler koşullarda ve karşılaştırma sonuçlarında kullanılır:

boolean gectiMi = puan >= 50;

Bu satırda puan >= 50 karşılaştırmasının sonucu, yani true ya da false, gectiMi değişkenine yazılır. puan 72 ise gectiMi true olur.

char harf = 'A';
boolean dogruMu = true;
System.out.println(harf);
System.out.println(dogruMu);
System.out.println(!dogruMu);
A
true
false

println bir char aldığında karakterin kendisini yazar: A. Kodunu, yani 65'i yazmaz. boolean değerler true ve false sözcükleriyle yazılır. Üçüncü satırdaki ! işareti mantıksal DEĞİL işlemidir ve true'yu false'a çevirir.

2.2.2.8Sınırları program ile görmek

Tabloları ezberlemek yerine sınırları bir programla yazdırmak mümkündür. Her ilkel tipin bir sarmalayıcı sınıfı (wrapper class) vardır: byte için Byte, int için Integer, double için Double, char için Character. Bu sınıflar tipin sınırlarını MIN_VALUE ve MAX_VALUE sabitleriyle sunar.

System.out.println("byte  : " + Byte.MIN_VALUE + " .. " + Byte.MAX_VALUE);
System.out.println("short : " + Short.MIN_VALUE + " .. " + Short.MAX_VALUE);
System.out.println("int   : " + Integer.MIN_VALUE + " .. " + Integer.MAX_VALUE);
System.out.println("long  : " + Long.MIN_VALUE + " .. " + Long.MAX_VALUE);
System.out.println("float : " + Float.MIN_VALUE + " .. " + Float.MAX_VALUE);
System.out.println("double: " + Double.MIN_VALUE + " .. " + Double.MAX_VALUE);
System.out.println("char  : " + (int) Character.MIN_VALUE + " .. "
                              + (int) Character.MAX_VALUE);
byte  : -128 .. 127
short : -32768 .. 32767
int   : -2147483648 .. 2147483647
long  : -9223372036854775808 .. 9223372036854775807
float : 1.4E-45 .. 3.4028235E38
double: 4.9E-324 .. 1.7976931348623157E308
char  : 0 .. 65535

Çıktıdaki 4.9E-324 bilimsel gösterimdir ve 4,9 × 10−324 anlamına gelir. E harfi “çarpı 10 üzeri” diye okunur. float ve double satırlarında soldaki değer en küçük negatif sayı değil, sıfırdan büyük en küçük sayıdır. Bu durum tam sayı tiplerinden farklıdır ve sık karıştırılır. char satırında (int) dönüşümü kullanılmıştır. Dönüşüm olmasaydı println bu iki değeri karakter olarak yazmaya çalışırdı. Kod 0 görünmeyen bir denetim karakteridir, 65.535 de yazdırılabilir bir karakter değildir.

2.2.2.9Taşma

Tanım 2.2.9

Taşma (overflow), bir işlemin sonucunun tipin değer aralığına sığmamasıdır. Java'da tam sayı taşması hata vermez: değer aralığın öbür ucuna sarar.

Taşmayı saatle düşünmek kolaydır. Saat 12'yi gösterirken bir saat ilerletilirse 13'e değil, 1'e döner. Tam sayı tipleri de böyle davranır: en büyük değere 1 eklenince en küçük değere dönülür. Önemli olan, bu dönüşün sessizce olmasıdır. Program ne derleme hatası ne de çalışma hatası verir; yanlış sonuçla yoluna devam eder.

int enBuyuk = Integer.MAX_VALUE;
System.out.println(enBuyuk);
System.out.println(enBuyuk + 1);   // taşma!
byte sayi1 = 80;
byte sayi2 = 50;
byte toplam = (byte) (sayi1 + sayi2);
System.out.println(toplam);        // 130 değil
long dogru = (long) enBuyuk + 1;
System.out.println(dogru);
2147483647
-2147483648
-126
2147483648

Satırlar şöyle çalışır:

  • enBuyuk int'in en büyük değerini, 2.147.483.647'yi tutar ve ilk satırda bu değer yazılır.
  • enBuyuk + 1 iki int arasındaki bir toplamadır ve sonucu da int'tir. Sonuç sığmaz ve en küçük değere sarar: -2147483648.
  • sayi1 + sayi2 80 + 50 = 130 verir. 130, byte sınırı olan 127'yi aşar. (byte) dönüşümü sonucun son 8 bitini alır ve değer 130 − 256 = −126 olur.
  • (long) enBuyuk + 1 ifadesinde dönüşüm toplamadan önce yapılır. İşlem long tipinde yürür; 64 bitlik aralık 2.147.483.648'i rahatça tutar.
125126127−128−127−126+1byte: en büyük değere 1 eklenince en küçüğe “sarar”
Şekil 2.2.14: Taşma. byte tipinde 127'ye 1 eklenince değer −128 olur; sayı ekseni bir çember gibi kapanır.

Çözüm daha geniş bir tip kullanmaktır. Ancak dönüşümün yeri önemlidir. long yanlis = enBuyuk + 1; satırı yine −2147483648 verir. Sağ taraf iki int arasında hesaplanır ve önce taşar; long değişkene ancak taşmış sonuç atanır. Dönüşüm işlemden önce yapılmalıdır: (long) enBuyuk + 1 ya da enBuyuk + 1L.

Örnek 2.2.1

Bir milyonun karesi int ile hesaplanırsa ne çıkar? int milyon = 1_000_000; iken milyon * milyon ifadesinin gerçek değeri 1012'dir; bu sayı int'in yaklaşık 2,1 milyarlık sınırını çok aşar. Java'nın verdiği sonuç -727379968'dir. Bu değer, 1012'nin ikili gösteriminin son 32 bitinin işaretli okunuşudur. Aynı hesap (long) milyon * milyon diye yazılınca doğru sonuç, 1000000000000 elde edilir. Sonucun negatif çıkması burada taşmayı ele verir. Her taşma bu kadar belirgin değildir; sonuç pozitif ama yanlış da olabilir.

Taşmanın bit düzeyinde nasıl oluştuğunu bir byte sayacı üzerinde adım adım izleyelim:

byte b = 125;
for (int i = 1; i <= 4; i++) {
    b++;
    System.out.println(b);
}

b 125 ile başlar ve döngü dört kez çalışır. Tablo 2.2.6 her turdaki değeri ve ikili gösterimi verir.

Tablo 2.2.6: byte sayacının izlemesi. En soldaki bit işaret bitidir ve ağırlığı −128'dir.
ii <= 4b++ sonrasıİkiliÇıktı
——125 (başlangıç)01111101
1doğru12601111110126
2doğru12701111111127
3doğru−12810000000-128
4doğru−12710000001-127
5yanlış——döngü biter
b (byte)= 1270-12816413211618141211−1280127 b (byte)= -1281-12806403201608040201−1280127
Şekil 2.2.15: Taşma anı. (a) b = 127, ikili 01111111: byte'ın en büyük değeri. (b) 1 eklenince ikili toplama 10000000 verir; işaret biti 1 olur ve değer −128'e sarar.

Taşmanın nedeni ikili toplamanın kendisidir. 127'nin ikili gösterimi 01111111'dir. Buna 1 eklenince bütün 1'ler sıfırlanır ve elde en soldaki bite geçer: 10000000. Bu bit işaret bitidir ve ağırlığı +128 değil, −128'dir. Diğer bitler sıfır olduğu için değer −128 olur. Bu gösterime ikiye tümleyen (two's complement) gösterim denir. İşlemci aynı toplama devresini hem pozitif hem negatif sayılar için kullanabildiği için bu gösterim seçilmiştir. Program 126, 127, −128, −127 yazar ve hiçbir uyarı vermez.

Tablodaki b++ satırı ayrıca dikkat ister. b = b + 1; yazılsaydı kod derlenmezdi, çünkü b + 1 bir int ifadesidir ve byte'a ancak açık dönüşümle atanabilir. ++ ve bileşik atama operatörleri ise dönüşümü kendileri yapar. Taşma da bu gizli dönüşümün sonucudur.

2.2.2.10İlkel tipler ve referans tipleri

Tanım 2.2.10

İlkel tipteki bir değişken değerin kendisini saklar. Referans tipindeki (reference type) bir değişken ise bir nesneyi gösterir: değişkende nesnenin kendisi değil, bellekteki yeri, yani nesneye bir başvuru tutulur.

yığın (stack): yerel değişkenlerint yas20double ort3.25String ad•heap: nesnelerString nesnesi"Ayşe"ilkel değişken değeri doğrudan tutar · referans değişken nesnenin adresini tutar
Şekil 2.2.16: İlkel ve referans tipleri. int ve double değişkenleri değeri doğrudan tutar; String değişkeni öbek bellekteki (heap) nesneye bir okla başvurur.

İlkel tipler sekiz tanedir ve adları küçük harfle başlar. Geri kalan bütün tipler referans tipidir. String (karakter dizisi), diziler, sınıflar, arayüzler ve enum'lar bu gruptadır. Referans tiplerinin adları büyük harfle başlar. Nesneler genellikle new ile oluşturulur. String bu kuralın sık görülen bir istisnasıdır: "Ali" gibi bir dizgi sabiti, new yazılmadan da bir nesne oluşturur.

Hiçbir nesneyi göstermeyen bir referansın değeri null'dır. null ile boş dizgi karıştırılmamalıdır. Boş dizgi "" gerçek bir nesnedir ve uzunluğu sıfırdır: "".length() 0 verir. null ise “burada nesne yok” demektir. null bir referans üzerinde metot çağrılırsa program NullPointerException ile durur. Yerel bir String değişkeni için üç durum birbirinden ayrıdır. Değer atanmamış değişkeni yazdıran kod derlenmez. null atanmış değişken null yazar. "" atanmış değişken ise boş bir satır yazar.

İki tip grubunun farkı atamada da görülür. int a = 5; int b = a; satırlarından sonra b, 5'in ayrı bir kopyasını tutar; b değişince a etkilenmez. İki referans değişkeni ise aynı nesneyi gösterebilir. Bu ayrım nesne yönelimli programlamanın temel konularından biridir.

2.2.3Tanımlayıcılar ve anahtar kelimeler

Bir programdaki değişkenlerin, metotların ve sınıfların her birinin bir adı vardır. Programcı bu adları seçmekte özgürdür, ama bu özgürlüğün sınırları bellidir. Bazı karakterler adlarda kullanılamaz, bazı sözcükler ise dile ayrılmıştır. Ayrıca Java topluluğunun uyduğu yazım alışkanlıkları vardır. Bu kesim üç düzeyi sırayla ele alır: derleyicinin zorunlu kuralları, ayrılmış sözcükler ve uzlaşımlar.

2.2.3.1Tanımlayıcı kuralları

Tanım 2.2.11

Tanımlayıcı (identifier), değişkenlere, sınıflara, metotlara ve öteki program öğelerine verilen addır.

Tanımlayıcılar şu kurallara uyar:

  • Yalnız harf, rakam (0–9), alt çizgi _ ve dolar işareti $ içerebilir. Boşluk, -, @, . gibi karakterler bulunamaz.
  • İlk karakter harf, _ ya da $ olmalıdır. Tanımlayıcı rakamla başlayamaz.
  • Anahtar kelimeler ve true, false, null sabitleri tanımlayıcı olamaz.
  • Uzunluk sınırı yoktur.
  • Büyük ve küçük harf ayrıdır: kisiAdi ile kisiadi farklı değişkenlerdir. Bildirimdeki yazımdan farklı yazılan bir ad cannot find symbol hatası verir.

Rakamla başlama yasağının bir nedeni vardır. Derleyici bir rakam gördüğünde sayı okumaya başlar. 2yas ile karşılaşan derleyici önce 2 sayısını okur, ardından gelen yas'ı ayrı bir sözcük sayar. Aynı nedenle ad-soyad, “ad eksi soyad” diye okunur; çünkü - çıkarma operatörüdür.

Java harf olarak yalnız İngilizce harfleri değil, bütün Unicode harflerini kabul eder. öğrenci adlı bir değişken derlenir ve çalışır. Yine de adlarda yalnız İngilizce harfler kullanılması önerilir. Dosyalar farklı karakter kodlamasıyla açılınca Türkçe harfler bozulabilir ve kod başka sistemlerde derlenmez hâle gelebilir. Bu yüzden adlar öğrenciNo yerine ogrenciNo, sayaç yerine sayac biçiminde yazılır. Geçerli örnekler: kullaniciAdi, toplamPuan, ogrenci_adi, veri1, _sayac.

2.2.3.2Geçerli ve geçersiz adlar

Tablo 2.2.7: Tanımlayıcı örnekleri. ✓ geçerli, ✗ geçersiz.
AdDurumNeden?
ogrenciNo✓harf ile başlıyor
sayi_2✓harf, rakam ve _
$fiyat✓$ izinli, ama önerilmez
2yas✗rakamla başlıyor
maas hesabi✗boşluk içeriyor
ad-soyad✗- çıkarma operatörüdür
@isim✗@ izinli değil
class✗anahtar kelime
Class✓büyük C: anahtar kelime değil

$ işareti geçerlidir ama derleyicinin kendi ürettiği sınıf adlarında kullanılır. Programcının bu işareti kullanmaması bu yüzden önerilir. Class derlenir, çünkü Java büyük-küçük harfe duyarlıdır ve anahtar kelimelerin hepsi küçük harflidir. Ancak Class, Java API'sinde var olan bir sınıfın adıdır ve okuyanı şaşırtır.

Geçersiz bir ad her zaman açık bir ileti üretmez. Rakamla başlayan bir ad derleyiciyi şaşırtır; ileti adı değil, deyimi işaret eder:

int 2yas = 20;
Tanim.java:3: error: not a statement
        int 2yas = 20;
        ^
Tanim.java:3: error: ';' expected
        int 2yas = 20;
           ^
2 errors

Derleyici int sözcüğünden sonra bir ad bekler. 2'yi görünce burada bir ad olmadığını anlar ve int'i tek başına anlamsız bir deyim sayar (not a statement). Ardından 2yas kısmını yeni bir deyimin başı gibi okumaya çalışır ve noktalı virgül bekler. İki ileti de asıl sorunu, yani adın rakamla başlamasını söylemez. İletiler kafa karıştırıcı göründüğünde ilk bakılacak yer, satırdaki adların kurallara uyup uymadığıdır.

2.2.3.3Anahtar kelimeler

Tanım 2.2.12

Anahtar kelime (keyword, reserved word), dilde belirli bir görevi olan ve bu yüzden ayrılmış sözcüktür. Anahtar kelimeler değişken, sınıf ya da metot adı olarak kullanılamaz. Hepsi küçük harfle yazılır.

Java 21'de 51 anahtar kelime vardır. Tablo 2.2.8 bunları görevlerine göre gruplar.

Tablo 2.2.8: Java 21'in 51 anahtar kelimesi, görev gruplarına göre.
GrupAnahtar kelimelerSayı
Tiplerboolean byte char short int long float double void9
Kontrol akışıif else switch case default for while do break continue return11
Sınıf ve nesneclass interface enum extends implements new this super instanceof package import11
Düzenleyicilerpublic protected private static final abstract native synchronized volatile transient strictfp11
İstisnalartry catch finally throw throws assert6
Ayrılmış, kullanılmıyorconst goto _3

Üç sözcük sık sık anahtar kelime sanılır: true, false ve null. Bunlar anahtar kelime değil, sabit değerdir (literal). Yine de onlar da ad olarak kullanılamaz. Bir başka grup bağlamsal kelimelerdir. var (Java 10 ile), yield (Java 14 ile) ve record (Java 16 ile) gibi sözcükler yalnız belirli yerlerde özel anlam taşır. Dile sonradan eklendikleri için, eski programlarda değişken adı olarak kullanılmış olabilirler. Bu kodların bozulmaması için tam anahtar kelime yapılmamışlardır. String, System ve main ise hiçbir gruba girmez. Bunlar API'deki sınıfların ve metotların adlarıdır. int String = 3; satırı bu yüzden derlenir, ama okuyanı şaşırtacağı için asla yazılmamalıdır.

Bir anahtar kelime ad olarak kullanılınca derleyici, o sözcüğün başlattığı yapıyı bekler. new bir nesne oluşturma ifadesini başlatır; ardından bir sınıf adı ve parantez gelmelidir:

int new = 5;
Anahtar.java:3: error: not a statement
        int new = 5;
        ^
... (toplam 4 hata)

Tek bir hatalı sözcük dört ileti üretir. Derleyici new'den sonra bir sınıf adı (<identifier> expected) ve ardından ( ya da [ bekler. İletilerin hiçbiri “new anahtar kelimedir” demez. Tanımlayıcı hatalarında olduğu gibi, anlamsız görünen bir ileti dizisi çoğu zaman adın kendisinde bir sorun olduğunu gösterir.

2.2.3.4Anahtar kelime grupları

Anahtar kelimelerin çoğu ancak ilgili yapılar yazılırken tam anlamını kazanır. Başlangıçta her birinin ne iş gördüğünü kısaca bilmek yeterlidir. Tip anahtar kelimeleri ilkel tiplerin adlarıdır; onlara void eklenir. void bir tip değil, bir metodun değer döndürmediğini belirten işarettir: public static void main.

Tablo 2.2.9: Kontrol akışı anahtar kelimeleri.
KelimeGörevi
if / elseKoşulu denetler; doğruysa if bloğu, değilse else bloğu çalışır.
switchBir değişkenin farklı değerlerine göre farklı işlemler yapar.
caseswitch içinde bir durumu (değeri) temsil eder.
defaultswitch içinde hiçbir durum eşleşmediğinde yapılacak işlem.
forBir işlemi bir sayaçla, koşul sağlandıkça tekrarlar.
whileKoşul sağlandığı sürece işlemi tekrarlar.
dodo-while döngüsü: gövde en az bir kez çalışır, koşul sonda denetlenir.
breakDöngüden ya da switch bloğundan hemen çıkar.
continueDöngünün o turunun kalanını atlar, bir sonraki tura geçer.
returnMetodu bitirir; gerekiyorsa bir sonuç döndürür.
Tablo 2.2.10: Sınıf ve nesne anahtar kelimeleri.
KelimeGörevi
classSınıf tanımlar. Sınıflar nesnelerin şablonudur ve Java programlarının temel yapı taşıdır.
interfaceArayüz tanımlar: bir sınıfın sağlaması gereken metotları belirleyen sözleşme.
enumSabit değerlerden oluşan bir tip: enum Renk { KIRMIZI, YESIL }.
extendsKalıtım: bir sınıfın başka bir sınıfın alan ve metotlarını alması.
implementsBir sınıfın bir arayüzü uygulaması, yani arayüzün metotlarını sağlaması.
newNesne oluşturur: bellekte yer ayırır ve ilk değerleri verir.
thisNesnenin kendisine başvuru; aynı adlı alanı yerel değişkenden ayırt eder.
superÜst sınıfın metotlarına, alanlarına ve yapıcısına erişir.
instanceofBir nesnenin belirli bir sınıf ya da arayüz tipinden olup olmadığını denetler.
package / importpackage sınıfın paketini (ad alanını) belirler; import başka paketteki sınıfları kullanıma açar.
Tablo 2.2.11: Düzenleyiciler (modifiers).
KelimeGörevi
publicHer yerden, bütün sınıflardan erişilebilir.
protectedAynı paketten ve alt sınıflardan erişilebilir.
privateYalnız aynı sınıfın içinden erişilebilir.
staticÜye nesneye değil sınıfa aittir; bütün nesneler arasında paylaşılır.
finalDeğişken: değeri değişmez. Metot: alt sınıfta yeniden yazılamaz. Sınıf: kalıtılamaz.
abstractSoyut sınıf (nesnesi oluşturulamaz) ya da gövdesiz metot; alt sınıf metodun gövdesini yazmak zorundadır.
nativeGövdesi Java dışında, platforma özgü kodla (C/C++) yazılmış metot; JNI ile bağlanır.
synchronizedÇok iş parçacıklı programda bir bloğa aynı anda tek iş parçacığının girmesini sağlar.
volatileDeğişkenin her iş parçacığında her zaman güncel değeriyle okunmasını sağlar.
transientNesne serileştirilirken (kaydedilirken) bu alan dikkate alınmaz.
strictfpOndalıklı işlemlerin her platformda aynı sonucu vermesi. Java 17'den beri bu zaten varsayılandır; kelimenin etkisi kalmadı.

İstisna grubundaki kelimeler program çalışırken oluşan hatalarla ilgilidir. try hata verebilecek kodu çevreler. catch bu blokta oluşan istisnayı yakalar ve işler. finally bloğu, istisna olsa da olmasa da en sonda çalışır; açılan bir dosyanın kapatılması bu bloğa yazılır. throw bir istisna nesnesi fırlatır ve normal akışı keser. throws metot başlığında, metodun fırlatabileceği istisna türlerini listeler. assert bir koşulun doğru olduğunu denetler. Bu denetim varsayılan olarak kapalıdır ve java -ea ile açılır.

Son grup ilginçtir. const ve goto ayrılmış ama hiçbir işlevi olmayan sözcüklerdir. C ve C++'tan gelen programcılar bu sözcükleri yanlışlıkla kullanmasın diye ayrılmışlardır. Tek başına alt çizgi _ ise Java 9'dan beri ad olamaz. Java 21'de int _ = 3; yazılınca derleyici, bu kullanımın bir önizleme özelliği olduğunu ve kapalı olduğunu bildirir. Java 22 ile _ “kullanılmayan değişken” anlamını kesin olarak kazandı.

2.2.3.5İsimlendirme kuralları

Derleyicinin zorunlu kurallarının yanında, Java topluluğunun uyduğu yazım alışkanlıkları da vardır. Bunlara isimlendirme kuralları (naming conventions) denir.

Tablo 2.2.12: Java isimlendirme kuralları.
Ne?KuralÖrnekler
Sınıf ve arayüzPascalCase, büyük harfle başlarString, Ogrenci, MerhabaDunya
Değişken, alan, metotcamelCase, küçük harfle başlarfirstName, classAverage, hesapla()
İlkel tiplerküçük harf (anahtar kelimedir)int, float, boolean
İsimlendirilmiş sabitBÜYÜK_HARF, kelimeler _ ile ayrılırPI, GUNLER_HAFTADA, MAX_SAYI
Pakettamamen küçük harfsyntax, tr.edu.giresun

camelCase yazımında ilk kelime küçük, sonraki kelimeler büyük harfle başlar. Büyük harfler devenin hörgüçlerine benzetilir ve ad buradan gelir. PascalCase yazımında ise ilk kelime de dahil her kelime büyük harfle başlar. Bu ayrım kodu okuyan kişiye anında bilgi verir: Ogrenci bir sınıf, ogrenci bir değişken, OGRENCI_SAYISI bir sabittir.

class OgrenciKaydi { }    // sınıf
String isimSoyisim;       // değişken
boolean evliMi;           // evet/hayır
final int MAX_SAYI = 100; // sabit

Bunlar derleyici kuralı değil, uzlaşımdır. Uyulmazsa kod yine derlenir. class ogrenci_kaydi ya da int ToplamPuan hatasız çalışır. Ancak böyle bir kodu başka bir Java programcısının okuması zorlaşır. Uzlaşımlar ekip çalışmasının ortak dilidir. Java API'sinin kendisi de bu kurallara uyar: String bir sınıf, length() bir metot, Integer.MAX_VALUE bir sabittir.

2.2.4Atama, sabitler ve dönüşüm

Değişkenler bildirildikten sonra değer alır ve bu değerler değişir. Bu kesim, değerin değişkene nasıl konduğunu, kodda doğrudan yazılan değerlerin nasıl okunduğunu ve bir değerin bir tipten ötekine nasıl geçtiğini anlatır. Tip dönüşümü, başlangıç düzeyindeki Java programlarında en çok hata çıkan konulardan biridir.

2.2.4.1Atama ifadeleri

Tanım 2.2.13

Atama (assignment), bir değişkene değer vermektir. = atama operatörüyle yazılır: değişken = ifade;. Önce sağ taraftaki ifade hesaplanır, sonra sonuç sol taraftaki değişkene yazılır.

Sağ taraftaki ifade bir sabit değer, başka bir değişken ya da bir işlem olabilir:

int sayi, sonuc;
int sayi1 = 4, sayi2 = 6;
String isim;

sayi = 10;             // sabit değer
sonuc = sayi1 + sayi2; // toplam: 10
isim = "Ahmet";        // metin
sayi = sayi * 2;       // 10 → 20

İlk üç satır bildirimdir. sayi1 ve sayi2 bildirimle birlikte değer alır; sayi, sonuc ve isim ise henüz boştur. Ardından dört atama gelir ve her birinin sağ tarafı farklıdır. Birincisi bir sabittir (10), ikincisi iki değişkenin toplamıdır (4 + 6 = 10). Üçüncüsü bir metin ("Ahmet"), dördüncüsü değişkenin kendi değerini kullanan bir işlemdir. Son satırdan sonra sayi 20 olur.

sayi = sayi * 2;10 * 2 = 20① sağ taraf hesaplanır20② sola yazılır
Şekil 2.2.17: sayi = sayi * 2: önce sağ taraf 10 * 2 = 20 hesaplanır, sonra sonuç sayi kutusuna yazılır.

= işareti matematikteki eşitlik değildir. Matematikte x = x + 1 çelişkili bir denklemdir; hiçbir sayı kendisinin bir fazlasına eşit olamaz. Java'da ise x = x + 1 “x'in yeni değeri, eski değerinin bir fazlası olsun” demektir. Okurken “eşittir” yerine “alır” demek bu yanılgıyı önler: “x, x + 1'i alır.”

Atamanın sol tarafında yalnız bir değişken bulunabilir. 5 = sayi; satırı derlenmez:

Sol.java:4: error: unexpected type
        5 = sayi;
        ^
  required: variable
  found:    value
1 error

İleti, sol tarafta bir değişken beklendiğini (required: variable) ama bir değer bulunduğunu (found: value) söyler. Atamada tipler de uyumlu olmalıdır. String bir değişkene sayı, int bir değişkene metin atanamaz. int x = 3; String s = x; satırları incompatible types: int cannot be converted to String iletisini verir.

Atama aynı zamanda bir ifadedir ve bir değer üretir: atanan değeri. Bu yüzden a = b = 0; yazılabilir. Atama sağdan sola gruplanır; önce b = 0 yapılır, bu ifadenin değeri olan 0 da a'ya atanır. İki değişken birden sıfırlanmış olur.

2.2.4.2Bileşik atama

Tanım 2.2.14

Bileşik atama (compound assignment) operatörleri bir işlemi ve atamayı birleştirir: +=, -=, *=, /=, %=. x op= y yazımı, x = x op y ile aynı işi yapar.

int x = 5;
int y = 10;
x = x + y;
x -= y;
x *= 2;
x /= 4;
x %= 3;
System.out.println("x = " + x + ", y = " + y);

Program x = 2, y = 10 yazar. Tablo 2.2.13 her satırdan sonraki değerleri verir.

Tablo 2.2.13: Bileşik atama izlemesi.
SatırAçık yazımıHesapxy
int x = 5;——5
int y = 10;——510
x = x + y;—5 + 101510
x -= y;x = x - y15 − 10510
x *= 2;x = x * 25 × 21010
x /= 4;x = x / 410 / 4 (tam sayı bölmesi)210
x %= 3;x = x % 32'nin 3'e bölümünden kalan210
x15inty10int x10inty10int x2inty10int
Şekil 2.2.18: Bileşik atamanın üç anı. (a) x = x + y sonrası x = 15. (b) x *= 2 sonrası x = 10. (c) x /= 4 sonrası x = 2; y baştan sona 10'dur.

İki adım dikkat ister. x /= 4 satırında 10 / 4 iki tam sayı arasında yapılır ve sonuç 2,5 değil 2'dir; kesirli kısım atılır. x %= 3 satırında 2, 3'ten küçük olduğu için bölüm 0, kalan 2'dir. y hiçbir satırda sol tarafta olmadığı için değişmez.

Bileşik atamanın iki inceliği vardır. Birincisi, sağ taraf bütün olarak hesaplanır, sonra işlem yapılır. k *= 2 + 3 ifadesi k = k * 2 + 3 değil, k = k * (2 + 3) demektir; k 7 ise sonuç 35'tir. İkincisi, bileşik atama sonucu sol taraftaki değişkenin tipine kendisi dönüştürür. byte b = 10; b += 5; derlenir ve b 15 olur. Oysa b = b + 5; derlenmez, çünkü b + 5 bir int'tir. Bu kolaylığın bir bedeli de vardır: int m = 10; m /= 0.3; hata vermeden derlenir ve m 33 olur. 10 / 0,3 = 33,33… hesaplanır, ardından kesir sessizce atılır.

2.2.4.3İlk değer ve varsayılan değerler

Bir değişken değer atanmadan bildirilebilir. İlk değer bildirimle birlikte de verilebilir: int yas = 25;. Bu işleme ilk değer verme (initialization) denir. Değerin ne zaman verildiği, değişkenin nerede bildirildiğine göre iki farklı kurala bağlıdır.

Yerel değişkenler, yani bir metodun içinde bildirilen değişkenler, varsayılan değer almaz. Değer atanmadan okunan yerel değişken programın derlenmesini engeller:

int sayac;
System.out.println(sayac);
Ilk.java:4: error: variable sayac
  might not have been initialized

Derleyici programın her olası yolunu inceler ve sayac'ın okunduğu noktaya değer almadan ulaşılabildiğini görür. Bu denetim, rastgele bir bellek içeriğinin sessizce kullanılmasını önler. C dilinde aynı hata derlenir ve program her çalıştırılışında farklı, anlamsız bir sayı yazabilir.

Sınıf alanları ise otomatik olarak varsayılan değer (default value) alır. Tablo 2.2.14 bu değerleri verir.

Tablo 2.2.14: Sınıf alanlarının varsayılan değerleri.
Alan tipiVarsayılan
int, long, byte, short0
double, float0.0
char'\u0000' (kod 0)
booleanfalse
String ve öteki referanslarnull

Bir sınıfın değer verilmemiş int, long, double, float, char (kodu), boolean ve String alanları sırayla yazdırılınca 0 0 0.0 0.0 0 false null çıkar. Yine de varsayılan değerlere güvenmemek ve değeri açıkça vermek iyi bir alışkanlıktır. Açıkça yazılan int sayac = 0;, okuyan kişiye sıfırın bilinçli bir seçim olduğunu söyler.

2.2.4.4Sabit değerler

Tanım 2.2.15

Sabit değer (literal), kaynak kodda doğrudan yazılan değerdir: 2, 3.7, 'y', "Ali", true gibi.

Her sabit değerin bir tipi vardır ve bu tip yazılış biçiminden anlaşılır:

  • Tam sayı: + ya da - ile başlayabilir ama virgül içeremez: +42, -17. Okunabilirlik için basamaklar arasına alt çizgi konabilir (Java 7 ile geldi): 1_000_000. Onaltılık sayılar 0x, ikilik sayılar 0b ile başlar: 0x1F, 0b1010.
  • Ondalıklı: ondalık ayırıcı noktadır. Bilimsel gösterim e ile yazılır: 2.5e3 = 2,5 × 103, 7.68e-2 = 7,68 × 10−2.
  • Karakter: tek tırnak içinde: 'y'. Özel karakterler ters bölü ile yazılır: '\n' yeni satır, '\t' sekme, '\'' tek tırnak, '\\' ters bölü.
  • Metin: çift tırnak içinde: "Merhaba". Mantıksal: true, false.
System.out.println(2.5e3);
System.out.println(7.68e-2);
System.out.println(1_000_000);
System.out.println(0x1F);
System.out.println(0b1010);
System.out.println(3000000000L);
System.out.println("Ali\tVeli\n\"Java\"");
2500.0
0.0768
1000000
31
10
3000000000
Ali	Veli
"Java"

Çıktı birkaç şeyi gösterir. 2.5e3 bir double'dır ve 2500.0 diye yazılır; tam sayı değerli olsa da sonundaki .0 tipini belli eder. Alt çizgiler yalnız kaynak kodda okunurluk içindir, çıktıda görünmez. 0x1F onaltılıkta 1 × 16 + 15 = 31, 0b1010 ikilikte 8 + 2 = 10'dur; println ikisini de onluk tabanda yazar. Son satırdaki metinde \t bir sekme, \n bir satır sonu üretir; \" ise metnin içine çift tırnak koyar. Bu yüzden tek bir println iki satır yazar.

Sık yapılan hata

Türkçedeki ondalık virgülü koda taşımak. double oran = 0,45; derlenmez; Java'da ondalık ayırıcı noktadır: 0.45. Benzer biçimde binlik ayırıcı nokta da yazılamaz: 1.000.000 anlamsızdır. Bin, 1000 ya da 1_000 diye yazılır. Bir başka tuzak da başa sıfır koymaktır: 010 sekizlik tabanda okunur ve değeri 10 değil, 8'dir.

2.2.4.5Ondalık sayıların hassaslığı

Ondalık sayılar sınırlı sayıda bitle ve ikili tabanda saklanır. Onluk tabanda 1/3 = 0,333… sonsuza dek sürer ve sonlu basamakla tam yazılamaz. İkili tabanda da benzer bir durum vardır: 1/10 = 0,000110011…2 diye sonsuz tekrar eder. Onluk tabanda masum görünen 0,1 bu yüzden bilgisayarda yaklaşık saklanır. double'daki 0,1'in tam değeri 0,1000000000000000055511151231257827… gibi bir sayıdır.

System.out.println(1.0 / 3.0);
System.out.println(1.0/3 + 1.0/3 + 1.0/3);
System.out.println(0.1 + 0.2);
System.out.println(0.1 + 0.2 == 0.3);
double t = 0.0;
for (int i = 0; i < 10; i++) t += 0.1;
System.out.println(t);
double fark = (0.1 + 0.2) - 0.3;
System.out.println(Math.abs(fark) < 1e-9);
0.3333333333333333
1.0
0.30000000000000004
false
0.9999999999999999
true

Sonuçlar satır satır şöyle okunur:

  • 1.0 / 3.0 yaklaşık bir değerdir ve 16 basamakla yazılır.
  • Üç tane 1.0/3'ün toplamı tam 1.0 çıkar. Burada yuvarlama hataları birbirini götürmüştür. Bu bir şanstır, kural değildir.
  • 0.1 + 0.2 sonucu 0.30000000000000004'tür. İki sayının küçük hataları toplanır ve sonuç 0.3 sabitinin saklanan değerinden farklı olur. Bu yüzden 0.1 + 0.2 == 0.3 false verir.
  • 0,1'in on kez toplanması tam 1 etmez: 0.9999999999999999. Küçük hatalar her toplamada birikir.
  • Son satırda iki sayının farkının mutlak değeri çok küçük bir sınırla, 10−9 ile karşılaştırılır. Fark yaklaşık 5,55 × 10−17'dir; sınırdan küçük olduğu için sonuç true'dur.

Bu davranış Java'ya özgü değildir. Bütün modern işlemciler kayan noktalı sayıları IEEE 754 standardına göre saklar ve C, Python, JavaScript aynı sonuçları verir. Bundan iki kural çıkar. Birincisi, ondalıklı sayılar == ile karşılaştırılmaz; farkın çok küçük olup olmadığına bakılır: Math.abs(a - b) < 1e-9. İkincisi, kuruşu kuruşuna doğru olması gereken para hesaplarında double kullanılmaz. Bunun için BigDecimal sınıfı vardır; new BigDecimal("0.1") ile new BigDecimal("0.2")'nin toplamı tam olarak 0,3'tür. Bir başka yol, tutarları kuruş cinsinden long tam sayılarla tutmaktır.

Sık yapılan hata

Ondalıklı bir sayaçla döngü kurmak. for (double t = 0; t != 1.0; t += 0.1) döngüsü hiç bitmez. t 1,0'a tam olarak hiç ulaşmaz; 0,9999999999999999'dan sonra 1,0999999999999999'a geçer ve t != 1.0 hep doğru kalır. Döngü sayacı tam sayı olmalıdır; ondalıklı değer gerekiyorsa sayaçtan hesaplanır: double t = i * 0.1;.

2.2.4.6İsimlendirilmiş sabitler

Tanım 2.2.16

İsimlendirilmiş sabit (named constant), final ile bildirilen ve değeri bir kez verildikten sonra değiştirilemeyen değişkendir. Sınıf düzeyindeki kalıbı şudur: public static final Tip SABIT_ADI = değer;.

Kodun içine gömülü, anlamı belirtilmemiş sayılara sihirli sayı denir. gun = 4 * 7; satırındaki 7'nin haftanın gün sayısı olduğu, ancak bağlamdan tahmin edilebilir. Aynı satır gun = 4 * GUNLER_HAFTADA; diye yazılınca anlam kendiliğinden ortaya çıkar. Değer değişirse de tek bir yerden güncellenir. Sabit adları büyük harfle yazılır, kelimeler alt çizgiyle ayrılır.

public class Sabitler {
    public static final double PI = 3.14159;
    public static final int GUNLER_HAFTADA = 7;

    public static void main(String[] args) {
        double r = 2.0;
        double alan = PI * r * r;
        System.out.println("Alan: " + alan);
        int gun = 4 * GUNLER_HAFTADA;
        System.out.println(gun + " gün");
    }
}
Alan: 12.56636
28 gün

PI ve GUNLER_HAFTADA sınıf düzeyinde, main'in dışında bildirilmiştir. static olmaları, nesne oluşturulmadan main içinde kullanılabilmelerini sağlar. Alan 3,14159 × 2 × 2 = 12,56636 bulunur. Java'nın kendisi de daha duyarlı bir değer sunar: Math.PI. Aynı yarıçapla Math.PI * r * r 12,566370614359172 verir.

Sabiti değiştirmeye çalışmak ya da double bir sonucu float'a atamak derlenmez:

PI = 3.14;
float alan = PI * r * r;
error: cannot assign a value to
  static final variable PI
error: incompatible types: possible
  lossy conversion from double to float

Birinci ileti final anahtar kelimesinin işini yaptığını gösterir. Değişmemesi gereken bir değer yanlışlıkla değiştirilmek istenince hata derleme sırasında yakalanır. İkinci ileti bir tip dönüşümü sorunudur. PI * r * r bir double ifadesidir ve float daha dar bir tiptir. Derleyici “olası kayıplı dönüşüm” (possible lossy conversion) uyarısıyla atamayı reddeder.

2.2.4.7Atama uyumluluğu

Bir değer, başka tipte bir değişkene her zaman atanamaz. Java'nın kuralı basittir: değer kayıpsız sığıyorsa atama kendiliğinden yapılır, kayıp olabilecekse programcının açıkça istemesi gerekir.

byteshortintlongfloatdoublecharörtük (otomatik) genişletme →ters yön: açık dönüşüm (cast) gerekir
Şekil 2.2.19: Atama uyumluluğu. byte, short, int, long, float, double zincirinde sağa doğru atama otomatik yapılır; char int'e atanabilir. Ters yön dönüşüm işareti ister.
Tanım 2.2.17

Genişletme dönüşümü (widening conversion), bir değerin daha geniş aralıklı bir tipe geçmesidir ve otomatik yapılır. Daraltma dönüşümü (narrowing conversion) ise daha dar aralıklı bir tipe geçiştir; değer kaybı olabileceği için açık dönüşüm ister.

Şekil 2.2.19 zinciri gösterir: byte → short → int → long → float → double. Bir değer, zincirde daha sağdaki bir tipin değişkenine doğrudan atanabilir. int bir değer long'a, long bir değer double'a sorunsuz geçer. char zincire int'ten girer: bir char değeri int'e ve onun sağındaki tiplere atanabilir, tersi derlenmez.

char karakter = 'A';
int tamsayi;
tamsayi = karakter;   // mümkün: 65
karakter = tamsayi;   // hata
Uyum.java:5: error: incompatible types:
  possible lossy conversion from int to char
        karakter = tamsayi;
                   ^

tamsayi = karakter; satırında 'A''nın kodu olan 65, int değişkene yazılır. Ters yöndeki atama ise reddedilir. tamsayi'nın o anda 65 olduğu bilinse bile derleyici değere değil, tipe bakar. int bir değişken −5 ya da 100.000 gibi char'a sığmayan bir değer de taşıyabilir.

Zincirin bir inceliği vardır. long 64 bit, float ise 32 bittir; yine de long → float otomatik izinlidir. Bunun nedeni, float'ın aralığının long'unkinden çok daha geniş olmasıdır. Ancak float yalnız 7 basamak kadar duyarlıdır. Çok büyük bir sayıda son basamaklar yuvarlanır. long buyuk = 123456789123L; değeri float'a atanıp geri okununca 123456790528 elde edilir. Aynı durum int → float için de geçerlidir: 16.777.217 float'a atanınca 16.777.216 olur. Genişletme aralık bakımından güvenlidir, ama duyarlık bakımından her zaman değil.

2.2.4.8Örtük ve açık dönüşüm

Tanım 2.2.18

Tip dönüşümü (type conversion), bir değeri başka bir tipe çevirmektir. Java'nın kendiliğinden yaptığı dönüşüme örtük dönüşüm (implicit), programcının hedef tipi parantez içinde yazarak istediği dönüşüme açık dönüşüm (explicit conversion, cast) denir: (int) d.

Küçük tipten büyük tipe dönüşümü Java kendiliğinden yapar. Büyük tipten küçük tipe dönüşümde ise hedef tip parantez içinde yazılır. Programcı bu işaretle olası kaybı kabul ettiğini derleyiciye bildirmiş olur. Kayıp iki biçimde ortaya çıkar:

  • Ondalıklı sayıdan tam sayıya geçerken kesirli kısım atılır; sayı yuvarlanmaz. (int) 3.99 3, (int) -3.99 −3 verir. Kesir sıfıra doğru kesilir.
  • Sığmayan bir tam sayı taşar. (byte) 130 değerin son 8 bitini alır ve −126 verir.
double ondalikDegisken = 5;     // örtük: 5.0
System.out.println(ondalikDegisken);
double d = 5.0;
int tamSayiDegisken = (int) d;   // açık: 5
System.out.println(tamSayiDegisken);
System.out.println((int) 3.99);
System.out.println((int) -3.99);
System.out.println(Math.round(3.99));
System.out.println((byte) 130);
5.0
5
3
-3
4
-126

double ondalikDegisken = 5; satırında int sabiti 5 örtük olarak 5.0'a çevrilir; println bu yüzden 5.0 yazar. (int) d açık dönüşümdür ve 5.0'ı 5'e çevirir. Bu değerde kayıp yoktur, ama derleyici değere değil tipe baktığı için dönüşüm işareti yine gerekir. (int) 3.99 ile (int) -3.99 kesirin atıldığını gösterir. Yuvarlama isteniyorsa Math.round kullanılır: Math.round(3.99) 4 verir. (byte) 130 taşmanın açık dönüşümle de oluştuğunu gösterir. Dönüşüm işareti hatayı önlemez; yalnız derleyicinin itirazını susturur.

Sık yapılan hata

Math.round'un dönüş tipini unutmak. Math.round(double) int değil, long döndürür. int n = Math.round(3.99); bu yüzden possible lossy conversion from long to int iletisiyle derlenmez; int n = (int) Math.round(3.99); yazılır. Bir başka ayrıntı da negatif yarımların yuvarlanmasıdır: Math.round(2.5) 3, Math.round(-2.5) ise −2 verir. Yöntem, sayıya 0,5 ekleyip aşağı yuvarlar.

2.2.4.9Tam sayı bölmesi ve dönüşümün yeri

Tanım 2.2.19

Tam sayı bölmesi (integer division), iki tam sayının bölümünün yine tam sayı olarak hesaplanmasıdır: kesirli kısım atılır. Sayısal yükseltme (numeric promotion) ise bir işlemdeki işlenenlerin ortak bir tipe çevrilmesidir: bir işlenen double ise öteki de double'a yükseltilir.

Java bir işlemin tipine işlenenlerin tipine bakarak karar verir. Sonucun atanacağı değişkenin tipi bu kararı etkilemez. Tablo 2.2.15 aynı 7 ve 2 sayılarıyla yazılmış altı ifadeyi karşılaştırır.

Tablo 2.2.15: Tam sayı bölmesi ve dönüşümün yeri.
İfadeSonuçNeden?
7 / 23iki int → tam sayı bölmesi, kesir atılır
7 / 2.03.52.0 double → işlem double yapılır
(double) 7 / 23.5önce 7 → 7.0, sonra bölme
(double) (7 / 2)3.0önce tam sayı bölmesi (3), sonra 3 → 3.0: geç kalınmış dönüşüm
(int) 3.993kesirli kısım atılır
Math.round(3.99)4en yakın tam sayıya yuvarlar (long döndürür)

Üçüncü ve dördüncü satırlar arasındaki fark, dönüşüm işaretinin önceliğinden gelir. (double) tekli bir operatördür ve bölmeden önce uygulanır. (double) 7 / 2 ifadesinde dönüşüm yalnız 7'ye uygulanır; bölme 7.0 / 2 olarak yapılır. (double) (7 / 2) ifadesinde ise parantez önce hesaplanır. Tam sayı bölmesi 3'ü verir ve dönüşüm, kesir çoktan kaybolduktan sonra gelir.

Yükseltme kuralının bir sonucu daha vardır. byte ve short değerler bir işleme girerken önce int'e yükseltilir. byte + byte işleminin sonucu bu yüzden int'tir:

Toplam.java:4: error: incompatible types: possible lossy conversion from int to byte
        byte c = a + b;
                   ^
1 error

a ve b küçük sayılardır ve toplamları 30 da byte'a sığar. Yine de derleyici, iki byte'ın toplamının en çok 254 olabileceğini ve sığmayabileceğini bilir. Çözüm byte c = (byte) (a + b); yazmak ya da sonucu int bir değişkende tutmaktır.

Dönüşümün yerini adım adım izlemek için şu kodu ele alalım:

int a = 9, b = 4;
double d1 = a / b;
double d2 = (double) a / b;
double d3 = (double) (a / b);
int k = (int) 9.75;
long r = Math.round(9.75);
byte y = (byte) 200;
System.out.println(d1 + " " + d2 + " " + d3);
System.out.println(k + " " + r + " " + y);

Program iki satır yazar: 2.0 2.25 2.0 ve 9 10 -56. Tablo 2.2.16 her değişkenin nasıl hesaplandığını verir.

Tablo 2.2.16: Tip dönüşümü izlemesi (a = 9, b = 4).
DeğişkenİfadeAra adımlarDeğer
d1a / b9 / 4 = 2 (tam sayı bölmesi) → atamada 2.02.0
d2(double) a / b9 → 9.0; b 4.0'a yükseltilir; 9.0 / 4.02.25
d3(double) (a / b)9 / 4 = 2 → 2.02.0
k(int) 9.75kesir atılır9
rMath.round(9.75)en yakın tam sayı (long)10
y(byte) 200200 = 11001000; işaret biti 1 → 200 − 256−56
a / b9 / 4int ÷ inttam bölme2intd12.0double (double) a9.0doubleb yükseltilir4.0doubled22.25double 20011001000int (son 8 bit)(byte)-56byte
Şekil 2.2.20: Dönüşümün yeri. (a) d1: önce tam sayı bölmesi 2'yi verir, sonra 2 → 2.0. (b) d2: önce 9 → 9.0, bölme double yapılır ve 2.25 bulunur. (c) y: 200'ün son 8 biti 11001000, işaretli okununca −56'dır.

d1 satırı en sık yapılan hatayı gösterir. Hedefin double olması bölmeyi kurtarmaz. Java önce sağ tarafı hesaplar; o sırada sonucun nereye atanacağına bakmaz. İki int bölünür, 2 bulunur ve ancak atamada 2.0'a çevrilir. y satırında 200 ikili tabanda 11001000'dir. Bu 8 bit byte olarak okunduğunda en soldaki 1, −128 ağırlığını taşır: −128 + 64 + 8 = −56.

Örnek 2.2.2

Santigrat dereceyi Fahrenhayta çeviren formül F = C × 9 / 5 + 32'dir. int c = 37; için üç farklı yazımın sonucu nedir?

  • c * 9.0 / 5 + 32: 9.0 bir double olduğu için işlem baştan double yürür. 37 × 9,0 = 333,0; 333,0 / 5 = 66,6; 66,6 + 32 = 98.6.
  • c * 9 / 5 + 32: her şey int'tir. 37 × 9 = 333; 333 / 5 = 66 (kesir atılır); 66 + 32 = 98. Sonuç 0,6 eksiktir.
  • 9 / 5 * c + 32: işlemler soldan sağa yapılır. 9 / 5 = 1 (tam sayı bölmesi); 1 × 37 = 37; 37 + 32 = 69. Sonuç neredeyse 30 derece yanlıştır.

Matematikte bu üç yazım aynıdır. Java'da ise işlemin sırası ve işlenenlerin tipi sonucu değiştirir. Tam sayı bölmesinin ne kadar erken yapıldığı, kaybın büyüklüğünü belirler.

2.2.4.10char ile int arasında dönüşüm

Her char aslında bir sayıdır: karakterin Unicode kodu. 'A' = 65, 'B' = 66, 'a' = 97. Bu yüzden karakterler arasında hesap yapılabilir. Kurallar öteki sayısal tiplerle aynıdır:

  • char → int örtüktür. int → char için (char) gerekir.
  • İki char + ile toplanırsa ikisi de int'e yükseltilir ve bir sayı çıkar. Metin elde etmek için ifadenin bir String ile başlaması gerekir.
  • İngilizce alfabede büyük ve küçük harfler arasında 32 fark vardır: 'a' - 'A' = 97 − 65 = 32.
  • Rakamların kodları 48'den başlar ve ardışık dizilir. Bu yüzden '7' - '0' rakamın sayı değerini, yani 7'yi verir.

Karakter kodlarının tamamı ASCII tablosunda görülebilir.

char karakter = 'A';
int kod = karakter;               // 65
System.out.println(kod);
char sonraki = (char) (kod + 1);
System.out.println(sonraki);
System.out.println('B' + 'C');
System.out.println("" + 'B' + 'C');
System.out.println((char) (karakter + 32));
System.out.println((int) 'ş');
65
B
133
BC
a
351

'B' + 'C' 66 + 67 = 133 verir; burada hiçbir metin yoktur. "" + 'B' + 'C' ifadesi ise boş bir dizgiyle başlar. Soldan sağa önce "" + 'B' hesaplanır ve "B" metni oluşur. Ardından 'C' bu metne eklenir: BC. (char) (karakter + 32) büyük A'yı küçük a'ya çevirir. Son satır Türkçe 'ş''nin kodunu verir: 351. Türkçe harfler İngilizce harflerin arasında değil, Unicode tablosunun daha ileri bir bölümündedir. Bu yüzden 32 farkı kuralı onlar için geçerli değildir: 'Ş' 350, 'ı' 305, 'İ' 304 kodludur.

Bu kuralları bir izleme üzerinde birleştirelim:

char c = 'd';
int kod = c;
char buyuk = (char) (c - 32);
int fark = 'z' - c;
char sonraki = c;
sonraki++;
System.out.println(kod + " " + buyuk + " " + fark + " " + sonraki);
System.out.println(c + 1);
System.out.println("" + c + 1);
Tablo 2.2.17: char ↔ int izlemesi.
SatırHesapSonuç
char c = 'd';'d''nin kodu saklanırc = 'd' (100)
int kod = c;örtük char → intkod = 100
char buyuk = (char) (c - 32);100 − 32 = 68 (int), (char) ile 68 = 'D'buyuk = 'D'
int fark = 'z' - c;122 − 100fark = 22
char sonraki = c; sonraki++;100 → 101; ++ dönüşüm işareti istemezsonraki = 'e'
println(kod + " " + … )" " metni olduğu için birleştirme100 D 22 e
println(c + 1)char sayıya yükseltilir: 100 + 1101
println("" + c + 1)önce "d", sonra + 1 metne eklenird1
c'd'char · 100kod100intbuyuk'D'char · 68 c'd'char · 100kod100intbuyuk'D'char · 68fark22intsonraki'e'char · 101"" + c + 1 → "d" + 1 → "d1"
Şekil 2.2.21: char ↔ int izlemesinden iki an. (a) c = 'd', kod = 100 ve buyuk = 'D' hesaplanmıştır. (b) Bütün değişkenler hazırdır; son iki satır 101 ve d1 yazar.

Tablodaki son iki satır aynı değerlerle çok farklı sonuçlar üretir. c + 1 ifadesinde metin yoktur. char sayıya yükseltilir ve 101 yazılır; harf yazılmaz. Harf isteniyorsa (char) (c + 1) yazılır ve e çıkar. "" + c + 1 ifadesinde ise soldaki boş dizgi bütün ifadeyi metin işlemine çevirir. c metne d olarak, 1 de 1 olarak eklenir. sonraki++ satırı da dikkat çekicidir. sonraki = sonraki + 1; derlenmezdi, çünkü sağ taraf int'tir. ++ ise bileşik atama gibi dönüşümü kendisi yapar.

2.2.5Operatörler

Operatörler değerler üzerinde işlem yapan simgelerdir. +, < ve && birer operatördür (operator); üzerinde işlem yaptıkları değerlere işlenen (operand) denir. Java'nın operatörleri dört gruba ayrılır: aritmetik, karşılaştırma, mantıksal ve atama operatörleri. Bir ifadede birden çok operatör bulununca hangisinin önce uygulanacağını öncelik kuralları belirler.

2.2.5.1Aritmetik operatörler

Tablo 2.2.18: Aritmetik operatörler (x = 17, y = 5).
OperatörAnlamıİfadeSonuç
+toplamax + y22
-çıkarmax - y12
*çarpmax * y85
/bölmex / y3
%kalan (mod)x % y2
-17 / 5−3
-17 % 5−2
17.0 / 53.4
17 / 5 = 3 (tam grup)17 % 5 = 2beşerli 3 grup, artan 2
Şekil 2.2.22: 17 nesne 5'erli gruplara ayrılınca 3 grup oluşur ve 2 nesne artar: 17 / 5 = 3, 17 % 5 = 2.

/ iki tam sayı arasında tam sayı bölmesi yapar. % ise bölümden kalanı verir. Şekil 2.2.22 ikisini birlikte gösterir: 17 = 5 × 3 + 2. Bölüm ve kalan her zaman bu eşitliği sağlar: (x / y) * y + x % y ifadesi x'e eşittir. Negatif sayılarda Java bölümü sıfıra doğru keser: −17 / 5 = −3,4 değil −3'tür. Eşitliğin korunması için kalan −2 olur: 5 × (−3) + (−2) = −17. Kural şudur: kalanın işareti bölünenin işaretidir. 17 % -5 bu yüzden 2 verir.

% operatörünün birkaç yaygın kullanımı vardır:

  • Tek-çift testi: n % 2 == 0 ise n çifttir. Tek sayı testi için n % 2 != 0 yazılır. n % 2 == 1 negatif tek sayılarda yanlış sonuç verir, çünkü -7 % 2 −1'dir.
  • Son basamak: n % 10 son basamağı, n / 10 son basamağı atılmış sayıyı verir. n = 1234 için 4 ve 123 bulunur.
  • Birim ayırma: saniyeyi saat, dakika ve saniyeye ayırmak gibi işler / ve % ile yapılır.
Örnek 2.2.3

3725 saniye kaç saat, kaç dakika, kaç saniyedir? int s = 3725; için:

  • s / 3600 = 1. Bir saat 3600 saniyedir ve 3725'in içinde bir kez vardır.
  • s % 3600 / 60 = 125 / 60 = 2. Önce saatten artan 125 saniye bulunur, sonra bunun içindeki tam dakikalar sayılır. % ve / eşit önceliklidir ve soldan sağa uygulanır.
  • s % 60 = 5. Tam dakikalardan artan saniyelerdir.

Sonuç: 1 saat 2 dakika 5 saniye. Sağlama: 1 × 3600 + 2 × 60 + 5 = 3725.

Java'da üs operatörü yoktur. 2 ** 3 derlenmez, 2 ^ 3 ise derlenir ama üs almaz: ^ bitlerin özel veyasıdır ve 1 verir. Üs için Math.pow(2, 3) kullanılır; sonuç double tipinde 8.0'dır. + operatörünün de ikinci bir anlamı vardır. İşlenenlerinden biri String ise birleştirme (concatenation) yapar: "Yaş: " + 20 ifadesi Yaş: 20 metnini verir.

Tam sayı bölmesinde bölen 0 ise program ArithmeticException: / by zero ile durur. Ondalıklı bölmede ise istisna oluşmaz: 5.0 / 0 Infinity, 0.0 / 0 NaN (sayı değil) verir. Bu fark da işlenenlerin tipinden gelir.

2.2.5.2Artırma ve azaltma

Tanım 2.2.20

Artırma (++) ve azaltma (--) operatörleri bir değişkeni 1 artırır ya da azaltır (increment / decrement). Operatör değişkenden sonra yazılırsa sonek (x++), önce yazılırsa önek (++x) biçimidir.

x++ ve ++x ikisi de x'i 1 artırır. Fark, ifadenin değerindedir. Sonek biçim eski değeri, önek biçim yeni değeri verir. i++; tek başına bir deyim olarak yazıldığında ifadenin değeri kullanılmaz ve iki biçim arasında fark yoktur. Fark yalnız ifade başka bir işlemin parçası olunca ortaya çıkar.

int a = 5;
int b = a++;
System.out.println(a + " " + b);
int c = ++a;
System.out.println(a + " " + c);
int d = a--;
System.out.println(a + " " + d);
Tablo 2.2.19: Artırma ve azaltma izlemesi.
SatırNe olur?aYeni değişkenÇıktı
int a = 5;—5
int b = a++;önce eski değer (5) kullanılır, sonra a artar6b = 5
println(a + " " + b)66 5
int c = ++a;önce a artar, sonra yeni değer (7) kullanılır7c = 7
println(a + " " + c)77 7
int d = a--;önce eski değer (7), sonra a azalır6d = 7
println(a + " " + d)66 7
a6intb5int a7intb5intc7int a6intb5intc7intd7int
Şekil 2.2.23: (a) b = a++ sonrası: a = 6, b = 5. (b) c = ++a sonrası: a = 7, c = 7. (c) d = a-- sonrası: a = 6, d = 7.
Sık yapılan hata

i = i++; yazmak. Bu satır i'yi artırmaz. Sağ taraf önce hesaplanır: i++ eski değeri, örneğin 5'i verir ve i'yi 6 yapar. Ardından atama gelir ve i'ye o eski değer, 5 yazılır. Artış böylece silinir. Bir değişkeni artırmak için i++; ya da i += 1; yeterlidir. Genel kural da buradan çıkar: aynı değişkeni bir ifadede hem değiştirip hem okumak, kodu okunmaz hâle getirir.

2.2.5.3İşlem önceliği

Tanım 2.2.21

İşlem önceliği (operator precedence), bir ifadede birden çok operatör bulunduğunda hangisinin önce uygulanacağını belirleyen sıradır. Eşit öncelikli operatörlerde birleşme yönü (associativity) karar verir: soldan sağa ya da sağdan sola.

Tablo 2.2.20: Operatörlerin öncelik sırası. → soldan sağa, ← sağdan sola gruplanır.
ÖncelikOperatörlerYön
1 (en yüksek)sonek: x++ x--→
2tekli: +x -x ! ++x --x · tip dönüşümü (int)←
3çarpma, bölme, mod: * / %→
4toplama, çıkarma: + -→
5karşılaştırma: < > <= >=→
6eşitlik: == !=→
7mantıksal VE: &&→
8mantıksal VEYA: ||→
9üçlü: ? :←
10 (en düşük)atama: = += -= *= …←

Tablo matematikteki işlem sırasını genişletir. Çarpma ve bölme toplamadan önce gelir; karşılaştırmalar aritmetikten sonra, mantıksal bağlaçlar karşılaştırmalardan sonra, atama en sonda yapılır. Bu sıra günlük yazımla uyumludur: x + 1 < y * 2 && z == 0 ifadesi parantezsiz de beklendiği gibi ((x + 1) < (y * 2)) && (z == 0) diye okunur.

Eşit öncelikli ikili operatörlerde soldaki önce uygulanır: 10 - 4 - 3 = (10 − 4) − 3 = 3'tür, 10 − (4 − 3) = 9 değil. Tekli operatörlerde ve atamada ise sağdaki önce uygulanır: a = b = 0 önce b'yi, sonra a'yı sıfırlar. Sırayı değiştirmek ya da okunurluğu artırmak için parantez kullanılır. Gereksiz görünen bir parantez de çoğu zaman yararlıdır; okuyanın öncelik tablosunu hatırlamasına gerek bırakmaz.

Önceliğin nasıl uygulandığını bir ifade üzerinde adım adım görelim: System.out.println(2 + 3 * 4 - 6 / 2);

  1. 2 + 3 * 4 - 6 / 2: * ve /, + ve -'den önce gelir. İkisinden soldaki önce: 3 * 4 = 12.
  2. 2 + 12 - 6 / 2: sırada kalan yüksek öncelikli işlem 6 / 2 = 3.
  3. 2 + 12 - 3: kalan + ve - eşit önceliklidir ve soldan sağa yapılır: 2 + 12 = 14.
  4. 14 - 3 = 11.

Sonuç 11'dir. Parantez sırayı değiştirir: (2 + 3) * 4 - 6 / 2 = 5 × 4 − 3 = 20 − 3 = 17. Birkaç kısa örnek daha: 20 / 4 * 2 soldan hesaplanır ve 5 × 2 = 10 verir, 20 / 8 değil. -2 * -3 ifadesinde tekli eksiler önce uygulanır ve sonuç 6'dır.

Dizgi birleştirme de aynı soldan sağa kuralına uyar ve bu yüzden şaşırtıcı sonuçlar verebilir. 1 + 2 + "3" + 4 + 5 ifadesinde önce 1 + 2 = 3 hesaplanır; henüz metin yoktur. Sonra 3 + "3" metin olur: "33". Bundan sonraki her + birleştirmedir: "334", "3345". Sonuç 3345'tir.

2.2.5.4Karşılaştırma operatörleri

Tanım 2.2.22

Karşılaştırma operatörleri (comparison, ilişkisel operatörler) iki değer arasındaki ilişkiyi değerlendirir. Sonuç her zaman bir boolean'dır: true ya da false.

Tablo 2.2.21: Karşılaştırma operatörleri (x = 10, y = 20).
OperatörAnlamıÖrnekSonuç
==eşit mi?x == yfalse
!=eşit değil mi?x != ytrue
>soldaki büyük mü?y > xtrue
<soldaki küçük mü?x < ytrue
>=büyük veya eşit mi?x >= 30false
<=küçük veya eşit mi?x <= ytrue

Karşılaştırmanın sonucu bir değişkende saklanabilir: boolean buyukMu = y > x;. Bu satırdan sonra buyukMu true olur. İki karakterli operatörlerin yazımı sabittir: >= doğrudur, => yazılamaz. Operatörün içine boşluk da konamaz: > = derlenmez.

Matematikteki 0 < x < 10 yazımı Java'da çalışmaz. 0 < x < 10 ifadesinde önce 0 < x hesaplanır ve bir boolean çıkar. Ardından bu boolean 10 ile karşılaştırılmaya çalışılır ve derleyici hata verir. Doğru yazım iki karşılaştırmayı mantıksal VE ile bağlar: 0 < x && x < 10.

Sık yapılan hata

= ile =='yi karıştırmak. = atamadır, == karşılaştırmadır. if (x = 5) yazılınca koşulun yerine bir atama girer. Atamanın değeri 5'tir, yani bir int. Java koşul olarak yalnız boolean kabul ettiği için kod derlenmez:

Esit.java:4: error: incompatible types: int cannot be converted to boolean
        if (x = 5) { }
              ^
1 error

C dilinde aynı satır derlenir ve koşul her zaman doğru sayılır; hata gizli kalır. Java'nın katı tip denetimi bu hatayı derleme sırasında yakalar. Bir istisna vardır: boolean bitti; … if (bitti = true) derlenir, çünkü atamanın değeri bir boolean'dır. boolean değişkenlerde en güvenli yazım karşılaştırmayı hiç kullanmamaktır: if (bitti) ve if (!bitti).

2.2.5.5Mantıksal operatörler

Tanım 2.2.23

Mantıksal operatörler (logical operators) boolean değerleri birleştirir. VE (AND) &&: iki koşul da true ise true. VEYA (OR) ||: en az biri true ise true. DEĞİL (NOT) !: değeri tersine çevirir.

Tablo 2.2.22: Mantıksal operatörlerin doğruluk tablosu.
ABA && BA || B!A
truetruetruetruefalse
truefalsefalsetruefalse
falsetruefalsetruetrue
falsefalsefalsefalsetrue

Tablo kısaca şöyle okunur: A && B yalnız ikisi de doğruyken doğrudur; A || B yalnız ikisi de yanlışken yanlıştır. Günlük dildeki “veya” bazen “ikisinden yalnız biri” anlamına gelir (“çay veya kahve”). Java'daki || ise ikisi de doğru olduğunda da doğru verir.

int x = 10, y = 20, z = 30;
System.out.println(x < y && y < z);
System.out.println(x > y || y < z);
System.out.println(!(x < y));
int yas = 20;
boolean ehliyet = true;
System.out.println(yas >= 18 && ehliyet);
true
true
false
true

x < y && y < z ifadesinde iki karşılaştırma da doğrudur (10 < 20 ve 20 < 30), sonuç true'dur. x > y || y < z ifadesinde ilk karşılaştırma yanlış, ikincisi doğrudur; VEYA için biri yeterlidir. !(x < y) doğru olan x < y'yi tersine çevirir ve false verir. Son satır bir kuralı koda döker: 18 yaşında ya da daha büyük olan ve ehliyeti bulunan kişi araç kullanabilir. İki koşul da sağlandığı için sonuç true'dur.

! operatörünün önceliği yüksektir. !x < y yazılırsa ! yalnız x'e uygulanmaya çalışılır ve int bir değer tersine çevrilemeyeceği için kod derlenmez. Bu yüzden karşılaştırmanın tersi parantezle yazılır: !(x < y). Aynı ifade daha sade biçimde x >= y diye de yazılabilir.

2.2.5.6Kısa devre değerlendirme

Tanım 2.2.24

Kısa devre değerlendirme (short-circuit evaluation), && ve || operatörlerinde sonuç sol taraftan belli olunca sağ tarafın hiç hesaplanmamasıdır.

&&'de sol taraf false ise sonuç zaten false'tur; sağ taraf ne olursa olsun bunu değiştiremez. Java bu durumda sağ tarafı hiç hesaplamaz. ||'de sol taraf true ise sonuç zaten true'dur ve sağ taraf yine atlanır. gun == 6 || gun == 7 ifadesinde gun 6 ise ikinci karşılaştırma hiç yapılmaz. Bu ifade “hafta sonu” anlamına gelir. Tersi !(gun == 6 || gun == 7) ise “hafta içi” demektir.

Kısa devre yalnız hız kazandırmaz; tehlikeli bir işlemi korumak için de kullanılır. Önce “bölen sıfır değil mi?” diye bakılır, ancak öyleyse bölme yapılır:

int bolen = 0;
int sayi = 10;
if (bolen != 0 && sayi / bolen > 1) {
    System.out.println("Oran 1'den büyük");
} else {
    System.out.println("Bölünemez ya da küçük");
}
Bölünemez ya da küçük

bolen 0'dır. bolen != 0 yanlış çıkar ve && sağ tarafa hiç geçmez. Böylece sayi / bolen bölmesi yapılmaz ve program else bloğuna geçer. Sıra önemlidir. Koşul sayi / bolen > 1 && bolen != 0 diye yazılsaydı bölme önce yapılırdı. Program sıfıra bölme hatasıyla, java.lang.ArithmeticException: / by zero iletisiyle dururdu. Koruyucu koşul her zaman sola yazılır.

Java'da kısa devre yapmayan iki operatör daha vardır: tek & ve tek |. Bunlar boolean değerlerle kullanıldığında iki tarafı da her zaman hesaplar. a != 0 & 10 / a > 1 ifadesi a = 0 iken sol taraf yanlış olsa bile bölmeyi yapar ve istisna fırlatır. Koşullarda neredeyse her zaman çift karakterli && ve || kullanılır.

2.2.5.7Üçlü operatör

Tanım 2.2.25

Üçlü operatör (ternary operator, koşul operatörü) bir koşula göre iki değerden birini seçen kısa bir ifadedir: koşul doğruysa değer1, değilse değer2 sonuç olur. Üç işleni olan tek Java operatörüdür.

sonuç = (koşul) ? değer1 : değer2;

Bir sayının pozitif ya da negatif olduğunu belirlemek için:

int sayi = -5;
String sonuc = (sayi > 0) ? "Pozitif" : "Negatif";
System.out.println("Sayı " + sonuc);
Sayı Negatif

sayi −5 olduğu için sayi > 0 yanlıştır ve iki nokta üst üsteden sonraki değer, "Negatif" seçilir. Aynı işi yapan if-else şöyledir:

String sonuc;
if (sayi > 0) {
    sonuc = "Pozitif";
} else {
    sonuc = "Negatif";
}

İki yazım arasındaki temel fark şudur: if-else bir deyimdir ve bir iş yapar, ?: ise bir ifadedir ve bir değer üretir. Bu yüzden ?: atamanın sağında ya da bir println'ın içinde kullanılır. Tek başına bir deyim olarak yazılamaz:

Tern.java:4: error: not a statement
        x > 0 ? System.out.println("a") : System.out.println("b");
              ^
1 error

İki seçeneğin tipi uyumlu olmalıdır. Birkaç örnek: double mutlak = (sayi < 0) ? -sayi : sayi; mutlak değeri verir ve −4.5 için 4.5 bulunur. int buyuk = (a > b) ? a : b; iki sayının büyüğünü seçer; 8 ve 13 için 13 bulunur. Seçenekler farklı sayısal tiplerdeyse sonuç geniş olana yükseltilir: true ? 1 : 2.0 ifadesinin değeri 1 değil, 1.0'dır.

Üçlü operatörler iç içe kullanılabilir. Bir sayının sıfır mı, pozitif mi, negatif mi olduğu tek satırda bulunur:

int sayi = -5;
String sonuc = (sayi == 0) ? "Sıfır" : (sayi > 0) ? "Pozitif" : "Negatif";
System.out.println("Sayı " + sonuc);
Tablo 2.2.23: İç içe üçlü operatörün sonuçları.
sayisayi == 0sayi > 0Sonuç
0truehesaplanmazSıfır
7falsetruePozitif
−5falsefalseNegatif

İfade şöyle okunur: “sıfır mı? evet → Sıfır; hayır → pozitif mi? evet → Pozitif; hayır → Negatif.” İkinci ?:, birincinin değer2'sidir; üçlü operatör sağdan gruplanır. İkiden fazla iç içe ?: okunurluğu bozar. Bu durumda if-else if zinciri tercih edilir.

2.2.6Kontrol deyimlerinin söz dizimi

Kontrol deyimleri programın akışını yönetir. Bir koşula göre dallanmayı ya da bir işin tekrarlanmasını sağlarlar. Ayrıntılı kullanımları kendi başına bir konudur. Burada her birinin yazım kuralı ve en sık rastlanan söz dizimi tuzakları verilir.

Tablo 2.2.24: Kontrol deyimlerinin söz dizimi.
DeyimSöz dizimiKural
if / elseif (koşul) { … } else { … }koşul boolean olmalı; if (…); sonundaki ; boş deyimdir
switchswitch (d) { case 1: … break; default: … }int, char, String, enum; break yoksa alttaki durumlara düşer
switch (Java 14 ve sonrası)x = switch (d) { case 1 -> "P"; default -> "?"; };düşme yok; değer üreten ifade
forfor (int i = 0; i < n; i++) { … }başlangıç bir kez; koşul turdan önce, güncelleme sonra
whilewhile (koşul) { … }gövde hiç çalışmayabilir
do-whiledo { … } while (koşul);gövde en az bir kez çalışır; sondaki ; zorunlu
break / continuebreak; · continue;break çıkar, continue turun kalanını atlar

if deyiminde koşul her zaman parantez içinde yazılır ve boolean olmalıdır. Gövde tek bir deyimse süslü parantez yazılmayabilir, ama bu alışkanlık hataya açıktır. Parantezsiz bir if'e yalnız ilk deyim bağlıdır; altına girintili yazılan ikinci satır koşuldan bağımsız çalışır. Derleyici girintiye bakmaz. Bu yüzden gövde tek satır olsa bile süslü parantez kullanmak güvenlidir.

switch deyiminde denetlenen değer int (ve byte, short), char, String ya da enum olabilir. long, double ve boolean kullanılamaz; derleyici selector type double is not allowed gibi bir ileti verir. Klasik biçimde her case'in sonuna break yazılır. break unutulursa eşleşen durumdan sonraki durumlar da sırayla çalışır. Bu davranışa aşağı düşme (fall-through) denir. Aşağı düşme bazen bilinçli kullanılır: case "Cmt": case "Paz": yazımı iki durumu tek bir koda bağlar. Java 14 ile gelen ok (->) biçiminde ise aşağı düşme yoktur ve switch bir değer üretebilir. İki biçim aynı switch içinde karıştırılamaz (different case kinds used in the switch).

for döngüsünün başlığı noktalı virgülle ayrılmış üç bölümden oluşur. Başlangıç bölümü yalnız bir kez, döngüye girerken çalışır. Koşul her turdan önce denetlenir; yanlışsa döngü biter. Güncelleme her turun sonunda çalışır. Her for döngüsü bir while döngüsüyle de yazılabilir: başlangıç döngüden önceye, güncelleme gövdenin sonuna taşınır.

while ile do-while arasındaki fark koşulun yeridir. while koşulu önce denetler; koşul baştan yanlışsa gövde hiç çalışmaz. do-while ise gövdeyi önce çalıştırır, koşulu sonra denetler; gövde en az bir kez çalışır. do-while'ın sonundaki noktalı virgül zorunludur ve unutulursa ';' expected hatası alınır.

break içinde bulunduğu en yakın döngüden ya da switch'ten hemen çıkar. continue ise yalnız o turun kalanını atlar ve bir sonraki tura geçer. for döngüsünde continue'dan sonra güncelleme bölümü yine çalışır. switch bir döngünün içindeyse, switch içindeki break yalnız switch'ten çıkar; döngü sürer.

Örnek 2.2.4

1'den 100'e kadar olan sayılardan 3'ün katı olmayanların toplamı continue ile şöyle bulunur:

int toplam = 0;
for (int i = 1; i <= 100; i++) {
    if (i % 3 == 0) continue;   // 3'ün katlarını atla
    toplam += i;
}

3'ün katı olan bir i için continue çalışır, toplam += i atlanır ve döngü i++ ile sürer. Sağlama: 1'den 100'e kadar sayıların toplamı 5050'dir. 3'ün katları 3, 6, …, 99'dur; 33 tanedir ve toplamları 3 × (1 + 2 + … + 33) = 3 × 561 = 1683'tür. Sonuç 5050 − 1683 = 3367'dir.

Sık yapılan hata

Sonsuz döngü. Koşulu yanlışa götüren bir adımı olmayan döngü hiç bitmez. for (int i = 1; i <= 10; i--) döngüsünde i azaldıkça koşul hep doğru kalır. int i = 1; while (i <= 3) { System.out.println(i); } döngüsünde ise i gövdede hiç güncellenmez ve ekrana durmadan 1 yazılır. Her döngü yazılırken “koşul hangi adımda yanlış olacak?” sorusu sorulmalıdır.

2.2.7Sık yapılan hatalar

Söz dizimi hataları iki gruba ayrılır. Birinci gruptakileri derleyici yakalar; program hiç çalışmaz ve ileti hatanın yerini gösterir. İkinci gruptakiler derlenir ama yanlış çalışır. Bunlar daha tehlikelidir, çünkü ancak çıktı dikkatle incelenince fark edilir.

Tablo 2.2.25: Derleyicinin yakaladığı hatalar ve iletileri.
HataÖrnekİletiDoğrusu
Eksik noktalı virgülSystem.out.println("a")';' expectedsatır sonuna ;; hata çoğu zaman bir önceki satırın sonundadır
Dosya adı ≠ sınıf adımerhabadunya.java içinde public class MerhabaDunyaclass MerhabaDunya is public, should be declared in a file named MerhabaDunya.javadosyayı sınıf adıyla kaydetmek
Büyük-küçük harfstring, Int, system.outcannot find symbol; package system does not existString, int, System
Değersiz yerel değişkenint sayac; println(sayac);variable sayac might not have been initializedokumadan önce değer vermek
Soneksiz sabitfloat f = 3.14; · long l = 3000000000;possible lossy conversion from double to float · integer number too large3.14f · 3000000000L
Daraltmada dönüşümü unutmakchar k = i; · int n = d;incompatible types: possible lossy conversion(char) i · (int) d
= ile ==if (x = 5)int cannot be converted to booleanif (x == 5)
Tablo 2.2.26: Derlenen ama yanlış çalışan kodlar.
HataÖrnekSonuçDoğrusu
Tam sayı bölmesidouble ort = (3 + 4) / 2;3.0(3 + 4) / 2.0 → 3.5
Geç kalınmış dönüşüm(double) (a / b)kesir önceden kaybolmuş(double) a / b
Taşmalong l = Integer.MAX_VALUE + 1;−2147483648Integer.MAX_VALUE + 1L
switch'te break yokcase 1: … case 2: …sonraki durumlar da çalışırher case sonuna break ya da -> biçimi
if'ten sonra ;if (x > 0); { … }boş deyim koşula bağlanır, blok her zaman çalışır;'ü silmek
Sonsuz döngüfor (int i = 1; i <= 10; i--)döngü bitmezkoşulu yanlışa götüren güncelleme
Ondalıkta ==0.1 + 0.2 == 0.3falseMath.abs(a - b) < 1e-9
Birleştirme sırası"Toplam: " + a + bsayılar yan yana yazılır"Toplam: " + (a + b)

if (x > 0); { … } hatası özellikle sinsidir. if'in hemen arkasındaki noktalı virgül, hiçbir şey yapmayan bir boş deyimdir. Koşula bağlanan budur. Alttaki süslü parantezli blok ise koşuldan bağımsız, sıradan bir bloktur ve her zaman çalışır. x = −3 iken bile blok çalışır. Derleyici bunu hata saymaz, çünkü iki satır da ayrı ayrı geçerlidir.

2.2.8Özet

  • Derleme: javac Ad.java kaynak kodu denetler ve Ad.class dosyasına bytecode olarak yazar. java Ad JVM'i başlatır, sınıfı yükler ve main'i çağırır. Aynı bytecode JVM bulunan her platformda çalışır.
  • JVM bir yığın makinesidir: komutlar değerleri işlenen yığınına iter ve oradan alır. Yorumlayıcı bytecode'u çalıştırır, JIT derleyicisi sık çalışan kodu makine koduna çevirir.
  • Söz dizimi: deyimler ; ile biter, bloklar { } ile açılıp kapanır, Java büyük-küçük harfe duyarlıdır. Açıklamalar // ve /* */ ile yazılır.
  • İlkel tipler: byte 1, short 2, int 4, long 8 bayt; float 4, double 8 bayt; char 2 bayt; boolean. Sınırı aşan tam sayı hata vermeden taşar ve aralığın öbür ucuna sarar.
  • Ondalıklı sayılar ikili tabanda yaklaşık saklanır: 0.1 + 0.2 0,3 değildir. Ondalıklı sayılar == ile karşılaştırılmaz.
  • Adlar harf, rakam, _ ve $ içerir; rakamla başlamaz; anahtar kelime olmaz. Sınıflar PascalCase, değişkenler camelCase, sabitler BÜYÜK_HARF yazılır.
  • Atama: sağ taraf önce hesaplanır. += gibi bileşik atamalar dönüşümü kendileri yapar. Yerel değişkene okunmadan önce değer verilmelidir. final değişken bir kez değer alır.
  • Dönüşüm: byte → short → int → long → float → double yönü otomatiktir. Ters yön (tip) ile yapılır; kesir atılır, sığmayan değer taşar. int / int tam sayı bölmesidir ve dönüşüm bölmeden önce yapılmalıdır.
  • Operatörler: öncelik sırası tekli > * / % > + - > karşılaştırma > && > || > ?: > atama. && ve || kısa devre yapar. ?: bir değer üreten ifadedir.
  • Akış: if/else, switch (+ break), for, while (0 ya da daha çok tur), do-while (en az 1 tur). break çıkar, continue turun kalanını atlar.

2.2.9Kavram listesi

2.2.10Kendini Dene

  1. Deneme.java adlı bir dosyada public class Test tanımlı. Dosya javac Deneme.java ile derlenmek isteniyor. Ne olur? Program nasıl çalıştırılır hâle getirilir?

    Cevap

    Derleme başarısız olur: class Test is public, should be declared in a file named Test.java. Public bir sınıfın adı dosya adıyla birebir aynı olmalıdır. Dosya Test.java adıyla kaydedilir, javac Test.java ile derlenir ve java Test ile (uzantısız) çalıştırılır.

  2. int a = 5, b = 7; int c = a * b; satırları derlendiğinde a * b hesaplanırken JVM'in işlenen yığınında hangi değerler bulunur? imul komutundan hemen sonra yığında ne kalır?

    Cevap

    Önce iload_1 5'i, sonra iload_2 7'yi yığına iter; yığında alttan üste 5, 7 durur. imul bu iki değeri çıkarır, çarpar ve sonucu iter: yığında yalnız 35 kalır. Ardından istore_3 35'i c'ye yazar ve yığın boşalır. Toplamadaki iadd ile aynı düzen geçerlidir.

  3. Aşağıdaki satır neden yanlış sonuç verir? Doğru değer nasıl elde edilir?
    long mikrosaniye = 24 * 60 * 60 * 1000 * 1000;

    Cevap

    Bütün sabitler int'tir ve çarpma int tipinde yapılır. Gerçek sonuç 86.400.000.000'dir ve int sınırını aşar; taşan değer 500654080 olur. long değişkene ancak bu taşmış değer atanır. İlk sabite L eklenince çarpma baştan long yürür: 24L * 60 * 60 * 1000 * 1000 = 86400000000.

  4. byte b = 120; b += 10; satırlarından sonra b kaçtır? Aynı iş b = b + 10; diye yazılsa ne olur?

    Cevap

    b = −126 olur. 130, byte sınırı 127'yi aşar; bileşik atama sonucu kendisi byte'a çevirir ve 130 − 256 = −126 kalır. b = b + 10; ise derlenmez: b + 10 bir int ifadesidir ve byte'a ancak (byte) ile atanabilir (possible lossy conversion from int to byte).

  5. Şu adlardan hangileri geçerli tanımlayıcıdır? _2x, x-2, int, Integer, toplam$, 9lu

    Cevap

    Geçerli: _2x (alt çizgiyle başlar), Integer (bir sınıf adıdır, anahtar kelime değildir; ama ad olarak kullanılması okuyanı şaşırtır), toplam$ ($ izinlidir). Geçersiz: x-2 (- çıkarma operatörüdür), int (anahtar kelime), 9lu (rakamla başlar).

  6. char c = '5'; iken System.out.println(c + 1); ve System.out.println(c - '0' + 1); ne yazdırır?

    Cevap

    Birinci satır 54 yazar: '5''in kodu 53'tür ve c + 1 bir int toplamıdır. İkinci satır 6 yazar: c - '0' = 53 − 48 = 5, yani rakamın sayı değeridir; buna 1 eklenir.

  7. int x = 7; int y = x-- - --x; satırlarından sonra x ve y kaçtır?

    Cevap

    İşlenenler soldan sağa hesaplanır. x-- eski değeri, 7'yi verir ve x 6 olur. --x önce x'i 5 yapar ve 5'i verir. y = 7 − 5 = 2, x = 5 olur.

  8. int a = 0; iken if (a != 0 & 10 / a > 1) ile if (a != 0 && 10 / a > 1) koşulları arasında ne fark vardır?

    Cevap

    Tek & kısa devre yapmaz ve iki tarafı da hesaplar. Sol taraf yanlış olsa da 10 / a bölmesi yapılır ve program ArithmeticException: / by zero ile durur. && ise sol taraf yanlış olunca sağ tarafa hiç bakmaz; koşul yanlış sayılır ve program güvenle sürer.

  9. int k = 3; ile başlayan iki döngünün sonunda k kaçtır?
    do { k -= 4; } while (k > 5);
    while (k > 5) { k -= 4; } (yine k = 3 ile başlayarak)

    Cevap

    do-while gövdeyi bir kez çalıştırır: k = −1 olur, -1 > 5 yanlıştır ve döngü biter; sonuç −1. while koşulu önce denetler: 3 > 5 yanlış olduğu için gövde hiç çalışmaz; sonuç 3.

2.2.11İleri okuma

  • Oracle, The Java Tutorials, “Language Basics” kesimi: değişkenleri, operatörleri, ifadeleri ve kontrol akışı deyimlerini kısa örneklerle anlatan resmî öğretici.
  • J. Gosling vd., The Java Language Specification, Java SE 21: anahtar kelimelerin, sabit değerlerin (Bölüm 3), tiplerin (Bölüm 4) ve dönüşüm kurallarının (Bölüm 5) kesin tanımı. Bir derleyici iletisinin nedeni merak edildiğinde başvurulacak kaynak.
  • T. Lindholm vd., The Java Virtual Machine Specification, Java SE 21: bytecode komutlarını, işlenen yığınını ve .class dosyasının yapısını ayrıntılı tanımlar; javap -c çıktısını okumayı kolaylaştırır.
  • C. S. Horstmann, Core Java, Volume I: Fundamentals, 13. baskı, Bölüm 3: ilkel tipleri, dönüşümleri ve operatörleri deneyimli bir programcının gözünden, sık yapılan hatalara dikkat çekerek işler.

2.2.12Kaynaklar

  1. Oracle, The Java Tutorials: “Language Basics” (değişkenler, operatörler, ifadeler, kontrol akışı), docs.oracle.com/javase/tutorial.
  2. J. Gosling vd., The Java Language Specification, Java SE 21 Edition (Bölüm 3: söz dizimi, anahtar kelimeler, sabit değerler; Bölüm 4: tipler; Bölüm 5: dönüşümler).
  3. T. Lindholm vd., The Java Virtual Machine Specification, Java SE 21 Edition (bytecode ve JVM).
  4. P. Deitel, H. Deitel, Java: How to Program, 11. baskı, Pearson, 2017 (Bölüm 2–5).
  5. K. Sierra, B. Bates, Head First Java, 3. baskı, O'Reilly, 2022.
  6. C. S. Horstmann, Core Java, Volume I: Fundamentals, 13. baskı, 2024 (Bölüm 3).
  7. A. B. Downey, C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019.

İlgili sayfalar: Bölüm 2.2 sunumu · Çalışma soruları · ASCII Tablosu · İlkel Tipler Laboratuvarı · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.