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
.javadosyasınınjavacile bytecode'a,javaile ç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,breakvecontinuedeyimlerinin 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ı
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ı
.javadosyası 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 errorWindows'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.
| Araç | Ne işe yarar? | Örnekler |
|---|---|---|
| Metin düzenleyici | Kaynak kodu düz metin olarak yazmak | Notepad++, 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österilir | Eclipse, IntelliJ IDEA, NetBeans, Java eklentileriyle VS Code |
| JShell (Java 9 ve sonrası) | Tek satırlık Java ifadelerini sınıf yazmadan denemek | jshell> 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 MerhabaDunyabir 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.javaolmalı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.Mainya damain(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ı
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):
sayiileSayiiki ayrı addır;Systemyerinesystemyazı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.
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 errorssystem.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ı
MOVveADDgibi 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.
Ş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
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:
- Programcı kaynak kodu düz metin olarak yazar ve
MerhabaDunya.javaadıyla kaydeder. javac MerhabaDunya.javakomutu derleyiciyi çalıştırır. Derleyici söz dizimini ve tipleri denetler. Hata yoksa aynı klasördeMerhabaDunya.classdosyası oluşur.java MerhabaDunyakomutu 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.- İşletim sistemi ve işlemci asıl komutları yürütür; ekranda Merhaba, Dünya! görünür.
.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.
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.
javac kaynak dosyayı .class dosyasına çevirir; java JVM'i başlatır ve program çıktı verir.Ç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
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: returnHer 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:
iconst_5: sabit 5 işlenen yığınına itilir. Kaynaktaki karşılığıint x = 5;satırının sağ tarafıdır.istore_1: yığının üstündeki değer alınır ve 1 numaralı yerel değişkene, yanix'e yazılır. Yığın yeniden boşalır.getstatic #7:System.outnesnesine bir başvuru yığına itilir.#7, sınıf dosyasındaki sabitler tablosunun 7 numaralı kaydıdır.iload_1:x'in değeri (5) yığına kopyalanır. Kaynaktaki karşılığıSystem.out.println(x);satırıdır.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.return:mainbiter, program sonlanır.
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)
returnJVM 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.
| Komut | İşlenen yığını | 1: a | 2: b | 3: toplam | Açıklama |
|---|---|---|---|---|---|
iconst_5 | 5 | sabit 5 itilir | |||
istore_1 | boş | 5 | a = 5 | ||
bipush 7 | 7 | 5 | bir bayta sığan sabit 7 itilir | ||
istore_2 | boş | 5 | 7 | b = 7 | |
iload_1 | 5 | 5 | 7 | a kopyalanır | |
iload_2 | 5, 7 | 5 | 7 | b kopyalanır | |
iadd | 12 | 5 | 7 | iki değer çıkar, toplam girer | |
istore_3 | boş | 5 | 7 | 12 | toplam = 12 |
getstatic | out | 5 | 7 | 12 | System.out başvurusu |
iload_3 | out, 12 | 5 | 7 | 12 | println'ın argümanı |
invokevirtual | boş | 5 | 7 | 12 | ekrana 12 yazılır |
return | boş | 5 | 7 | 12 | main biter |
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
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
.classdosyaları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.
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
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.
.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,MathveScannerbu kütüphanelerdeki sınıflardır.
Ş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-elsebuna örnektir. - Döngüler (loops), bir işlemi tekrarlar.
forvewhilebuna ö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
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.
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.
| Satır | Kod | yas | boy | harf | Açıklama |
|---|---|---|---|---|---|
| 1 | int yas; | ? | kutu ayrılır, içi boştur | ||
| 2 | yas = 25; | 25 | atama: kutuya 25 konur | ||
| 3 | double boy = 1.78; | 25 | 1.78 | bildirim ve ilk değer tek satırda | |
| 4 | yas = yas + 1; | 26 | 1.78 | önce 25 + 1 = 26 hesaplanır | |
| 5 | char harf = 'K'; | 26 | 1.78 | 'K' | karakter tek tırnakla yazılır |
| 6 | println(…) | 26 | 1.78 | 'K' | çıktı: 26 1.78 K |
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
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.
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
| Tip | Bellek | En küçük değer | En büyük değer | Örnek |
|---|---|---|---|---|
byte | 1 bayt (8 bit) | −128 | 127 | byte b = 100; |
short | 2 bayt (16 bit) | −32.768 | 32.767 | short s = 32000; |
int | 4 bayt (32 bit) | −2.147.483.648 | 2.147.483.647 | int n = 1_000_000; |
long | 8 bayt (64 bit) | −9.223.372.036.854.775.808 | 9.223.372.036.854.775.807 | long l = 3000000000L; |
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.
| Tip | Bellek | En küçük pozitif | En büyük (±) | Anlamlı basamak |
|---|---|---|---|---|
float | 4 bayt (32 bit) | ≈ 1,4 × 10−45 | ≈ 3,4028235 × 1038 | ≈ 7 |
double | 8 bayt (64 bit) | ≈ 4,9 × 10−324 | ≈ 1,7976931348623157 × 10308 | ≈ 15–16 |
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
falseprintln 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
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
2147483648Satırlar şöyle çalışır:
enBuyukint'in en büyük değerini, 2.147.483.647'yi tutar ve ilk satırda bu değer yazılır.enBuyuk + 1ikiintarasındaki bir toplamadır ve sonucu daint'tir. Sonuç sığmaz ve en küçük değere sarar: -2147483648.sayi1 + sayi280 + 50 = 130 verir. 130,bytesı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 + 1ifadesinde dönüşüm toplamadan önce yapılır. İşlemlongtipinde yürür; 64 bitlik aralık 2.147.483.648'i rahatça tutar.
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.
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.
i | i <= 4 | b++ sonrası | İkili | Çıktı |
|---|---|---|---|---|
| — | — | 125 (başlangıç) | 01111101 | |
| 1 | doğru | 126 | 01111110 | 126 |
| 2 | doğru | 127 | 01111111 | 127 |
| 3 | doğru | −128 | 10000000 | -128 |
| 4 | doğru | −127 | 10000001 | -127 |
| 5 | yanlış | — | — | döngü biter |
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
İ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.
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ı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,nullsabitleri tanımlayıcı olamaz. - Uzunluk sınırı yoktur.
- Büyük ve küçük harf ayrıdır:
kisiAdiilekisiadifarklı değişkenlerdir. Bildirimdeki yazımdan farklı yazılan bir adcannot find symbolhatası 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
| Ad | Durum | Neden? |
|---|---|---|
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 errorsDerleyici 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
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.
| Grup | Anahtar kelimeler | Sayı |
|---|---|---|
| Tipler | boolean byte char short int long float double void | 9 |
| Kontrol akışı | if else switch case default for while do break continue return | 11 |
| Sınıf ve nesne | class interface enum extends implements new this super instanceof package import | 11 |
| Düzenleyiciler | public protected private static final abstract native synchronized volatile transient strictfp | 11 |
| İstisnalar | try catch finally throw throws assert | 6 |
| Ayrılmış, kullanılmıyor | const 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.
| Kelime | Görevi |
|---|---|
if / else | Koşulu denetler; doğruysa if bloğu, değilse else bloğu çalışır. |
switch | Bir değişkenin farklı değerlerine göre farklı işlemler yapar. |
case | switch içinde bir durumu (değeri) temsil eder. |
default | switch içinde hiçbir durum eşleşmediğinde yapılacak işlem. |
for | Bir işlemi bir sayaçla, koşul sağlandıkça tekrarlar. |
while | Koşul sağlandığı sürece işlemi tekrarlar. |
do | do-while döngüsü: gövde en az bir kez çalışır, koşul sonda denetlenir. |
break | Döngüden ya da switch bloğundan hemen çıkar. |
continue | Döngünün o turunun kalanını atlar, bir sonraki tura geçer. |
return | Metodu bitirir; gerekiyorsa bir sonuç döndürür. |
| Kelime | Görevi |
|---|---|
class | Sınıf tanımlar. Sınıflar nesnelerin şablonudur ve Java programlarının temel yapı taşıdır. |
interface | Arayüz tanımlar: bir sınıfın sağlaması gereken metotları belirleyen sözleşme. |
enum | Sabit değerlerden oluşan bir tip: enum Renk { KIRMIZI, YESIL }. |
extends | Kalıtım: bir sınıfın başka bir sınıfın alan ve metotlarını alması. |
implements | Bir sınıfın bir arayüzü uygulaması, yani arayüzün metotlarını sağlaması. |
new | Nesne oluşturur: bellekte yer ayırır ve ilk değerleri verir. |
this | Nesnenin 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. |
instanceof | Bir nesnenin belirli bir sınıf ya da arayüz tipinden olup olmadığını denetler. |
package / import | package sınıfın paketini (ad alanını) belirler; import başka paketteki sınıfları kullanıma açar. |
| Kelime | Görevi |
|---|---|
public | Her yerden, bütün sınıflardan erişilebilir. |
protected | Aynı paketten ve alt sınıflardan erişilebilir. |
private | Yalnı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. |
final | Değişken: değeri değişmez. Metot: alt sınıfta yeniden yazılamaz. Sınıf: kalıtılamaz. |
abstract | Soyut sınıf (nesnesi oluşturulamaz) ya da gövdesiz metot; alt sınıf metodun gövdesini yazmak zorundadır. |
native | Gö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. |
volatile | Değişkenin her iş parçacığında her zaman güncel değeriyle okunmasını sağlar. |
transient | Nesne serileştirilirken (kaydedilirken) bu alan dikkate alınmaz. |
strictfp | Ondalı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.
| Ne? | Kural | Örnekler |
|---|---|---|
| Sınıf ve arayüz | PascalCase, büyük harfle başlar | String, Ogrenci, MerhabaDunya |
| Değişken, alan, metot | camelCase, küçük harfle başlar | firstName, classAverage, hesapla() |
| İlkel tipler | küçük harf (anahtar kelimedir) | int, float, boolean |
| İsimlendirilmiş sabit | BÜYÜK_HARF, kelimeler _ ile ayrılır | PI, GUNLER_HAFTADA, MAX_SAYI |
| Paket | tamamen küçük harf | syntax, 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; // sabitBunlar 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
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: ö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
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.
| Satır | Açık yazımı | Hesap | x | y |
|---|---|---|---|---|
int x = 5; | — | — | 5 | |
int y = 10; | — | — | 5 | 10 |
x = x + y; | — | 5 + 10 | 15 | 10 |
x -= y; | x = x - y | 15 − 10 | 5 | 10 |
x *= 2; | x = x * 2 | 5 × 2 | 10 | 10 |
x /= 4; | x = x / 4 | 10 / 4 (tam sayı bölmesi) | 2 | 10 |
x %= 3; | x = x % 3 | 2'nin 3'e bölümünden kalan | 2 | 10 |
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 initializedDerleyici 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.
| Alan tipi | Varsayılan |
|---|---|
int, long, byte, short | 0 |
double, float | 0.0 |
char | '\u0000' (kod 0) |
boolean | false |
String ve öteki referanslar | null |
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
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ılar0x, ikilik sayılar0bile başlar:0x1F,0b1010. - Ondalıklı: ondalık ayırıcı noktadır. Bilimsel gösterim
eile 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.
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
trueSonuçlar satır satır şöyle okunur:
1.0 / 3.0yaklaşı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.2sonucu 0.30000000000000004'tür. İki sayının küçük hataları toplanır ve sonuç0.3sabitinin saklanan değerinden farklı olur. Bu yüzden0.1 + 0.2 == 0.3false 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.
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
İ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ünPI 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 floatBirinci 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.
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.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; // hataUyum.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
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.993,(int) -3.99−3 verir. Kesir sıfıra doğru kesilir. - Sığmayan bir tam sayı taşar.
(byte) 130değ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
-126double 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.
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
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.
| İfade | Sonuç | Neden? |
|---|---|---|
7 / 2 | 3 | iki int → tam sayı bölmesi, kesir atılır |
7 / 2.0 | 3.5 | 2.0 double → işlem double yapılır |
(double) 7 / 2 | 3.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.99 | 3 | kesirli kısım atılır |
Math.round(3.99) | 4 | en 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 errora 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.
| Değişken | İfade | Ara adımlar | Değer |
|---|---|---|---|
d1 | a / b | 9 / 4 = 2 (tam sayı bölmesi) → atamada 2.0 | 2.0 |
d2 | (double) a / b | 9 → 9.0; b 4.0'a yükseltilir; 9.0 / 4.0 | 2.25 |
d3 | (double) (a / b) | 9 / 4 = 2 → 2.0 | 2.0 |
k | (int) 9.75 | kesir atılır | 9 |
r | Math.round(9.75) | en yakın tam sayı (long) | 10 |
y | (byte) 200 | 200 = 11001000; işaret biti 1 → 200 − 256 | −56 |
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.
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 birdoubleolduğu için işlem baştandoubleyürür. 37 × 9,0 = 333,0; 333,0 / 5 = 66,6; 66,6 + 32 = 98.6.c * 9 / 5 + 32: her şeyint'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 → chariçin(char)gerekir.- İki
char+ile toplanırsa ikisi deint'e yükseltilir ve bir sayı çıkar. Metin elde etmek için ifadenin birStringile 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);| Satır | Hesap | Sonuç |
|---|---|---|
char c = 'd'; | 'd''nin kodu saklanır | c = 'd' (100) |
int kod = c; | örtük char → int | kod = 100 |
char buyuk = (char) (c - 32); | 100 − 32 = 68 (int), (char) ile 68 = 'D' | buyuk = 'D' |
int fark = 'z' - c; | 122 − 100 | fark = 22 |
char sonraki = c; sonraki++; | 100 → 101; ++ dönüşüm işareti istemez | sonraki = 'e' |
println(kod + " " + … ) | " " metni olduğu için birleştirme | 100 D 22 e |
println(c + 1) | char sayıya yükseltilir: 100 + 1 | 101 |
println("" + c + 1) | önce "d", sonra + 1 metne eklenir | d1 |
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
| Operatör | Anlamı | İfade | Sonuç |
|---|---|---|---|
+ | toplama | x + y | 22 |
- | çıkarma | x - y | 12 |
* | çarpma | x * y | 85 |
/ | bölme | x / y | 3 |
% | kalan (mod) | x % y | 2 |
-17 / 5 | −3 | ||
-17 % 5 | −2 | ||
17.0 / 5 | 3.4 |
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 == 0isençifttir. Tek sayı testi içinn % 2 != 0yazılır.n % 2 == 1negatif tek sayılarda yanlış sonuç verir, çünkü-7 % 2−1'dir. - Son basamak:
n % 10son basamağı,n / 10son 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.
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
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);| Satır | Ne olur? | a | Yeni değişken | Çıktı |
|---|---|---|---|---|
int a = 5; | — | 5 | ||
int b = a++; | önce eski değer (5) kullanılır, sonra a artar | 6 | b = 5 | |
println(a + " " + b) | 6 | 6 5 | ||
int c = ++a; | önce a artar, sonra yeni değer (7) kullanılır | 7 | c = 7 | |
println(a + " " + c) | 7 | 7 7 | ||
int d = a--; | önce eski değer (7), sonra a azalır | 6 | d = 7 | |
println(a + " " + d) | 6 | 6 7 |
b = a++ sonrası: a = 6, b = 5. (b) c = ++a sonrası: a = 7, c = 7. (c) d = a-- sonrası: a = 6, d = 7.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
İş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.
| Öncelik | Operatörler | Yön |
|---|---|---|
| 1 (en yüksek) | sonek: x++ x-- | → |
| 2 | tekli: +x -x ! ++x --x · tip dönüşümü (int) | ← |
| 3 | çarpma, bölme, mod: * / % | → |
| 4 | toplama, çıkarma: + - | → |
| 5 | karşılaştırma: < > <= >= | → |
| 6 | eşitlik: == != | → |
| 7 | mantıksal VE: && | → |
| 8 | mantı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);
2 + 3 * 4 - 6 / 2:*ve/,+ve-'den önce gelir. İkisinden soldaki önce: 3 * 4 = 12.2 + 12 - 6 / 2: sırada kalan yüksek öncelikli işlem 6 / 2 = 3.2 + 12 - 3: kalan+ve-eşit önceliklidir ve soldan sağa yapılır: 2 + 12 = 14.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
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.
| Operatör | Anlamı | Örnek | Sonuç |
|---|---|---|---|
== | eşit mi? | x == y | false |
!= | eşit değil mi? | x != y | true |
> | soldaki büyük mü? | y > x | true |
< | soldaki küçük mü? | x < y | true |
>= | büyük veya eşit mi? | x >= 30 | false |
<= | küçük veya eşit mi? | x <= y | true |
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.
= 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 errorC 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
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.
A | B | A && B | A || B | !A |
|---|---|---|---|---|
true | true | true | true | false |
true | false | false | true | false |
false | true | false | true | true |
false | false | false | false | true |
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
truex < 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
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üçükbolen 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
Üç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ı Negatifsayi −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);sayi | sayi == 0 | sayi > 0 | Sonuç |
|---|---|---|---|
| 0 | true | hesaplanmaz | Sıfır |
| 7 | false | true | Pozitif |
| −5 | false | false | Negatif |
İ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.
| Deyim | Söz dizimi | Kural |
|---|---|---|
if / else | if (koşul) { … } else { … } | koşul boolean olmalı; if (…); sonundaki ; boş deyimdir |
switch | switch (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 |
for | for (int i = 0; i < n; i++) { … } | başlangıç bir kez; koşul turdan önce, güncelleme sonra |
while | while (koşul) { … } | gövde hiç çalışmayabilir |
do-while | do { … } while (koşul); | gövde en az bir kez çalışır; sondaki ; zorunlu |
break / continue | break; · 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.
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.
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.
| Hata | Örnek | İleti | Doğrusu |
|---|---|---|---|
| Eksik noktalı virgül | System.out.println("a") | ';' expected | satı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 MerhabaDunya | class MerhabaDunya is public, should be declared in a file named MerhabaDunya.java | dosyayı sınıf adıyla kaydetmek |
| Büyük-küçük harf | string, Int, system.out | cannot find symbol; package system does not exist | String, int, System |
| Değersiz yerel değişken | int sayac; println(sayac); | variable sayac might not have been initialized | okumadan önce değer vermek |
| Soneksiz sabit | float f = 3.14; · long l = 3000000000; | possible lossy conversion from double to float · integer number too large | 3.14f · 3000000000L |
| Daraltmada dönüşümü unutmak | char k = i; · int n = d; | incompatible types: possible lossy conversion | (char) i · (int) d |
= ile == | if (x = 5) | int cannot be converted to boolean | if (x == 5) |
| Hata | Örnek | Sonuç | Doğrusu |
|---|---|---|---|
| Tam sayı bölmesi | double 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şma | long l = Integer.MAX_VALUE + 1; | −2147483648 | Integer.MAX_VALUE + 1L |
switch'te break yok | case 1: … case 2: … | sonraki durumlar da çalışır | her 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ü bitmez | koşulu yanlışa götüren güncelleme |
Ondalıkta == | 0.1 + 0.2 == 0.3 | false | Math.abs(a - b) < 1e-9 |
| Birleştirme sırası | "Toplam: " + a + b | sayı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.javakaynak kodu denetler veAd.classdosyasına bytecode olarak yazar.java AdJVM'i başlatır, sınıfı yükler vemain'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:
byte1,short2,int4,long8 bayt;float4,double8 bayt;char2 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.20,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.finaldeğişken bir kez değer alır. - Dönüşüm:
byte → short → int → long → float → doubleyönü otomatiktir. Ters yön(tip)ile yapılır; kesir atılır, sığmayan değer taşar.int / inttam 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,continueturun kalanını atlar.
2.2.9Kavram listesi
- kaynak kod source code
- açıklama comment
- blok block
- JDK (geliştirme kiti) Java Development Kit
- bytecode (ara kod) bytecode
- Java Sanal Makinesi (JVM) Java Virtual Machine
- JIT (tam zamanında) derleyici JIT compiler
- platform bağımsızlığı platform independence
- değişken variable
- bildirim declaration
- ilk değer verme initialization
- veri tipi data type
- ilkel tip primitive type
- referans tipi reference type
- tam sayı integer
- karakter (char) character
- boolean (mantıksal tip) boolean
- taşma overflow
- tanımlayıcı identifier
- anahtar kelime keyword
- isimlendirme kuralı naming convention
- atama assignment
- bileşik atama compound assignment
- varsayılan değer default value
- sabit değer literal
- isimlendirilmiş sabit named constant
- tip dönüşümü type conversion
- açık dönüşüm (cast) cast
- genişletme / daraltma dönüşümü widening / narrowing conversion
- tam sayı bölmesi integer division
- ifade expression
- operatör operator
- artırma / azaltma increment / decrement
- işlem önceliği operator precedence
- karşılaştırma operatörü comparison operator
- mantıksal operatör logical operator
- kısa devre değerlendirme short-circuit evaluation
- koşul operatörü conditional (ternary) operator
2.2.10Kendini Dene
Deneme.javaadlı bir dosyadapublic class Testtanımlı. Dosyajavac Deneme.javaile 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. DosyaTest.javaadıyla kaydedilir,javac Test.javaile derlenir vejava Testile (uzantısız) çalıştırılır.int a = 5, b = 7; int c = a * b;satırları derlendiğindea * bhesaplanırken JVM'in işlenen yığınında hangi değerler bulunur?imulkomutundan hemen sonra yığında ne kalır?Cevap
Önce
iload_15'i, sonraiload_27'yi yığına iter; yığında alttan üste 5, 7 durur.imulbu iki değeri çıkarır, çarpar ve sonucu iter: yığında yalnız 35 kalır. Ardındanistore_335'ic'ye yazar ve yığın boşalır. Toplamadakiiaddile aynı düzen geçerlidir.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 çarpmainttipinde yapılır. Gerçek sonuç 86.400.000.000'dir veintsınırını aşar; taşan değer 500654080 olur.longdeğişkene ancak bu taşmış değer atanır. İlk sabiteLeklenince çarpma baştanlongyürür:24L * 60 * 60 * 1000 * 1000= 86400000000.byte b = 120; b += 10;satırlarından sonrabkaçtır? Aynı işb = b + 10;diye yazılsa ne olur?Cevap
b= −126 olur. 130,bytesınırı 127'yi aşar; bileşik atama sonucu kendisibyte'a çevirir ve 130 − 256 = −126 kalır.b = b + 10;ise derlenmez:b + 10birintifadesidir vebyte'a ancak(byte)ile atanabilir (possible lossy conversion from int to byte).Şu adlardan hangileri geçerli tanımlayıcıdır?
_2x,x-2,int,Integer,toplam$,9luCevap
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).char c = '5';ikenSystem.out.println(c + 1);veSystem.out.println(c - '0' + 1);ne yazdırır?Cevap
Birinci satır 54 yazar:
'5''in kodu 53'tür vec + 1birinttoplamıdır. İkinci satır 6 yazar:c - '0'= 53 − 48 = 5, yani rakamın sayı değeridir; buna 1 eklenir.int x = 7; int y = x-- - --x;satırlarından sonraxveykaçtır?Cevap
İşlenenler soldan sağa hesaplanır.
x--eski değeri, 7'yi verir vex6 olur.--xöncex'i 5 yapar ve 5'i verir.y= 7 − 5 = 2,x= 5 olur.int a = 0;ikenif (a != 0 & 10 / a > 1)ileif (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 da10 / abölmesi yapılır ve programArithmeticException: / by zeroile durur.&&ise sol taraf yanlış olunca sağ tarafa hiç bakmaz; koşul yanlış sayılır ve program güvenle sürer.int k = 3;ile başlayan iki döngünün sonundakkaçtır?do { k -= 4; } while (k > 5);while (k > 5) { k -= 4; }(yinek= 3 ile başlayarak)Cevap
do-whilegövdeyi bir kez çalıştırır:k= −1 olur,-1 > 5yanlıştır ve döngü biter; sonuç −1.whilekoşulu önce denetler:3 > 5yanlış 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
.classdosyası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
- Oracle, The Java Tutorials: “Language Basics” (değişkenler, operatörler, ifadeler, kontrol akışı), docs.oracle.com/javase/tutorial.
- 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).
- T. Lindholm vd., The Java Virtual Machine Specification, Java SE 21 Edition (bytecode ve JVM).
- P. Deitel, H. Deitel, Java: How to Program, 11. baskı, Pearson, 2017 (Bölüm 2–5).
- K. Sierra, B. Bates, Head First Java, 3. baskı, O'Reilly, 2022.
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 13. baskı, 2024 (Bölüm 3).
- 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.