Bilgisayar ve Programlamaya Giriş · Ders Notu
Değişkenler, Veri Tipleri ve Operatörler
Her program veriyle çalışır: bir öğrencinin yaşı, bir ürünün fiyatı, bir sınavın ortalaması. Program bu verileri bellekte adlandırılmış kutularda, yani değişkenlerde tutar. Her kutunun bir tipi vardır ve tip, kutuya hangi değerlerin sığacağını belirler. Bu not, Java'da değişken bildirmeyi ve değer vermeyi, sekiz ilkel tipi ve aralıklarını, sınır aşıldığında ortaya çıkan taşmayı ve ondalık sayıların neden tam saklanamadığını anlatır. Ardından tipler arasındaki dönüşümler, tam sayı bölmesi, aritmetik, karşılaştırma ve mantık operatörleri, işlem önceliği ve kısa devre değerlendirme ele alınır. Son kesimler klavyeden Scanner ile veri okumayı, printf ile biçimli çıktı üretmeyi ve bu araçları birleştiren küçük programları içerir. Nottaki bütün Java kodları JDK 21 ile derlenip çalıştırılmıştır; verilen çıktılar programların gerçek çıktısıdır.
Öğrenme çıktıları
- Değişken
- Değişken bildirmek, ilk değer vermek ve değişkenin değerini güncellemek.
- Tip
- İlkel tipleri ve değer aralıklarını tanımak; taşma ve kayan nokta hatası riskine göre uygun tipi seçmek.
- İfadeler
- Atama, tip dönüşümü ve operatörleri doğru kullanmak; bir ifadenin değerini öncelik ve tip kurallarıyla adım adım hesaplamak.
- Girdi/Çıktı
- Scanner ile klavyeden girdi almak, printf ile biçimli çıktı üretmek.
6.1Değişkenler
Bir hesap makinesiyle 70, 85 ve 90 notlarının ortalamasını alan biri, önce üç sayıyı toplar, ara sonucu ekranda tutar, sonra 3'e böler. Ara sonucu bir kâğıda yazmak da mümkündür. Bilgisayar programı da aynı ihtiyacı duyar: okuduğu sayıları, ara sonuçları ve son cevabı bir yerde saklamalıdır. Bu saklama yeri bellektir. Program, bellekte ayırdığı her yere bir ad verir ve o yere adıyla ulaşır.
6.1.1Değişken nedir?
Değişken (variable), belirli tipteki bir değer için bellekte ayrılmış adlı bir kutudur. Değişkenin üç özelliği vardır: adı, tipi (type) ve o anki değeri. Ad ve tip program boyunca sabit kalır; değer ise değişebilir.
int yas = 20; satırının bellekteki karşılığı. Kutunun adı yas, tipi int, değeri 20'dir. int tipi için 32 bitlik yer ayrılır.Şekil 6.1 bu üç özelliği bir arada gösterir. Kutunun üstündeki yas adı, programcının bu kutuya verdiği addır; Java'daki adıyla bir tanımlayıcıdır (identifier). Kutunun içindeki 20 değeri program çalışırken değişebilir: yeni bir yıl başladığında yaş 21 olur. Kutunun altındaki int ise tiptir. Tip iki şeyi belirler. Birincisi, kutunun boyutudur: int için 32 bit, yani 4 bayt ayrılır. İkincisi, kutuya konabilecek değerlerdir: int bir tam sayı tipidir, bu kutuya 20 konabilir ama 20,5 ya da “yirmi” konamaz.
“Değişken” adı matematikten gelir ama anlamı biraz farklıdır. Matematikte x = x + 1 çözümü olmayan bir denklemdir. Programlamada ise bu satır bir komuttur: “x kutusundaki değeri oku, 1 ekle, sonucu yine x kutusuna yaz.” Değişken, zamanla farklı değerler tutabilen bir kutudur; eşitlik işareti de bir eşitlik iddiası değil, bir yazma işlemidir.
6.1.2Bildirim ve ilk değer
Java'da bir değişken kullanılmadan önce bildirilmelidir. Bildirim, derleyiciye “bu adla, bu tipte bir kutu istiyorum” demektir.
Bildirim (declaration), bir değişkenin tipini ve adını belirten deyimdir: int yas;. İlk değer verme (initialization) ise değişkenin başlangıç değerini belirtir. İkisi tek deyimde de yazılabilir: double ortalama = 82.5;.
Sunumdaki dört satır, bildirimin farklı biçimlerini gösterir:
int yas; // bildirim
yas = 20; // atama
double ortalama = 82.5; // birlikte
final int MAKSIMUM = 100; // sabitBirinci satır yalnızca bildirimdir: yas adlı int kutusu ayrılır ama içine henüz bir değer konmamıştır. İkinci satır bir atamadır (assignment): 20 değeri yas kutusuna yazılır. Üçüncü satır bildirimle ilk değeri birleştirir; ortalama kutusu ayrılır ve aynı anda 82.5 değerini alır. Dördüncü satırdaki final sözcüğü kutuyu bir sabite çevirir: MAKSIMUM bir kez 100 değerini alır ve program boyunca bu değeri korur.
yas kutusunun değeri yoktur ve okunamaz; atamadan sonra 20'dir. ortalama tek satırda bildirilip değer alır. final ile bildirilen MAKSIMUM yeniden atanamaz.Java, bir metodun içinde bildirilen değişkenlere yerel değişken (local variable) der. Yerel değişkenler için katı bir kural vardır: değişken okunmadan önce kesin olarak bir değer almış olmalıdır. Derleyici bu kuralı programı çalıştırmadan, kodu okuyarak denetler. Aşağıdaki iki satırlık parça bu yüzden derlenmez:
int yas;
System.out.println(yas);error: variable yas might not have been initialized
System.out.println(yas);
^
1 errorİleti “yas değişkenine değer verilmemiş olabilir” der. Derleyici yas kutusunun boş olduğunu görür ve programı hiç üretmez. Bu kural bir güvenlik önlemidir. Kural olmasaydı program, kutuda o an rastlantıyla bulunan bir değeri okuyabilir ve anlamsız sonuçlar üretebilirdi. Bazı eski dillerde (örneğin C'de) bu durum gerçekten yaşanır.
final değişkenler için de benzer bir denetim vardır. Değer bir kez verildikten sonra ikinci atama derleme hatasıdır:
final int MAKSIMUM = 100;
MAKSIMUM = 200;error: cannot assign a value to final variable MAKSIMUM
MAKSIMUM = 200;
^
1 errorSabitler, programda birden çok yerde geçen ve değişmemesi gereken değerler için kullanılır: en yüksek not, KDV oranı, bir dizinin boyutu. Sayıyı her yerde ayrı ayrı yazmak yerine bir sabit tanımlamak iki yarar sağlar. Kod okunur hâle gelir; puan <= MAKSIMUM ifadesi puan <= 100 ifadesinden daha çok şey anlatır. Ayrıca değer değişirse yalnızca tek satır düzeltilir.
Bildirilen bir değişkenin otomatik olarak 0 değerini aldığını sanmak. Java'da yerel değişkenler kendiliğinden değer almaz; int toplam; satırından sonra toplam += 5; yazmak “might not have been initialized” derleme hatası verir. Toplam tutacak bir değişken açıkça sıfırla başlatılır: int toplam = 0;.
6.1.3Değişkenler bellekte adım adım
Değişkenlerin nasıl değer aldığını ve değiştirdiğini görmenin en iyi yolu, bir programı satır satır izlemektir. Sunumdaki örnek program şudur:
int yas; // bildirim
yas = 20; // ilk atama
yas = yas + 1; // güncelleme
double ortalama = 82.5; // bildirim + ilk değer
ortalama += 5; // bileşik atama
final int MAKSIMUM = 100; // sabit
System.out.println(yas);
System.out.println(ortalama);Program iki satır çıktı üretir:
21
87.5Tablo 6.1, her satır çalıştıktan sonra kutuların durumunu verir. Soru işareti, kutunun ayrıldığını ama henüz değer almadığını; tire, kutunun henüz hiç ayrılmadığını gösterir.
| Satır | Deyim | yas | ortalama | MAKSIMUM | Çıktı |
|---|---|---|---|---|---|
| 1 | int yas; | ? | – | – | |
| 2 | yas = 20; | 20 | – | – | |
| 3 | yas = yas + 1; | 21 | – | – | |
| 4 | double ortalama = 82.5; | 21 | 82.5 | – | |
| 5 | ortalama += 5; | 21 | 87.5 | – | |
| 6 | final int MAKSIMUM = 100; | 21 | 87.5 | 100 | |
| 7 | System.out.println(yas); | 21 | 87.5 | 100 | 21 |
| 8 | System.out.println(ortalama); | 21 | 87.5 | 100 | 87.5 |
Üçüncü satır dikkatle okunmalıdır. yas = yas + 1; deyimi iki aşamada çalışır. Önce eşittir işaretinin sağındaki ifade hesaplanır: yas kutusundaki 20 okunur ve 1 eklenir, sonuç 21'dir. Bu aşamada kutu henüz değişmemiştir. İkinci aşamada 21 değeri soldaki kutuya yazılır; eski 20 silinir. Bir kutu aynı anda yalnızca bir değer tutar ve yeni değer eskisinin üzerine yazılır.
yas kutusu boştur (?). Güncellemede önce sağ taraf hesaplanır: 20 + 1 → 21. Sonuç aynı kutuya yazılır. ortalama += 5 deyimi de aynı biçimde işler: 82.5 + 5 → 87.5.Yedinci ve sekizinci satırlar kutuları yalnızca okur. Okuma bir değeri kutudan kopyalar ama kutuyu değiştirmez; System.out.println(yas) satırından sonra yas hâlâ 21'dir. Son durumda bellekte üç kutu vardır ve ekranda iki satır çıktı görünür. Altıncı satırdan sonra MAKSIMUM = 200; yazılsaydı program derlenmezdi; final değişkenin değeri bir kez verilir.
6.1.4Atama ve güncelleme
Atama operatörü =, Java'nın en çok kullanılan operatörüdür ve her zaman aynı sırayla çalışır: önce sağ taraf hesaplanır, sonra değer soldaki değişkene yazılır. Sol tarafta her zaman tek bir değişken bulunur. 5 = x; ya da x + 1 = 7; gibi satırlar anlamsızdır, çünkü 5 ya da x + 1 bir kutu değildir.
Atanan değer, değişkenin tipiyle uyumlu olmalıdır. int bir kutuya "Ada" dizgisi atanamaz; derleyici bunu tip uyuşmazlığı hatası olarak bildirir.
Programlarda bir değişkenin değerini, kendi eski değerini kullanarak değiştirmek çok sık yapılır: toplama yeni bir fiyat eklemek, sayacı bir artırmak, bakiyeden bir tutar düşmek. Bu iş için Java'da kısa bir yazım vardır.
Bileşik atama (compound assignment), bir aritmetik işlemi atamayla birleştiren operatördür: +=, -=, *=, /=, %=. toplam += fiyat deyimi toplam = toplam + fiyat anlamına gelir.
toplam += fiyat; deyiminin üç adımı. Başta toplam = 50, fiyat = 20'dir. Önce iki kutu okunur ve 50 + 20 → 70 hesaplanır. Sonuç soldaki kutuya yazılır: toplam = 70. fiyat değişmez.Şekil 6.4 işlemin adımlarını gösterir. Sağdaki değişken (fiyat) yalnızca okunur, soldaki değişken (toplam) hem okunur hem yazılır. Bileşik atamanın bir inceliği daha vardır: sağ taraf, işlemden önce bütünüyle hesaplanır. i *= 3 + 7 deyimi i = i * 3 + 7 değil, i = i * (3 + 7) demektir. i = 5 için sonuç 22 değil 50'dir.
Bir alışveriş sepetinde toplam 0 TL'den başlar. Sırayla 45 TL'lik ve 30 TL'lik iki ürün eklenir, sonra 10 TL indirim uygulanır, en son tutar 2 taksite bölünür. Hangi deyimler yazılır ve her adımdan sonra toplam kaç olur?
| Deyim | Hesap | toplam |
|---|---|---|
int toplam = 0; | ilk değer | 0 |
toplam += 45; | 0 + 45 | 45 |
toplam += 30; | 45 + 30 | 75 |
toplam -= 10; | 75 − 10 | 65 |
toplam /= 2; | 65 / 2 (tam sayı bölmesi) | 32 |
Son satırdaki sonuç 32,5 değil 32'dir. toplam bir int olduğu için bölme tam sayı bölmesidir ve kesir atılır. Taksit tutarının kuruşuyla hesaplanması gerekiyorsa değişken baştan double seçilmelidir. İki tam sayının bölümü Java'da her zaman tam sayıdır.
6.1.5Tanımlayıcı kuralları ve adlandırma
Değişkenlere, metotlara ve sınıflara verilen adların hepsi tanımlayıcıdır. Java, bir tanımlayıcının nasıl yazılabileceğini birkaç kesin kuralla sınırlar. Bu kurallara uymayan bir ad derleme hatası verir.
| Kural | Geçerli | Geçersiz |
|---|---|---|
| Harf, _ veya $ ile başlar | ogrenci1 | 1ogrenci |
| Boşluk içermez | toplamPuan | toplam puan |
| Anahtar kelime değildir | sinifNo | class |
| Tek alt çizgi ad olamaz | _deger | _ |
| Büyük-küçük harfe duyarlı | puan ve Puan | Aynı değişken sanmak |
Tanımlayıcı bir harfle, alt çizgiyle (_) ya da dolar işaretiyle ($) başlar; sonraki karakterler bunlara ek olarak rakam da olabilir. Rakamla başlayan bir ad derleyiciyi şaşırtır. int 1ogrenci = 5; satırında derleyici int'ten sonra bir ad bekler ama 1 rakamıyla karşılaşır ve satırı anlamlandıramaz:
error: not a statement
int 1ogrenci = 5;
^
error: ';' expected
int 1ogrenci = 5;
^
2 errorsİletiler sorunu doğrudan söylemez; “bu bir deyim değil” ve “noktalı virgül bekleniyordu” der. Derleyici hatayı bulduğu yerde, kendi anladığı biçimde bildirir. Bu yüzden bir derleme iletisi okunurken iletinin işaret ettiği satırın tamamına bakılmalıdır.
Anahtar kelimeler (keyword), Java'nın kendi işleri için ayırdığı sözcüklerdir: int, double, class, if, final, new, return bunlardan birkaçıdır. Bu sözcükler ad olarak kullanılamaz; int class = 5; satırı üç ayrı hata iletisi üretir. Tek başına alt çizgi de JDK 21'de ad değildir. int _ = 3; satırını derleyici “adsız değişkenler önizleme özelliğidir” iletisiyle reddeder. Daha yeni Java sürümlerinde _ “adsız değişken” anlamına gelir; bu durumda da değişkene adıyla ulaşılamaz.
Son kural en sinsi olanıdır. Java büyük ve küçük harfi farklı karakter sayar. puan ve Puan iki ayrı değişkendir ve aynı programda yan yana yaşayabilir:
int puan = 70;
int Puan = 90;
System.out.println(puan + " " + Puan);70 90Program hatasız derlenir ve iki farklı değer yazar. Programcı aynı değişkeni kullandığını sanıyorsa hata sessizce oluşur. Bu yüzden büyük-küçük harf kullanımı rastgele bırakılmaz; adlandırma kuralları bu tutarlılığı sağlar.
Derleyicinin denetlediği kuralların yanında, Java programcılarının uyduğu yazılı olmayan adlandırma (naming convention) gelenekleri de vardır. Bu geleneklere uymayan kod derlenir ama okuyan herkesi yorar.
toplamPuan). Sınıf adları büyük harfle başlar (OgrenciKaydi). Sabitler büyük harflerle yazılır ve kelimeler alt çizgiyle ayrılır (MAKSIMUM_PUAN).- lowerCamelCase: değişken ve metot adları. İlk kelime küçük harfle başlar, sonraki her kelimenin ilk harfi büyüktür:
toplamPuan,ogrenciSayisi,ortalamaHesapla. Kelimeler arasındaki büyük harfler, devenin hörgüçleri gibi kelime sınırını gösterir. - UpperCamelCase: sınıf adları. Her kelime büyük harfle başlar:
OgrenciKaydi,Scanner,String. - BÜYÜK_HARF_VE_ALT_ÇİZGİ:
finalsabitler.MAKSIMUM_PUAN,KDV_ORANI.
Bir adın en önemli niteliği, değerin rolünü anlatmasıdır. x, a1, temp2 gibi adlar, kodu yazan kişiye o an anlamlı gelir; bir hafta sonra kimseye bir şey söylemez. toplamPuan adı ise hem tipini hem görevini sezdirir. Türkçe karakterler (ç, ğ, ı, ö, ş, ü) Java tanımlayıcılarında geçerlidir, ama derste ve çoğu projede ASCII harflerle yazmak tercih edilir: ogrenci, sinif, yas. Bu tercih, farklı karakter kodlamalı bilgisayarlar arasında dosya taşınırken çıkabilecek sorunları önler.
Bir yerde toplamPuan, başka bir yerde toplampuan yazmak. İkinci ad bildirilmemişse derleyici “cannot find symbol” (simge bulunamadı) hatası verir. Daha kötüsü, iki ad da bildirilmişse program derlenir ve değerler farklı kutulara gider. Adlandırma geleneğine her yerde aynı biçimde uymak, bu tür hataların çoğunu baştan önler.
6.2Veri tipleri
Bellek, en alt düzeyde yalnızca bitlerden, yani 0 ve 1'lerden oluşur. 01000001 bit dizisi bir yerde 65 sayısı, başka bir yerde 'A' harfi anlamına gelebilir. Bitlerin nasıl yorumlanacağını tip belirler. Java, her değişkenin tipinin derleme sırasında bilinmesini ister. Bu yüzden Java katı tipli bir dildir: derleyici her işlemin tiplere uygun olup olmadığını program çalışmadan önce denetler ve pek çok hatayı daha ilk derlemede yakalar.
6.2.1Temel tip grupları
Java tipleri iki büyük aileye ayrılır. İlkel tipler (primitive type) dilin içine gömülü sekiz temel tiptir. İlkel bir değişkenin kutusunda değerin kendisi durur. Referans tipleri ise dizgiler (String), diziler ve sınıflardır. Referans tipinde bir değişkenin kutusunda nesnenin kendisi değil, bellekte başka bir yerde duran nesneye giden bir referans (reference) bulunur.
| Grup | Örnek | Kullanım |
|---|---|---|
| Tam sayı | byte, short, int, long | Sayma ve ayrık değer |
| Kayan noktalı | float, double | Ölçüm ve yaklaşık hesap |
| Karakter | char | Tek UTF-16 kod birimi |
| Mantıksal | boolean | true / false |
| Referans | String, dizi, sınıf | Nesneye referans |
Gruplar, verinin doğasına göre seçilir. Bir sınıftaki öğrenci sayısı, bir dizideki konum, bir sayaç gibi sayılan değerler tam sayıdır. Boy, sıcaklık, fiyat gibi ölçülen değerler kayan noktalı sayıyla tutulur. Tek bir harf için char, evet/hayır cevabı için boolean kullanılır. Ad, adres, cümle gibi metinler ise String nesneleridir. Bu notta ağırlıklı olarak ilkel tipler ele alınır; String yalnızca birleştirme ve girdi okuma bağlamında görünür.
6.2.2İlkel tipler ve boyutları
Sekiz ilkel tipin her biri bellekte sabit sayıda bit kaplar. Bu sayı Java'da her bilgisayarda ve her işletim sisteminde aynıdır. C gibi bazı dillerde int'in boyutu makineye göre değişebilir; Java'da int her yerde 32 bittir. Bu kararlılık, aynı programın her yerde aynı sonucu vermesini sağlar.
| Tip | Bit | En küçük | En büyük | Sabit örneği |
|---|---|---|---|---|
byte | 8 | −128 | 127 | byte seviye = 7; |
short | 16 | −32.768 | 32.767 | short yil = 2024; |
int | 32 | −2.147.483.648 | 2.147.483.647 | int adet = 42; |
long | 64 | −9.223.372.036.854.775.808 | 9.223.372.036.854.775.807 | long nufus = 8_000_000L; |
float | 32 | yaklaşık ±3,4 × 1038; ≈ 7 anlamlı basamak | float oran = 1.5F; | |
double | 64 | yaklaşık ±1,8 × 10308; ≈ 15–16 anlamlı basamak | double fiyat = 19.90; | |
char | 16 | 0 | 65.535 | char not = 'A'; |
boolean | JVM'e bağlı | true ya da false | boolean aktif = true; | |
Bir tipin aralığı bit sayısından hesaplanır. n bitle 2n farklı bit dizisi yazılabilir. byte için 28 = 256 farklı değer vardır. Java tam sayı tipleri işaretlidir: bu değerlerin yarısı negatif sayılara, yarısı sıfır ve pozitif sayılara ayrılır. Sıfır pozitif tarafta yer kapladığı için aralık −128'den 127'ye uzanır; 128 sayısı byte'a sığmaz. Aynı hesap int için −231 … 231 − 1 aralığını verir:
231 = 2.147.483.648, dolayısıyla int aralığı −2.147.483.648 … 2.147.483.647
Kabaca akılda tutmak için int'in sınırı “yaklaşık ±2,1 milyar”, long'unki “yaklaşık ±9,2 kentilyon” (9,2 × 1018) olarak hatırlanabilir. char tipi 16 bitlik olmasına karşın işaretsizdir; negatif değeri yoktur ve 0 ile 65.535 arasındaki kodları tutar.
Programda doğrudan yazılan değerlere sabit (literal) denir: 42, 19.90, 'A', true. Sabitlerin de tipi vardır ve bu tip yazılış biçiminden anlaşılır:
- Noktasız bir tam sayı sabiti (
42) int'tir. SonunaLeklenirse long olur (42L). Küçüklde geçerlidir ama 1 rakamına çok benzediği için kullanılmaz. - Noktalı bir sabit (
19.90) ya da bilimsel gösterimle yazılmış bir sabit (3.9e10, yani 3,9 × 1010) double'dır. SonunaFeklenirse float olur (1.5F). - Uzun sayılarda basamakları gruplamak için alt çizgi kullanılabilir:
8_000_000_000L. Alt çizgiler yalnızca okumayı kolaylaştırır; değeri değiştirmez.
Java kodunda ondalık ayırıcı her zaman noktadır. 19.90 Java'da on dokuz virgül doksandır; 19,90 ise sözdizimi hatasıdır. Bu not, Türkçe metin içinde ondalık virgül kullanır (19,90 TL), ama kod parçalarında ve program çıktılarında Java'nın yazdığı biçimi aynen korur.
Dünya nüfusu (yaklaşık 8 milyar) bir değişkende tutulacak. int nufus = 8_000_000_000; satırı neden derlenmez? long nufus = 8_000_000_000; satırı neden yine derlenmez?
8 milyar, int'in en büyük değeri olan 2.147.483.647'den büyüktür; değer int kutusuna sığmaz. Ama asıl sorun daha önce başlar: noktasız ve L'siz yazılan 8_000_000_000 sabitinin kendisi int tipindedir. Derleyici bu sabiti int olarak okumaya çalışırken sınırı aşar ve sol taraftaki tipe bakmadan hata verir:
error: integer number too large
long nufus = 8_000_000_000;
^
1 errorHedef long olsa bile ileti aynıdır. Doğru yazım, sabitin kendisini L ile long yapmaktır: long nufus = 8_000_000_000L;. Bu satır derlenir ve System.out.println(nufus) 8000000000 yazar.
Kayan noktalı sabitlerde de benzer bir kural vardır. 1.5 bir double sabitidir; float oran = 1.5; satırı “possible lossy conversion from double to float” (double'dan float'a olası kayıplı dönüşüm) iletisiyle derlenmez. float istendiğinde sabit 1.5F yazılır. Çoğu programda bu soru hiç doğmaz, çünkü varsayılan seçim zaten double'dır.
6.2.3Tam sayılar bellekte nasıl durur?
Tam sayı tipi seçerken çoğu durumda int yeterlidir ve varsayılan seçim odur. long, int'in yetmediği büyük değerler için kullanılır: dünya nüfusu, milisaniye cinsinden zaman, banka hesabındaki kuruş tutarı. byte ve short ise genellikle büyük veri dizilerinde bellekten kazanmak için seçilir. Bir hesapta beklenen en büyük değer önceden kestirilmelidir; int'in sınırına yaklaşabilecek bir değer baştan long seçilir. Bu kararın önemi, sınır aşıldığında olanlarda yatar. Bunu anlamak için tam sayıların bitlerle nasıl gösterildiğine kısaca bakmak gerekir.
Java tam sayıları ikiye tümleyen (two's complement) gösterimle saklar. Bu gösterimde en soldaki bit işaret bitidir (sign bit): 0 ise sayı sıfır ya da pozitif, 1 ise negatiftir. byte üzerinde birkaç örnek:
| Bitler | İşaretsiz okunuş | byte değeri |
|---|---|---|
0000 0000 | 0 | 0 |
0000 0001 | 1 | 1 |
0111 1111 | 127 | 127 (en büyük) |
1000 0000 | 128 | 128 − 256 = −128 (en küçük) |
1000 0010 | 130 | 130 − 256 = −126 |
1100 1000 | 200 | 200 − 256 = −56 |
1111 1111 | 255 | 255 − 256 = −1 |
Tablonun son sütunu basit bir kurala dayanır: işaret biti 1 olan 8 bitlik bir dizi, işaretsiz değerinden 256 çıkarılarak okunur. Aynı kural int için 232 ile, long için 264 ile geçerlidir. Bu kural, taşmayı ve daraltma dönüşümlerini hesaplamak için yeterlidir.
6.2.4Taşma
Bir tam sayı işleminin sonucu tipin aralığına sığmadığında ne olur? Bir arabanın kilometre sayacı 999.999'dan sonra 000.000'a döner. Java tam sayıları da benzer davranır: sonuç aralığın bir ucundan taşar ve öbür uçtan geri girer.
Taşma (overflow), bir tam sayı işleminin sonucunun tipin aralığını aşmasıdır. Java'da taşma hata vermez. Sonucun yalnızca tipe sığan alt bitleri saklanır; değer sessizce aralığın öbür ucuna sarar.
Sunumdaki örnek program iki tipte de taşmayı gösterir:
int x = Integer.MAX_VALUE;
x++;
byte b = 127;
b++;
System.out.println(x + " " + b);-2147483648 -128Program satır satır şöyle çalışır:
| Satır | Deyim | x | b | Açıklama |
|---|---|---|---|---|
| 1 | int x = Integer.MAX_VALUE; | 2147483647 | – | bitler 0111 1111 … 1111 |
| 2 | x++; | −2147483648 | – | bitler 1000 0000 … 0000 |
| 3 | byte b = 127; | −2147483648 | 127 | bitler 0111 1111 |
| 4 | b++; | −2147483648 | −128 | bitler 1000 0000 |
| 5 | System.out.println(x + " " + b); | −2147483648 | −128 | çıktı: -2147483648 -128 |
İkili toplama, onluk sistemdeki elde mantığıyla çalışır. 0111…1 dizisine 1 eklenince en sağdaki 1 ile 1'in toplamı 0 olur ve 1 elde kalır. Elde sola doğru bütün 1'leri sıfırlayarak ilerler ve en sonunda işaret bitine ulaşır. Sonuç 1000…0'dır. Matematiksel olarak doğru cevap 2.147.483.648'dir, ama bu değer int aralığının dışındadır. İşaret biti 1 olan dizinin değeri 2.147.483.648 − 232 = −2.147.483.648 çıkar. Kilometre sayacı benzetmesiyle, sayı aralığın tepesinden taşar ve dibinden geri girer.
Taşmanın tehlikesi sessiz olmasıdır. Program durmaz, uyarı vermez; yanlış sayıyla hesaba devam eder. Taşma yalnızca sınıra bir eklemekle de olmaz. İki makul görünen sayının çarpımı bile int'i aşabilir:
int a = 50000;
int b = 50000;
System.out.println(a * b);
System.out.println((long) a * b);
System.out.println(a * b / 1000);
long yanlis = a * b;
System.out.println(yanlis);-1794967296
2500000000
-1794967
-179496729650.000 × 50.000 = 2.500.000.000'dır ve int sınırını aşar. İlk satır bu yüzden negatif bir sayı yazar: 2.500.000.000 − 232 = −1.794.967.296. İkinci satırda (long) a dönüşümü çarpmadan önce yapılır; işlem long ile yürür ve doğru sonuç çıkar. Üçüncü satır, taşmanın sonraki işlemlere de bulaştığını gösterir: yanlış çarpım 1000'e bölünür ve yanlış bir bölüm elde edilir. Dördüncü satır en yanıltıcı olanıdır. Sonuç long bir değişkene atanır ama çarpma yine int ile yapılır, çünkü işlemin iki işleneni (operand), yani üzerinde işlem yapılan iki değer de int'tir. Sonuç taşmış hâliyle long'a kopyalanır. Hedefin long olması, işlemin tipini değiştirmez.
long sonuc = a * b; yazınca çarpmanın long ile yapılacağını sanmak. Java bir ifadenin tipini yalnızca işlenenlerine bakarak belirler; sonucun nereye atanacağına bakmaz. İki int'in çarpımı int'tir ve gerekirse taşar. Doğrusu, işlenenlerden birini işlemden önce long yapmaktır: long sonuc = (long) a * b; ya da a * 1L * b.
6.2.5Sınır sabitleri ve Math.addExact
Tiplerin sınır değerlerini elle yazmak hem zor hem tehlikelidir; 2147483647 sayısında tek bir basamak yanlış yazılabilir. Java bu değerleri hazır sabitler olarak sunar:
| Sabit | Değer |
|---|---|
Integer.MIN_VALUE | -2147483648 |
Integer.MAX_VALUE | 2147483647 |
Long.MIN_VALUE | -9223372036854775808 |
Long.MAX_VALUE | 9223372036854775807 |
Benzer sabitler öteki tipler için de vardır: Byte.MAX_VALUE, Short.MIN_VALUE, Double.MAX_VALUE. Bu adlar hem okunaklıdır hem de yanlış yazılamaz.
Taşmanın sessizce geçmesi istenmiyorsa Math sınıfının “kesin” (exact) metotları kullanılır. Math.addExact, Math.subtractExact ve Math.multiplyExact işlemi yapar; sonuç tipe sığmıyorsa sessizce sarmak yerine bir istisna (exception) fırlatır. İstisna, programın olağan akışını durduran bir hata bildirimidir.
int x = Integer.MAX_VALUE;
System.out.println(x + 1);
// -2147483648 (sessiz taşma)
System.out.println(
Math.addExact(x, 1));
// ArithmeticException:
// integer overflow-2147483648
Exception in thread "main" java.lang.ArithmeticException: integer overflowİlk println sessiz taşmayı gösterir ve −2147483648 yazar. İkinci satırda Math.addExact(x, 1) çağrılır; sonuç int'e sığmadığı için ArithmeticException fırlatılır ve program “integer overflow” iletisiyle durur. long değerlerde ileti “long overflow” olur. Programın durması ilk bakışta kötü görünür, ama yanlış bir sonuçla devam etmesinden çok daha iyidir: hata hemen fark edilir ve yeri bellidir.
Taşmanın şaşırtıcı bir örneği de mutlak değerdedir. Math.abs(Integer.MIN_VALUE), beklenen 2.147.483.648 yerine yine −2147483648 verir. Nedeni aralığın simetrik olmamasıdır: en küçük int'in mutlak değeri olan 231 int'e sığmaz. Negatifini almak taşmaya yol açar ve sayı kendisine geri döner. Aynı durum -Integer.MIN_VALUE ifadesinde de görülür.
6.2.6Kayan noktalı sayılar
Ondalık kesirli sayılar Java'da kayan noktalı sayı (floating-point number) olarak saklanır. Ad, sayının bilimsel gösterime benzer bir biçimde tutulmasından gelir: bir dizi anlamlı basamak ve virgülün yerini belirten bir üs. 12.345,678 = 1,2345678 × 104 yazılışında virgül, üsse göre “kayar”. Bu sayede aynı 64 bit hem çok küçük (10−300) hem çok büyük (10300) sayıları tutabilir.
double, çoğu yaklaşık hesap için varsayılan tiptir; yaklaşık 15–16 anlamlı basamağı vardır. float yarı boyuttadır ve yaklaşık 7 anlamlı basamak tutar. Aradaki fark kolayca görülür:
System.out.println(0.1 + 0.2);
System.out.println(1.0 - 0.9);
System.out.println(0.1 + 0.2 == 0.3);
System.out.println(0.1 + 0.2 - 0.3);
System.out.println(Math.abs(0.1 + 0.2 - 0.3) < 1e-9);
System.out.println(0.1f + 0.2f);
System.out.println(1.1f * 3);
System.out.println(1.1 * 3);
System.out.println(100.0 / 3);
System.out.println(100.0f / 3);
System.out.println(0.5 + 0.25 == 0.75);
System.out.println(new java.math.BigDecimal(0.1));
System.out.println(new java.math.BigDecimal(0.2));
System.out.println(new java.math.BigDecimal(0.1 + 0.2));
System.out.println(new java.math.BigDecimal(0.3));0.30000000000000004
0.09999999999999998
false
5.551115123125783E-17
true
0.3
3.3000002
3.3000000000000003
33.333333333333336
33.333332
true
0.1000000000000000055511151231257827021181583404541015625
0.200000000000000011102230246251565404236316680908203125
0.3000000000000000444089209850062616169452667236328125
0.299999999999999988897769753748434595763683319091796875Bu çıktının satırları tek tek okunduğunda kayan noktalı sayıların temel sorunu ortaya çıkar. Sorun, ikili tabanın bazı ondalık kesirleri tam gösterememesidir. Onluk sistemde 1/3 sonsuz bir kesirdir: 0,3333… Hiçbir sonlu basamak sayısı onu tam yazamaz. İkili sistemde aynı sorun 1/10 için yaşanır. 0,1'in ikili açılımı 0,0001100110011… biçiminde sonsuza kadar sürer. Bellekteki 52 bitlik kesir alanı bu açılımı bir yerde keser ve sayı, 0,1'e çok yakın ama ondan biraz farklı bir değer olarak saklanır.
Çıktının son dört satırı, BigDecimal sınıfıyla bellekteki değerlerin tam ondalık açılımını gösterir. Saklanan “0.1”, gerçekte 0,1000000000000000055511…'dir. Saklanan “0.2” de 0,2'den biraz büyüktür. İki küçük fazlalık toplanınca sonuç, 0,3 için saklanabilecek en yakın double değerine değil, bir sonrakine düşer. println bu değeri 0.30000000000000004 diye yazar. 0,3 için saklanan değer ise 0,29999999999999998889…'dur. İki sayı farklı olduğu için 0.1 + 0.2 == 0.3 ifadesi false verir.
Aynı satırlar başka iki dersi de taşır. 1.1f * 3 float ile 3.3000002 verir, double ile 3.3000000000000003 verir. float'taki hata yedinci basamakta, double'daki hata on altıncı basamakta görünür. Buna karşılık 0.5 + 0.25 == 0.75 true'dur. 0,5 ve 0,25 ikinin kuvvetleriyle yazılabildiği için (1/2 ve 1/4) tam saklanır. Hatanın kaynağı kayan noktalı sayıların kendisi değil, onluk kesirlerin ikili tabanda sonsuz açılmasıdır.
double a = 0.1 + 0.2;
System.out.println(a);
System.out.println(a == 0.3);
System.out.println(Math.abs(a - 0.3) < 1e-9);0.30000000000000004
false
trueSunumdaki bu kısa program, karşılaştırmanın doğru yolunu gösterir. İki kayan noktalı sayı == ile karşılaştırılmaz. Bunun yerine aralarındaki farkın mutlak değeri küçük bir eşikle (epsilon) karşılaştırılır: Math.abs(a - 0.3) < 1e-9. Buradaki fark yaklaşık 5,55 × 10−17'dir ve 10−9'dan çok küçüktür; karşılaştırma true verir. Eşiğin büyüklüğü probleme göre seçilir. Milimetre düzeyinde ölçümlerde 10−6 yeterlidir; çok küçük fiziksel büyüklüklerde daha küçük bir eşik gerekir.
Küçük hatalar tekrarlanan işlemlerde birikir. 0,1'i on kez toplayan bir döngü 1,0 değil 0.9999999999999999 bulur. Para gibi kuruşu kuruşuna doğru olması gereken hesaplarda bu birikim kabul edilemez. Böyle hesaplarda ya tutarlar kuruş cinsinden tam sayı (long) olarak tutulur ya da BigDecimal sınıfı kullanılır. BigDecimal, sayıyı onluk basamaklarıyla saklar:
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b));
System.out.println(a.add(b).compareTo(new BigDecimal("0.3")) == 0);
BigDecimal fiyat = new BigDecimal("19.90");
BigDecimal adet = new BigDecimal("3");
System.out.println(fiyat.multiply(adet));0.3
true
59.70BigDecimal nesnesi dizgiyle ("0.1") oluşturulur; new BigDecimal(0.1) yazılırsa hatalı double değeri olduğu gibi kopyalanır. Toplama + yerine add, çarpma multiply metoduyla yapılır. 19,90 TL'lik üç ürünün tutarı tam olarak 59,70 çıkar.
İki double değeri == ile karşılaştırıp şaşırmak: if (toplam == 1.0) koşulu, toplam 0,1'lerin birikmesiyle oluştuysa hiç sağlanmaz. Kayan noktalı sayılar her zaman bir eşikle karşılaştırılır. Ayrıca kuruş hesabı double ile yapılmaz; tam sayı kuruş ya da BigDecimal kullanılır.
6.2.7char ve boolean
char tipi tek bir karakter tutar ve sabiti tek tırnakla yazılır: 'A', '7', 'ç'. Bilgisayar harfleri doğrudan saklayamaz; her karaktere bir sayı karşılık gelir ve kutuda o sayı durur. Bu eşleme Unicode standardının işidir. 'A' harfinin kodu 65, 'a' harfininki 97, '0' rakamınınki 48, boşluk karakterininki 32'dir. Java char'ı, Unicode metnini 16 bitlik birimlerle saklayan UTF-16 kodlamasının bir kod birimidir. Türkçe harflerin hepsi tek bir char'a sığar: 'ç' 231, 'ğ' 287, 'ı' 305, 'İ' 304, 'ş' 351 kodlarıyla saklanır. (Bazı emojiler gibi çok az kullanılan karakterler iki char gerektirir; bu ayrıntı başlangıç programlarında karşılaşılmaz.)
Tek tırnak ile çift tırnak farklı tipler üretir. 'A' bir char'dır; kutuda 65 sayısı durur. "A" ise tek harflik bir dizgidir (string), yani bir String nesnesidir; değişken bu nesneye giden referansı tutar. İkisi birbirinin yerine kullanılamaz. char c = "A"; satırı “String cannot be converted to char” iletisiyle derlenmez.
boolean tipi yalnızca iki değer alır: true (doğru) ve false (yanlış). Karşılaştırmaların ve koşulların sonucu boolean'dır. C dilinde 0 yanlış, sıfırdan farklı her sayı doğru sayılır; Java'da bu eşleme yoktur. boolean aktif = 1; satırı “int cannot be converted to boolean” hatası verir. Bu katılık, koşullarda sık yapılan bir hatayı derleme sırasında yakalar.
6.2.8char aritmetiği
char kutusunda bir sayı durduğu için char değerleriyle aritmetik yapılabilir. Sunumdaki örnek bu ilişkiyi adım adım gösterir:
char c = 'A';
c++;
int k = c + 1;
char d = (char)(c + 1);
System.out.println(c + " " + k + " " + d);B 67 C| Satır | Deyim | c (char) | k (int) | d (char) | Açıklama |
|---|---|---|---|---|---|
| 1 | char c = 'A'; | 'A' (65) | – | – | kutuda 65 durur |
| 2 | c++; | 'B' (66) | – | – | kod 1 artar; tip char kalır |
| 3 | int k = c + 1; | 'B' (66) | 67 | – | c + 1 int'tir: 66 + 1 = 67 |
| 4 | char d = (char)(c + 1); | 'B' (66) | 67 | 'C' (67) | 67 yeniden char'a çevrilir |
| 5 | System.out.println(…); | 'B' | 67 | 'C' | çıktı: B 67 C |
c + 1 ifadesinde c int'e yükselir ve sonuç 67 sayısıdır. (char)(c + 1) dönüşümü aynı 67'yi yeniden harf olarak yorumlar: 'C'.Örneğin can alıcı noktası üçüncü satırdır. c + 1 ifadesinde bir char ile bir int toplanır. Java, aritmetik işlemlerde char'ı önce int'e çevirir; sonuç int olur. k bu yüzden 'C' harfini değil 67 sayısını tutar. Sonucu harf olarak saklamak için açık bir (char) dönüşümü gerekir. Dönüşüm yazılmazsa satır derlenmez:
char c = 'A';
char d = c + 1;error: incompatible types: possible lossy conversion from int to char
char d = c + 1;
^
1 errorİkinci satırdaki c++ ise sorunsuz derlenir; ++ operatörü değişkenin kendi tipini korur. Son satırda yazdırma, tipe göre yapılır: char değer harf olarak (B, C), int değer sayı olarak (67) görünür.
char aritmetiğinin pratik kullanımları vardır:
System.out.println((int) 'A' + " " + (int) 'a' + " " + (int) '0' + " " + (int) ' ');
System.out.println('a' - 'A');
System.out.println((char) ('a' - 32));
System.out.println('7' - '0');
System.out.println('A' + 'B');
System.out.println("" + 'A' + 'B');
System.out.println((int) 'ç' + " " + (int) 'ğ' + " " + (int) 'İ' + " " + (int) 'ı' + " " + (int) 'ş');
char c = 'A';
c += 2;
System.out.println(c);
char h = 66;
System.out.println(h);65 97 48 32
32
A
7
131
AB
231 287 304 305 351
C
BKüçük ve büyük harf arasındaki fark İngiliz alfabesinde sabittir: 'a' − 'A' = 97 − 65 = 32. Küçük bir harften 32 çıkarılıp sonuç char'a çevrilince büyük harf elde edilir. Bir rakam karakterinden '0' çıkarılınca rakamın sayısal değeri bulunur: '7' − '0' = 55 − 48 = 7. Bu yöntem, metin olarak okunan bir sayının basamaklarını tek tek işlemek için kullanılır. Türkçe harflerde bu kısa yol işe yaramaz: 'ı' (305) ile 'I' (73) arasındaki fark 32 değildir. Türkçe metinlerde büyük-küçük harf dönüşümü için Java'nın dile duyarlı hazır metotları tercih edilir.
Çıktının beşinci ve altıncı satırları bir tuzağı gösterir. 'A' + 'B' ifadesinde iki char toplanır; ikisi de int'e çevrilir ve sonuç 65 + 66 = 131 sayısıdır, “AB” değil. Harfleri yan yana yazmak için işlemi bir dizgiyle başlatmak gerekir: "" + 'A' + 'B' “AB” verir. Kural basittir: + işleminin işlenenlerinden biri dizgiyse işlem toplama değil birleştirmedir. Son iki satır ise char'ın tip korumasını gösterir. c += 2 'A'yı 'C'ye ilerletir; bileşik atama sonucu kendiliğinden char'a çevirir. char h = 66; satırı da derlenir, çünkü 66 sabiti char aralığındadır ve derleyici bu dönüşümü güvenli bulur; h, 'B' harfini tutar.
6.3Tip dönüşümü
Bir programda farklı tipler sık sık karşılaşır. Bir öğrenci sayısı (int) bir ortalamayla (double) çarpılır, bir harfin koduna (char) bir sayı (int) eklenir, bir double sonuç int bir kutuya yazılmak istenir. Her karşılaşmada Java'nın bir değeri bir tipten öbürüne çevirmesi gerekir. Bu işleme tip dönüşümü (type conversion) denir. Bazı dönüşümleri Java kendiliğinden yapar, bazıları ise programcının açıkça istemesini gerektirir. Ayrımın ölçütü tektir: dönüşümde bilgi kaybı riski var mı?
6.3.1Atama uyumluluğu: genişletme ve daraltma
Genişletme (widening conversion), bir değeri daha geniş aralıklı bir tipe çevirmektir (örneğin int'ten double'a). Java genişletmeyi kendiliğinden, yani örtük olarak yapar. Daraltma (narrowing conversion) ise daha dar aralıklı bir tipe çevirmektir (örneğin double'dan int'e). Daraltma bilgi kaybettirebileceği için açık dönüşüm (cast) ister: hedef tip parantez içinde değerin önüne yazılır, (int) 9.8.
Genişletme güvenlidir, çünkü dar tipin her değeri geniş tipte de vardır. 7 sayısı int'te de double'da da gösterilebilir; double'a çevrilince 7.0 olur ve hiçbir şey kaybolmaz. Aşağıdaki atamaların hepsi derlenir:
int adet = 7;
double genis = adet;
long uzun = adet;
float f = 10;
double d = 'A';
long l = 5;
System.out.println(genis + " " + uzun + " " + f + " " + d + " " + l);
long buyuk = 123456789123L;
float ff = buyuk;
System.out.println(ff);
int i2 = 123456789;
float f2 = i2;
System.out.println(f2 + " " + (int) f2);7.0 7 10.0 65.0 5
1.2345679E11
1.2345679E8 123456792İlk satırdaki değerler beklenen dönüşümleri gösterir: int 7 double'da 7.0, long'da 7 olur; 10 float'ta 10.0; 'A' double'da 65.0 olur. Zincirin bir halkasında ise ince bir kayıp vardır. long'dan float'a ve int'ten float'a geçiş “genişletme” sayılır, çünkü aralık genişler. Ama float yalnızca yaklaşık 7 anlamlı basamak tutar. 123.456.789 sayısı float'a çevrilip geri alınınca 123.456.792 çıkar; son basamaklar yuvarlanmıştır. Java bu dönüşümde uyarı vermez. Büyük tam sayılarla çalışırken float yerine double seçmek bu sorunu büyük ölçüde ortadan kaldırır.
Daraltmada ise bilgi kaybı olağan bir durumdur. double 9.8 bir int kutusuna sığmaz; ondalık kısmın atılması gerekir. Java bu kararı programcıya bırakır ve açık dönüşüm yazılmadıkça atamayı reddeder:
double oran = 9.8;
int dar = oran;error: incompatible types: possible lossy conversion from double to int
int dar = oran;
^
1 errorİletideki “possible lossy conversion” ifadesi “olası kayıplı dönüşüm” demektir. Derleyici kaybın gerçekten olup olmayacağını bilmez; yalnızca olabileceğini bildirir. int dar = (int) oran; yazıldığında programcı kaybı kabul ettiğini açıkça söylemiş olur ve satır derlenir. Aynı kural sabitler için de geçerlidir: int x = 5L; satırı, 5 sayısı int'e rahatça sığdığı hâlde “possible lossy conversion from long to int” verir, çünkü sabitin tipi long'dur.
(int) ile 9'a çevrilir: .8 atılır, yuvarlama yapılmaz.Dönüşüm, kaynak değişkeni değiştirmez. (int) oran ifadesi, oran kutusundaki 9.8'i okuyup 9 değerini üretir; oran hâlâ 9.8'dir. Dönüşüm yalnızca üretilen sonucu etkiler. Bir dönüşümü değişkenin tipini kalıcı olarak değiştirmek sanmak yaygın bir yanılgıdır; Java'da bir değişkenin tipi bildirildiği anda belirlenir ve bir daha değişmez.
6.3.2Tip dönüşümü adım adım
Sunumdaki örnek, genişletmeyi, daraltmayı ve iki bölme biçimini tek programda gösterir:
int adet = 7;
double genis = adet; // 7.0
double oran = 9.8;
int dar = (int) oran; // 9
int bolum = 5 / 2; // 2
double dogru = 5.0 / 2; // 2.5| Satır | Deyim | Yeni kutu | Değer | Ne oldu? |
|---|---|---|---|---|
| 1 | int adet = 7; | adet (int) | 7 | 32 bitlik int kutusu |
| 2 | double genis = adet; | genis (double) | 7.0 | genişletme: otomatik, kayıpsız |
| 3 | double oran = 9.8; | oran (double) | 9.8 | 64 bitlik double kutusu |
| 4 | int dar = (int) oran; | dar (int) | 9 | daraltma: kesir atıldı; oran hâlâ 9.8 |
| 5 | int bolum = 5 / 2; | bolum (int) | 2 | int / int: tam sayı bölmesi |
| 6 | double dogru = 5.0 / 2; | dogru (double) | 2.5 | double / int: 2, 2.0'a genişler |
dogru kutusuna yazılır.Dördüncü satırdaki dönüşüm yuvarlama değil kesmedir (truncation): 9.8 en yakın tam sayı olan 10'a değil, 9'a çevrilir. Ondalık kısım ne olursa olsun atılır; 9.99 da 9 olur. Beşinci ve altıncı satırlar ise bir sonraki kesimin konusunu önceden gösterir: aynı 5 ve 2 sayıları, yazılış biçimlerine göre 2 ya da 2.5 sonucunu verir.
6.3.3Dönüşümlerde taşma ve kesme
Açık dönüşüm, derleyiciye “kaybı kabul ediyorum” demektir. Kaybın ne kadar olacağı ise dönüşümün türüne bağlıdır. Sunumdaki tablo, sık karşılaşılan durumları bir arada verir. Değerler JDK 21'de çalıştırılarak doğrulanmıştır.
| İfade | Sonuç | Neden |
|---|---|---|
(byte) 200 | -56 | yalnız alt 8 bit kalır: 200 − 256 |
(int) 3000000000L | -1294967296 | long → int: üst bitler atılır |
(int) 3.9 | 3 | ondalık kısım kesilir, yuvarlanmaz |
(int) -2.7 | -2 | sıfıra doğru kesilir |
(int) 3.9e10 | 2147483647 | double → int: int sınırında sıkışır |
Math.round(3.9) | 4 | en yakın tam sayı (long döner) |
Math.round(-2.7) | -3 | en yakın tam sayı |
Math.round(3.9e10) | 39000000000 | long olduğu için sığar |
Tablodaki satırlar üç ayrı davranışı gösterir.
Tam sayıdan daha dar tam sayıya. Dönüşüm, kaynak sayının yalnızca hedef tipe sığan alt bitlerini tutar ve üst bitleri atar. 200'ün ikili gösterimi 1100 1000'dir. Bu sekiz bit byte'a aynen kopyalanır. İşaret biti 1 olduğu için değer 200 − 256 = −56 olarak okunur. 3.000.000.000 için hesap int boyutunda yapılır: 3.000.000.000 − 232 = 3.000.000.000 − 4.294.967.296 = −1.294.967.296. Taşmadaki sarma kuralıyla aynı kuraldır. Birkaç örnek daha: (byte) 130 −126, (byte) 256 0, (byte) -129 127, (short) 40000 −25536 verir.
Kayan noktalıdan tam sayıya. Ondalık kısım atılır ve sayı sıfıra doğru kesilir: pozitif sayılar aşağı, negatif sayılar yukarı gider. 3.9 → 3 ve −2.7 → −2 bu yüzdendir. −2.7'nin −3'e değil −2'ye gitmesi şaşırtıcı gelebilir; −2, −3'ten daha büyüktür ve sıfıra daha yakındır. Sayı hedef tipin aralığını aşıyorsa bitler kesilmez; sonuç aralığın ucunda sıkışır. 3,9 × 1010 int'e sığmaz ve Integer.MAX_VALUE olan 2147483647 çıkar. Aynı biçimde (int) -3.9e10 en küçük int'i, (int) Double.NaN ise 0 verir.
Yuvarlama. Kesme yerine en yakın tam sayı isteniyorsa Math.round kullanılır. 3.9 → 4 ve −2.7 → −3 olur. Math.round, double alınca long döndürür; bu yüzden 3,9 × 1010 sorunsuz sığar. Sonuç int bir kutuya yazılacaksa ikinci bir dönüşüm gerekir: int ri = (int) Math.round(x);. Dönüşüm yazılmazsa int r = Math.round(2.7); satırı “possible lossy conversion from long to int” hatası verir.
(int) dönüşümünün sayıyı yuvarladığını sanmak. (int) 9.8 10 değil 9'dur; (int) -2.7 −3 değil −2'dir. Yuvarlama için Math.round, int sonuç için (int) Math.round(x) yazılır. Daraltma dönüşümü hata vermez; veri sessizce kaybolur.
6.3.4Tam sayı bölmesi: 5 / 2 ile 5.0 / 2
Java'da bölme operatörü / iki farklı iş yapar ve hangisini yapacağına işlenenlerin tipine bakarak karar verir. İki işlenen de tam sayıysa tam sayı bölmesi (integer division) yapılır: sonuç bölümün tam kısmıdır, kalan atılır. İşlenenlerden en az biri kayan noktalıysa öbürü de kayan noktalıya yükseltilir ve olağan bölme yapılır.
System.out.println(5 / 2);
System.out.println(5.0 / 2);
System.out.println(5 / 2.0);
System.out.println((double) 5 / 2);
System.out.println((double) (5 / 2));
System.out.println(5 % 2);2
2.5
2.5
2.5
2.0
1| İfade | İşlenen tipleri | Yapılan işlem | Çıktı |
|---|---|---|---|
5 / 2 | int, int | tam sayı bölmesi | 2 |
5.0 / 2 | double, int | 2 → 2.0; 5.0 / 2.0 | 2.5 |
5 / 2.0 | int, double | 5 → 5.0; 5.0 / 2.0 | 2.5 |
(double) 5 / 2 | double, int | önce dönüşüm: 5.0; sonra 5.0 / 2.0 | 2.5 |
(double) (5 / 2) | int, int | önce 5 / 2 = 2; sonra 2 → 2.0 | 2.0 |
5 % 2 | int, int | kalan: 5 = 2 · 2 + 1 | 1 |
(double) (5 / 2) ifadesinde önce parantez içi int olarak hesaplanır (2), dönüşüm ancak sonra gelir (2.0).Dördüncü ve beşinci satırlar arasındaki fark yalnızca bir çift paranteze dayanır ama sonuç değişir. Tip dönüşümü bölmeden önce gelen bir tekli operatördür. (double) 5 / 2 ifadesinde önce 5 double'a çevrilir, sonra bölme kayan noktalı yapılır. (double) (5 / 2) ifadesinde ise parantez önce hesaplanır; bölme iki int arasında yapılır ve 0,5 kaybolur. Sonradan yapılan dönüşüm 2'yi 2.0 yapar ama kaybolan kesri geri getirmez.
70, 85 ve 90 notlarının ortalaması dört farklı ifadeyle hesaplanıyor. Hangisi doğru sonucu verir?
int n1 = 70, n2 = 85, n3 = 90;
System.out.println((n1 + n2 + n3) / 3);
System.out.println((n1 + n2 + n3) / 3.0);
System.out.println((double) (n1 + n2 + n3) / 3);
System.out.printf("%.2f%n", (n1 + n2 + n3) / 3.0);81
81.66666666666667
81.66666666666667
81.67Toplam 70 + 85 + 90 = 245'tir. İlk satırda 245 ve 3 int olduğu için tam sayı bölmesi yapılır: 245 / 3 = 81, kalan 2 atılır. İkinci satırda 3.0 double'dır; 245 de double'a yükselir ve 245 / 3 = 81,666… bulunur. Üçüncü satırda dönüşüm bölmeden önce yapılır ve sonuç yine doğrudur. Son satır sonucu iki ondalık basamakla yazar: 81.67. Doğru ortalama için bölmenin en az bir işleneni kayan noktalı olmalıdır.
Tam sayı bölmesi her zaman bir hata değildir; çoğu zaman tam da istenen işlemdir. 17 elma 5'erli paketlere konursa 17 / 5 = 3 tam paket çıkar. 3725 saniyede 3725 / 3600 = 1 tam saat vardır. Bu tür hesaplarda tam sayı bölmesi, kalanı veren % operatörüyle birlikte kullanılır. Hata, ortalama ya da oran gibi kesirli bir sonuç beklenirken iki tam sayının bölünmesidir.
6.3.5Tip yükseltme
Bölmede görülen kural, bütün ikili aritmetik operatörlerde geçerlidir.
Tip yükseltme (type promotion), karma tipli bir ifadede dar tipteki işlenenin, işlem yapılmadan önce geniş tipe otomatik çevrilmesidir. Sonuç geniş tiptedir: int + double → double, int * long → long. Ayrıca byte, short ve char işlenenler her aritmetik işlemde önce int'e yükseltilir.
Kuralın ikinci yarısı şaşırtıcıdır: iki byte'ın toplamı bile int'tir. Java bu küçük tiplerle doğrudan aritmetik yapmaz; işlemciler 32 bitlik işlemlerde daha verimli çalışır ve ara sonucun taşma riski azalır. Bunun sonucu olarak aşağıdaki satırlar derlenmez:
short a = 1, b = 2;
short c = a + b;error: incompatible types: possible lossy conversion from int to short
short c = a + b;
^
1 errora + b int'tir ve int'ten short'a daraltma açık dönüşüm ister: short c = (short) (a + b);. Aynı nedenle byte b = 10; b = b + 5; de derlenmez. Bu davranış, char aritmetiğinde görülen c + 1 ifadesinin neden int olduğunu da açıklar.
Bir ifadede birden çok işlem varsa yükseltme her işlem için ayrı ayrı yapılır. 1 / 2 * 10.0 ifadesinde önce 1 / 2 iki int arasında hesaplanır ve 0 olur; ancak ondan sonra 0, 10.0 ile çarpılırken double'a yükselir. Sonuç 5.0 değil 0.0'dır. Bir double işlenenin ifadede bir yerde bulunması, ifadenin her yerini kayan noktalı yapmaz; yükseltme yalnızca o işlenenin katıldığı işlemde gerçekleşir.
6.4Operatörler
Operatör (operator), bir ya da daha fazla değer üzerinde işlem yapan simgedir: +, *, <, &&. Operatörün üzerinde işlem yaptığı değerlere işlenen (operand) denir. Operatörlerin, değişkenlerin ve sabitlerin bir değer üreten birleşimine ifade (expression) denir: a + b * 2.
Operatörler, aldıkları işlenen sayısına göre adlandırılır. -x, !aktif, i++ gibi tek işlenenli operatörler tekli, a + b gibi iki işlenenli olanlar ikili operatörlerdir. Her ifadenin bir değeri ve bir tipi vardır. 7 / 2 ifadesinin değeri 3, tipi int'tir; 7 > 2 ifadesinin değeri true, tipi boolean'dır.
6.4.1Aritmetik operatörler ve kalan
| Operatör | Anlam | Örnek |
|---|---|---|
+ - | Toplama / çıkarma | a + b |
* / | Çarpma / bölme | a * b |
% | Kalan | sayi % 2 |
++ -- | Bir artır / azalt | i++ |
+= -= | Bileşik atama | toplam += deger |
Toplama, çıkarma ve çarpma matematikteki gibidir; tek fark, sonucun tipin aralığına sığmak zorunda olmasıdır. Bölme operatörünün iki yüzü tam sayı bölmesi kesiminde görüldü. Bu kesimin asıl konusu % operatörüdür.
Kalan (remainder) operatörü %, tam sayı bölmesinden artan değeri verir. a ve b tam sayıları için a = (a / b) · b + (a % b) eşitliği her zaman sağlanır. Bölmede bölünen sayıya bölünen, bölme sonucunun tam kısmına bölüm denir.
Şekil 6.18, 17 nesnenin beşerli gruplanmasını gösterir: / kaç tam grup oluştuğunu, % kaç nesne arttığını söyler. Eşitlik kontrol edilebilir: 3 · 5 + 2 = 17. Kalan operatörü, ilk bakışta sanıldığından çok daha sık kullanılır:
- Çift-tek denetimi.
sayi % 2 == 0ise sayı çifttir. Genel olaraka % b == 0, a'nın b'ye tam bölündüğünü gösterir. - Basamak ayırma.
sayi % 10son basamağı verir;sayi / 10son basamağı atar. 1234 için 1234 % 10 = 4 ve 1234 / 10 = 123'tür. - Birim dönüşümü. Saniyeyi dakika ve saniyeye, kuruşu lira ve kuruşa ayırmak: 1575 kuruş, 1575 / 100 = 15 lira ve 1575 % 100 = 75 kuruştur.
- Döngüsel sayma. Haftanın günü, saat kadranı gibi başa dönen değerler: bugün çarşambaysa (2) 10 gün sonrası (2 + 10) % 7 = 5, yani cumartesidir (pazartesi 0 kabul edilirse).
Negatif sayılarda kalanın işareti bir kurala bağlıdır: Java'da kalan her zaman bölünenin işaretini alır. Bu kural, tam sayı bölmesinin sıfıra doğru kesmesinden doğar. Aşağıdaki programın ilk dört satırı negatif işlenenleri, sonraki satırları ise bu kesimde ve sıfıra bölme kesiminde geçen öteki durumları sınar:
System.out.println(7 / 2 + " " + 7 % 2);
System.out.println(-7 / 2 + " " + -7 % 2);
System.out.println(7 / -2 + " " + 7 % -2);
System.out.println(-7 / 3 + " " + -7 % 3 + " " + Math.floorMod(-7, 3) + " " + Math.floorDiv(-7, 3));
System.out.println(17 / 5 + " " + 17 % 5);
System.out.println(5.5 % 2);
System.out.println(1 / 2 * 10 + " " + 10 * 1 / 2);
System.out.println(1.0 / 0 + " " + -1.0 / 0 + " " + 0.0 / 0);
double nan = 0.0 / 0;
System.out.println((nan == nan) + " " + (nan != nan) + " " + Double.isNaN(nan));
System.out.println(Math.sqrt(-1) + " " + (1.0 / 0 - 1.0 / 0) + " " + (1.0 / 0 + 5));
System.out.println(1 / 0.0 > 1e308);3 1
-3 -1
-3 1
-2 -1 2 -3
3 2
1.5
0 5
Infinity -Infinity NaN
false true true
NaN NaN Infinity
trueÇıktının ilk dört satırı bu kuralı gösterir. −7 / 2 = −3'tür (−3,5 sıfıra doğru kesilir), kalan ise −7 − (−3 · 2) = −1'dir. 7 / −2 = −3 ve 7 % −2 = 1'dir; bölünen 7 pozitif olduğu için kalan da pozitiftir. −7 % 3 = −1'dir; matematikte alışılan “0 ile 2 arasındaki kalan” 2'dir. Bu değer gerekiyorsa Math.floorMod(-7, 3) kullanılır. Bu yüzden bir sayının tek olup olmadığı sayi % 2 == 1 ile değil sayi % 2 != 0 ile denetlenir; −7 için ilki yanlış sonuç verir.
Beşinci satır şekildeki 17 nesne örneğidir. % operatörü kayan noktalı sayılarla da çalışır: 5,5 = 2 · 2 + 1,5 olduğu için 5.5 % 2 1.5 verir. Çıktının yedinci satırı ise işlem sırasının tam sayı bölmesindeki etkisini gösterir: 1 / 2 * 10 önce 1 / 2 = 0 hesaplar ve 0 bulur; 10 * 1 / 2 önce 10 · 1 = 10 hesaplar ve 5 bulur. Matematikte iki ifade eşittir; Java'da tam sayı bölmesi yüzünden değildir.
6.4.2Sıfıra bölme ve özel değerler
Matematikte sıfıra bölme tanımsızdır. Java bu durumu tam sayılarda ve kayan noktalı sayılarda farklı ele alır.
| İfade | Sonuç | Açıklama |
|---|---|---|
1 / 0 | ArithmeticException: / by zero | tam sayı bölmesinde sıfıra bölme çalışma zamanı hatasıdır |
1.0 / 0 | Infinity | ondalıkta hata yok: sonsuz |
-1.0 / 0 | -Infinity | eksi sonsuz |
0.0 / 0 | NaN | Not a Number: tanımsız sonuç |
NaN == NaN | false | NaN kendine bile eşit değildir |
NaN != NaN | true | NaN denetimi için Double.isNaN(x) |
-7 % 3 | -1 | kalanın işareti bölünenin işaretidir |
5.5 % 2 | 1.5 | % kayan noktalı sayılarda da çalışır |
Tam sayılarda sıfıra bölme programı durdurur. Derleme başarılıdır, çünkü derleyici çoğu zaman bölenin sıfır olacağını önceden bilemez. Hata program çalışırken ortaya çıkar:
int a = 1;
int b = 0;
System.out.println("önce");
System.out.println(a / b);
System.out.println("sonra");önce
Exception in thread "main" java.lang.ArithmeticException: / by zero“önce” yazılır, bölme satırında JVM bir ArithmeticException fırlatır ve program orada biter; “sonra” hiç yazılmaz. Aynı hata kalan işleminde de görülür: 5 % 0 da “/ by zero” iletisiyle durur.
Kayan noktalı sayılarda ise sıfıra bölme hata vermez. Kayan nokta standardı (IEEE 754) bu durumlar için özel değerler tanımlar. Pozitif bir sayının sıfıra bölümü Infinity (sonsuz), negatifinki -Infinity olur. 0,0 / 0 gibi anlamı olmayan işlemler NaN (Not a Number, “sayı değil”) üretir. Math.sqrt(-1) ve ∞ − ∞ da NaN verir. Bu değerler programı durdurmaz, sonraki işlemlere taşınır: ∞ + 5 = ∞, NaN'lı her aritmetik işlem yine NaN'dır. Bir hesabın en sonunda beklenmedik biçimde NaN çıkması, genellikle çok önceki bir satırda sıfıra bölündüğünü gösterir.
NaN'ın en tuhaf özelliği kendine eşit olmamasıdır: x == x, x NaN ise false verir. Bu yüzden NaN denetimi x == Double.NaN ile yapılamaz; her zaman false döner. Doğru yol Double.isNaN(x) metodudur.
Kayan noktalı bölmede bölenin sıfır olabileceğini unutmak. Tam sayı bölmesi bu hatayı istisnayla hemen gösterir; kayan noktalı bölme ise Infinity ya da NaN üretip sessizce devam eder. Bölen sıfır olabiliyorsa bölmeden önce denetlenir.
6.4.3Artırma ve azaltma
Bir değişkeni 1 artırmak ya da 1 azaltmak o kadar sık yapılır ki Java bunun için ayrı operatörler sunar: ++ ve --. Tek başına bir deyim olarak yazıldıklarında i++;, i = i + 1; ile aynıdır. İnce bir fark, operatör bir ifadenin içinde kullanıldığında ortaya çıkar.
Sonek (postfix) biçiminde (i++) ifadenin değeri değişkenin eski değeridir; değişken bu değer kullanıldıktan sonra artırılır. Önek (prefix) biçiminde (++i) önce değişken artırılır; ifadenin değeri yeni değerdir.
int i = 5;
int a = i++; // önce kullan, sonra artır
int b = ++i; // önce artır, sonra kullan
i--;
System.out.println(i + " " + a + " " + b);6 5 7| Satır | Deyim | İfadenin değeri | i | a | b |
|---|---|---|---|---|---|
| 1 | int i = 5; | – | 5 | – | – |
| 2 | int a = i++; | i++ → 5 (eski değer) | 6 | 5 | – |
| 3 | int b = ++i; | ++i → 7 (yeni değer) | 7 | 5 | 7 |
| 4 | i--; | tek başına deyim | 6 | 5 | 7 |
| 5 | System.out.println(…); | çıktı: 6 5 7 | 6 | 5 | 7 |
i++: ifadenin değeri 5'tir, a = 5 olur, sonra i 6'ya çıkar. Önek ++i: önce i = 7 olur, ifadenin değeri yeni değer 7'dir ve b = 7 olur.İkinci satırda i++ önce eski değer olan 5'i üretir; bu değer a'ya yazılır. Artırma da aynı satırda gerçekleşir ve i 6 olur. Üçüncü satırda ++i önce i'yi 7 yapar, sonra bu yeni değeri üretir; b = 7 olur. Dördüncü satırda i-- tek başına bir deyimdir. Değeri hiçbir yerde kullanılmadığı için önek ve sonek arasındaki fark burada önemsizdir: --i; yazılsaydı da i 6 olurdu.
Önek ve sonek farkı tuzaklara açıktır. j = j++; satırı j'yi artırmaz: sağ taraf eski değer olan 5'i üretir, j bir an 6 olur, sonra atama 5'i yeniden yazar. Bir ifadede aynı değişkenin birden çok kez artırılması da okunması zor kod üretir. k++ + k++ ifadesi k = 1 için 1 + 2 = 3 verir ve k'yi 3 yapar. Sonuçlar Java'da kesin kurallarla belirlidir ama okuyan herkesi düşündürür. ++ ve --'yi yalnızca tek başına bir deyim olarak kullanmak kodu açık tutar.
6.4.4Bileşik atama ve örtük daraltma
Bileşik atama operatörleri, +=, -=, *=, /= ve %=, yalnızca kısa bir yazım değildir. Sonucu soldaki değişkenin tipine kendiliğinden daraltırlar. Java dil tanımı, x op= y deyimini x = (T) (x op y) diye tanımlar; buradaki T, x'in tipidir.
byte b = 10;
b += 5;
int i = 5;
i += 3.7;
// b = b + 5; // derlenmez: int → byte
System.out.println(b + " " + i);15 8| Satır | Deyim | Derleyicinin gördüğü | b | i |
|---|---|---|---|---|
| 1 | byte b = 10; | 10 | – | |
| 2 | b += 5; | b = (byte) (b + 5): 10 + 5 = 15 (int) → 15 (byte) | 15 | – |
| 3 | int i = 5; | 15 | 5 | |
| 4 | i += 3.7; | i = (int) (i + 3.7): 5 + 3.7 = 8.7 (double) → 8 | 15 | 8 |
| 5 | // b = b + 5; | yorum: derlenmeyeceği için devre dışı | 15 | 8 |
| 6 | System.out.println(b + " " + i); | çıktı: 15 8 | 15 | 8 |
b += 5, int toplamı byte'a daraltır. i += 3.7, double toplamı int'e daraltır ve kesri atar. Aynı işi b = b + 5; biçiminde yazmak derleme hatası verir.Beşinci satırdaki yorum önemlidir. b = b + 5; satırında b + 5 int'tir ve int'ten byte'a atama açık dönüşüm ister; satır derlenmez. Aynı işi yapan b += 5; ise derlenir, çünkü dönüşüm bileşik atamanın tanımında gizlidir. Bu kolaylık bir tehlike de taşır: dönüşüm sessizdir. i += 3.7 sonucu 8.7'yi uyarı vermeden 8'e indirir. byte sınırı aşıldığında taşma da sessizce olur:
int i = 5;
i *= 3 + 7;
System.out.println(i);
int x = 17;
x %= 5;
System.out.println(x);
int y = 20;
y /= 3;
System.out.println(y);
double z = 20;
z /= 3;
System.out.println(z);
byte b = 120;
b += 10;
System.out.println(b);
short s = 1;
s++;
System.out.println(s);
char c = 'a';
c -= 32;
System.out.println(c);50
2
6
6.666666666666667
-126
2
AÇıktının satırları sırasıyla okunur. i *= 3 + 7 deyiminde sağ taraf bütünüyle önce hesaplanır: 5 · (3 + 7) = 50. x %= 5, 17'yi kalan olan 2'ye çevirir. y /= 3 int'te tam sayı bölmesidir: 20 / 3 = 6; double z ile aynı işlem 6.666666666666667 verir. b += 10, 120 + 10 = 130'u byte'a sığdırmak için 256 çıkarır: −126. Son iki satır, ++ ve -= operatörlerinin de değişkenin tipini koruduğunu gösterir: short s 2 olur, char c 'a'dan 32 çıkarılarak 'A' olur.
x += y ile x = x + y deyimlerini her durumda aynı sanmak. İki fark vardır. Birincisi, bileşik atama örtük daraltma yapar; x = x + y ise tipler uymadığında derlenmez. İkincisi, bileşik atamada sağ taraf bir bütündür: x *= a + b, x = x * (a + b) demektir, x = x * a + b değil.
6.4.5Karşılaştırma ve mantık operatörleri
Programlar karar verir: not 60'tan büyükse geçti yaz, yaş 18'den küçükse uyar. Bu kararların temelinde, sonucu true ya da false olan ifadeler vardır.
| Operatör | Anlam | Örnek |
|---|---|---|
== != | Eşit / farklı | a == b |
< <= > >= | Sıra karşılaştırması | not >= 60 |
&& | İki koşul da doğru | yas >= 18 && belgeVar |
|| | En az biri doğru | haftaSonu || tatil |
! | Mantıksal değil | !aktif |
Karşılaştırma operatörleri (comparison operator) iki değeri karşılaştırır ve her zaman boolean bir sonuç üretir. Eşitlik iki eşittir işaretiyle yazılır: ==. Tek eşittir (=) atamadır; ikisini karıştırmak en sık yapılan hatalardandır. != “eşit değil” demektir. Matematikteki ≤ ve ≥ işaretleri <= ve >= diye iki karakterle yazılır.
Mantık operatörleri (logical operator) boolean değerleri birleştirir. && (ve) iki koşul da doğruysa doğrudur. || (veya) en az biri doğruysa doğrudur. ! (değil) tek işlenenlidir ve değeri tersine çevirir. Dört olası durumun tamamı bir doğruluk tablosunda gösterilir:
| p | q | p && q | p || q | !p |
|---|---|---|---|---|
| false | false | false | false | true |
| false | true | false | true | true |
| true | false | false | true | false |
| true | true | true | true | false |
int yas = 20;
boolean belgeVar = true;
System.out.println(yas >= 18 && belgeVar);
System.out.println(!belgeVar);
System.out.println(yas > 65 || yas < 12);
System.out.println(5 > 3 == true);
int not = 75;
System.out.println(not >= 60 && not <= 100);
System.out.println(true ^ true);true
false
false
true
true
falseYaş 20 ve belge varken yas >= 18 && belgeVar doğrudur. yas > 65 || yas < 12 ise iki koşul da yanlış olduğu için yanlıştır. Bir değerin bir aralıkta olup olmadığı iki karşılaştırmanın && ile birleşimiyle yazılır: not >= 60 && not <= 100. (not Java'da anahtar kelime değildir; değişken adı olarak kullanılabilir.) Son satırdaki ^ “ya o ya bu” (özel veya) operatörüdür; iki işlenen farklıysa doğrudur.
Matematikte alışılan 0 < x < 10 yazılışı Java'da çalışmaz:
error: bad operand types for binary operator '<'
System.out.println(0 < x < 10);
^
first type: boolean
second type: int
1 error0 < x önce hesaplanır ve boolean bir değer üretir. Ardından bu boolean değer 10 ile karşılaştırılmak istenir; boolean ile int arasında < tanımlı değildir. Doğru yazılış 0 < x && x < 10'dur.
6.4.6İşlem önceliği ve ifade ağacı
2 + 3 * 4 ifadesinin değeri 20 mi, 14 mü? Matematikte çarpmanın toplamadan önce yapıldığı bilinir ve cevap 14'tür. Java da aynı kuralı izler ve bunu bütün operatörlere genişletir.
İşlem önceliği (operator precedence), bir ifadede farklı operatörlerin hangi sırayla uygulanacağını belirleyen kurallar dizisidir. Aynı öncelikteki operatörler soldan sağa uygulanır; atama operatörleri ise sağdan sola uygulanır.
!, ++, --, tip dönüşümü), çarpımsal, toplamsal, sıra karşılaştırması, eşitlik, &&, || ve en son atama.Merdiven, sunumdaki dört kurala karşılık gelir. Parantez içi her zaman önce değerlendirilir; tekli ! ve tip dönüşümü de erken uygulanır. Çarpma, bölme ve kalan; toplama ve çıkarmadan önce gelir. Karşılaştırmalar aritmetikten sonra, && ve || ise karşılaştırmalardan sonra değerlendirilir; &&, ||'den önce gelir. Aynı düzeydeki operatörlerde soldan sağa gidilir. Bu yüzden 10 - 4 - 3 ifadesi (10 − 4) − 3 = 3'tür, 10 − (4 − 3) = 9 değil. 100 / 10 / 5 de (100 / 10) / 5 = 2'dir. Atama ise sağdan sola işler: a = b = 4; önce b'ye 4'ü yazar, sonra bu değeri a'ya yazar.
Derleyici bir ifadeyi okurken onu, önceliğe göre bir ifade ağacına (expression tree) dönüştürür. Ağacın yapraklarında sabitler ve değişkenler, iç düğümlerinde operatörler bulunur. En alttaki işlemler önce, kökteki işlem en son hesaplanır. Sunumdaki ifade bu süreci adım adım gösterir:
2 + 3 * 4 > 10 && 7 % 2 == 12 + 3 * 4 > 10 && 7 % 2 == 1 ifadesinin ağacı. Kökte en düşük öncelikli operatör && bulunur; onun iki alt ağacı iki karşılaştırmadır.| Adım | Hesaplanan işlem | İfadenin yeni hâli |
|---|---|---|
| 0 | – | 2 + 3 * 4 > 10 && 7 % 2 == 1 |
| 1 | 3 * 4 = 12 (*, +'dan önce) | 2 + 12 > 10 && 7 % 2 == 1 |
| 2 | 2 + 12 = 14 | 14 > 10 && 7 % 2 == 1 |
| 3 | 14 > 10 → true | true && 7 % 2 == 1 |
| 4 | 7 % 2 = 1 | true && 1 == 1 |
| 5 | 1 == 1 → true | true && true |
| 6 | true && true → true | true |
&& sağ tarafı da hesaplar; son adımda kök true olur.Üçüncü adımda bir tip değişimi olur: 14 > 10 karşılaştırması iki int alır ama boolean üretir. Ağacın üst katlarında artık sayılar değil mantıksal değerler dolaşır. && operatörünün iki işleneni de boolean olmalıdır. Sol taraf true olduğu için sonuç henüz belli değildir; sağ taraf da hesaplanır. Sol taraf false olsaydı sağ tarafa hiç bakılmazdı.
System.out.println(2 + 3 * 4);
System.out.println((2 + 3) * 4);
System.out.println(10 - 4 - 3);
System.out.println(100 / 10 / 5);
System.out.println(2 + 3 * 4 > 10 && 7 % 2 == 1);
System.out.println(10 - 4 / 2 * 3 + 7 % 4);
System.out.println(-3 * 2 + 8 / 3 % 2);
int a, b;
a = b = 4;
System.out.println(a + " " + b);
boolean p = true, q = false, r = true;
System.out.println(p || q && !r);
System.out.println((p || q) && !r);14
20
3
2
true
7
-6
4 4
true
falseSon iki satır mantık operatörlerinin önceliğini gösterir. p || q && !r ifadesinde önce !r, sonra &&, en son || uygulanır: true || (false && false) = true. Parantezle sıra değiştirilince sonuç da değişir: (true || false) && false = false. -3 * 2 + 8 / 3 % 2 ifadesinde ise tekli eksi önce gelir, sonra soldan sağa *, / ve % uygulanır: −6 + (8 / 3) % 2 = −6 + 2 % 2 = −6 + 0 = −6.
Öncelik kurallarının hepsini ezberlemek gerekmez. Okuyanın aklında soru işareti bırakacak her yerde parantez kullanmak, hem hatayı önler hem de niyeti görünür kılar. (a + b) / 2 yazmak, a + b / 2'nin ne anlama geldiğini düşünmekten kolaydır.
6.4.7Karma ifade: öncelik ve tip birlikte
Gerçek ifadelerde öncelik ve tip kuralları birlikte çalışır. Sunumdaki örnek ikisinin etkileşimini gösterir:
int a = 7, b = 2;
double r = a / b * 2.0 + a % b;
System.out.println(r);7.0| Adım | İşlem | Tipler | İfadenin yeni hâli |
|---|---|---|---|
| 1 | a / b = 7 / 2 = 3 | int / int → int (tam sayı bölmesi) | 3 * 2.0 + a % b |
| 2 | 3 * 2.0 = 6.0 | int * double → double | 6.0 + a % b |
| 3 | a % b = 7 % 2 = 1 | int % int → int | 6.0 + 1 |
| 4 | 6.0 + 1 = 7.0 | double + int → double | 7.0 |
/, * ve % aynı öncelikte olduğu için soldan sağa işlenir; + hepsinden sonra gelir./, * ve % aynı öncelik düzeyindedir ve soldan sağa işlenir; + ise hepsinden sonra gelir. İlk işlem a / b'dir ve iki int olduğu için 3.5 değil 3 verir. İfadede 2.0 gibi bir double bulunması bu ilk işlemi etkilemez; 2.0 ancak ikinci işlemde devreye girer. Sonuç 7.0'dır. a / b yerine a / 2.0 yazılsaydı ilk işlem 3.5 verir ve sonuç 3,5 · 2,0 + 1 = 8,0 olurdu. Aynı sayılarla iki farklı cevap çıkması, sonucu operatörlerin sırasının ve işlenen tiplerinin birlikte belirlediğini gösterir.
6.4.8Kısa devre değerlendirme
&& ve || operatörlerinin işlerini hızlandıran ve programları güvenli kılan bir özelliği vardır.
Kısa devre değerlendirme (short-circuit evaluation), mantık ifadesinin sonucu sol işlenenden belli olduğunda sağ işlenenin hiç hesaplanmamasıdır. && için sol taraf false ise sonuç false'tur; || için sol taraf true ise sonuç true'dur. Bu durumlarda sağ taraf çalıştırılmaz.
int a = 10, b = 0;
if (b != 0 && a / b > 2) {
System.out.println("Oran büyük");
}
b = 2;
if (b != 0 && a / b > 2) {
System.out.println("Oran büyük");
}Oran büyükProgram aynı koşulu iki farklı b değeriyle sınar. Koşulun sol tarafı b != 0, sağ tarafı a / b > 2'dir. (if deyimi, koşul doğruysa süslü parantez içindeki satırları çalıştırır; yanlışsa atlar.)
| b | Sol: b != 0 | Sağ: a / b > 2 | Koşul | Çıktı |
|---|---|---|---|---|
| 0 | 0 != 0 → false | hesaplanmaz | false | yok (blok atlanır) |
| 2 | 2 != 0 → true | 10 / 2 = 5; 5 > 2 → true | true | Oran büyük |
&& ifadesinin sonucu bellidir; a / b > 2 hiç hesaplanmaz ve sıfıra bölme hatası oluşmaz.b = 0 iken sağ taraf çalıştırılsaydı a / b tam sayıyı sıfıra bölerdi ve program ArithmeticException ile çökerdi. Kısa devre bunu önler: sol taraf false olduğunda sonuç ne olursa olsun false'tur ve Java sağ tarafa bakmaz. Bu yüzden koşulların sırası önemlidir. Koşullar ters yazılırsa koruma ortadan kalkar:
int a = 10, b = 0;
if (a / b > 2 && b != 0) {
System.out.println("Oran büyük");
}Exception in thread "main" java.lang.ArithmeticException: / by zeroBurada önce a / b hesaplanır ve program, b != 0 denetimine sıra gelmeden durur. Koruyucu koşul her zaman sola, korunan işlem sağa yazılır. || için aynı fikir tersinden işler: b == 0 || a / b > 2 ifadesinde b = 0 ise sol taraf true'dur ve bölme yapılmaz.
int a = 10, b = 0;
if (b == 0 || a / b > 2) {
System.out.println("b sıfır ya da oran büyük");
}
int sayac = 0;
boolean s1 = false && (++sayac > 0);
boolean s2 = true || (++sayac > 0);
boolean s3 = false & (++sayac > 0);
System.out.println(sayac + " " + s1 + " " + s2 + " " + s3);b sıfır ya da oran büyük
1 false true falseİkinci kısımdaki sayaç, hangi sağ tarafların gerçekten çalıştığını sayar. false && (++sayac > 0) ve true || (++sayac > 0) ifadelerinde sağ taraf kısa devre yüzünden çalışmaz. Tek & ise kısa devre yapmayan “ve” operatörüdür; iki tarafı da her zaman hesaplar. Sayacın 1 olması, yalnızca bu üçüncü ifadede artırmanın gerçekleştiğini gösterir. Günlük programlamada koşullar için hep && ve || kullanılır.
6.4.9Dizgi birleştirme
+ operatörünün de iki yüzü vardır. İki işlenen sayıysa toplama yapar. İşlenenlerden en az biri bir dizgiyse (String) dizgi birleştirme (string concatenation) yapar: öteki işlenen metne çevrilir ve iki metin uç uca eklenir. Operatörün davranışını işlenenlerin tipi belirler.
System.out.println(2 + 3); // 5
System.out.println("2" + "3"); // 23
System.out.println("Sonuç: " + 2 + 3); // Sonuç: 23
System.out.println("Sonuç: " + (2 + 3)); // Sonuç: 5Dört satırın sonuçlarını açıklamak için iki kural yeterlidir. Birincisi, + soldan sağa işler. İkincisi, her adımda o adımdaki iki işlenene bakılır: ikisi de sayıysa toplama, biri dizgiyse birleştirme yapılır.
| İfade | 1. adım | 2. adım | Çıktı |
|---|---|---|---|
2 + 3 | int + int → toplama: 5 | – | 5 |
"2" + "3" | String + String → birleştirme: "23" | – | 23 |
"Sonuç: " + 2 + 3 | String + int → "Sonuç: 2" | String + int → "Sonuç: 23" | Sonuç: 23 |
"Sonuç: " + (2 + 3) | parantez: int + int → 5 | String + int → "Sonuç: 5" | Sonuç: 5 |
"Sonuç: " + 2 + 3 ifadesinde önce "Sonuç: " + 2 birleştirilir ve "Sonuç: 2" olur; sonra 3 de metne eklenir. Parantezli yazılışta önce 2 + 3 = 5 toplanır, sonra "Sonuç: 5" oluşur.Üçüncü satır en çok şaşırtan satırdır. Okuyan kişi 2 ile 3'ün toplanacağını bekler ama Java soldan sağa gider: ilk adımda “Sonuç: ” dizgisiyle 2 birleşir. Bu adımın sonucu bir dizgidir; ikinci adımda dizgiyle 3 birleşir. 2 + 3 hiç toplanmaz. Parantez toplamanın önce yapılmasını sağlar. Kural, işlemin başında da geçerlidir:
System.out.println(2 + 3);
System.out.println("2" + "3");
System.out.println("Sonuç: " + 2 + 3);
System.out.println("Sonuç: " + (2 + 3));
System.out.println(2 + 3 + " elma");
System.out.println("Sonuç: " + 2 * 3);
System.out.println("Sonuç: " + 7 / 2);
System.out.println('A' + 1 + "B");
System.out.println("A" + 'B' + 1);
System.out.println(1 + 'A');
System.out.println("x" + 1.5 + true);
String s = "Sonuç: ";
s += 5;
System.out.println(s);5
23
Sonuç: 23
Sonuç: 5
5 elma
Sonuç: 6
Sonuç: 3
66B
AB1
66
x1.5true
Sonuç: 52 + 3 + " elma": soldaki iki işlenen sayıdır ve önce toplanır; sonuç “5 elma”dır."Sonuç: " + 2 * 3ve"Sonuç: " + 7 / 2: * ve /, +'dan önceliklidir. Önce sayısal işlem yapılır (6 ve 3), sonra birleştirme.'A' + 1 + "B": ilk adımda char ile int toplanır (65 + 1 = 66), sonra “B” eklenir: “66B”."A" + 'B' + 1ise baştan dizgiyle başladığı için “AB1” verir."x" + 1.5 + true: double ve boolean değerler de metne çevrilir: “x1.5true”.s += 5: bileşik atama dizgilerde de çalışır ve metnin sonuna ekler.
Dizgilerle ilgili bir uyarı da karşılaştırmayla ilgilidir. İki dizginin içeriğinin aynı olup olmadığı == ile değil equals metoduyla denetlenir. ==, String gibi referans tiplerinde iki değişkenin aynı nesneyi gösterip göstermediğine bakar, içeriğe bakmaz:
String s1 = "Ada";
String s2 = new String("Ada");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));false
trues1 ve s2 aynı “Ada” metnini taşır ama bellekte iki ayrı nesnedir. == false, equals true verir.
6.5Girdi ve çıktı
Şimdiye kadarki programlar hep aynı sayılarla çalıştı; değerler kodun içine yazılmıştı. Bir sıcaklık dönüştürücüsü ya da not ortalaması hesaplayan bir program ise her çalıştığında farklı değerlerle çalışmalıdır. Bu değerler kullanıcıdan, çoğunlukla klavyeden alınır. Java'da klavyeden okumanın en kolay yolu Scanner sınıfıdır. Sonuçların düzgün hizalanmış, belirli sayıda ondalık basamakla yazılması için de printf metodu kullanılır.
6.5.1Scanner ile girdi
Scanner klavye = new Scanner(System.in);
System.out.print("Yaş: ");
int yas = klavye.nextInt();
klavye.nextLine(); // satır sonunu tüket
System.out.print("Ad: ");
String ad = klavye.nextLine();Scanner, Java'nın java.util paketindedir. Kullanılmadan önce dosyanın en başında, sınıf tanımından önce import java.util.Scanner; satırı yazılır. Bu satır derleyiciye Scanner adının nereden geleceğini söyler; yazılmazsa “cannot find symbol” hatası alınır.
Scanner klavye = new Scanner(System.in);:newile bir Scanner nesnesi oluşturulur ve standart girdiye, yani klavyeye (System.in) bağlanır.klavyebu nesneye referans tutan değişkendir; adı serbestçe seçilebilir.System.out.print("Yaş: ");: kullanıcıya ne girmesi gerektiğini söyleyen bir istem (prompt) yazılır.printlndeğilprintkullanıldığı için satır sonu eklenmez; kullanıcı aynı satırda yazar.int yas = klavye.nextInt();: program durur ve kullanıcının bir tam sayı yazıp Enter'a basmasını bekler. Okunan sayıyasdeğişkenine yazılır.klavye.nextLine();: satır sonunda kalan Enter karakteri tüketilir.nextInt()sayının arkasındaki Enter'ı okumaz; bu çağrı onu tampondan alır.String ad = klavye.nextLine();: kullanıcının yazdığı satırın tamamı, boşluklar dahil, okunur.
Scanner'ın okuma metotları, beklenen tipe göre seçilir: nextInt() int, nextLong() long, nextDouble() double, nextBoolean() boolean okur. next() boşluğa kadar tek bir kelime, nextLine() ise satırın sonuna kadar her şeyi okur. Sayı ve kelime okuyan metotlar, girdideki boşlukları ve satır sonlarını ayırıcı sayar ve atlar. Kullanıcı 20, 30 ve 40 sayılarını ister aynı satıra boşluklarla, ister ayrı satırlara yazsın; üç nextInt() çağrısı üçünü de okur.
Programın ekranda görünen hâli, kullanıcının yazdıklarıyla birlikte şöyledir (kullanıcı 20 ve Ada Yılmaz yazar):
Yaş: 20
Ad: Ada Yılmaz6.5.2Girdi tamponu adım adım
Kullanıcının yazdıkları Scanner'a doğrudan değil, bir tampon (buffer) üzerinden ulaşır. Tampon, klavyeden gelen karakterlerin okunana kadar beklediği bir sıradır. Kullanıcı Enter'a bastığında yazdığı satır, sonundaki satır sonu karakteriyle birlikte tampona girer. Satır sonu, Java'da \n ile, şekillerde ⏎ simgesiyle gösterilir. Scanner'ın her okuma metodu tampondan belirli karakterleri alır, gerisini sonraki okumalara bırakır.
nextInt() 20'yi okur ve satır sonunu tamponda bırakır. Boş nextLine() bu satır sonunu tüketir. Sonraki nextLine() adı okur.| Deyim | Ne olur? | Okunan | Tampon |
|---|---|---|---|
new Scanner(System.in) | Scanner klavyeye bağlanır | – | boş |
print("Yaş: ") | istem yazılır; imleç aynı satırda kalır | – | boş |
nextInt() | tampon boş: bekler. Kullanıcı 20⏎ yazar | 20 | ⏎ |
nextLine() | ⏎'e kadar okur, ⏎'i tüketir | "" (boş dizgi) | boş |
print("Ad: ") | istem yazılır | – | boş |
nextLine() | tampon boş: bekler. Kullanıcı Ada Yılmaz⏎ yazar | "Ada Yılmaz" | boş |
nextInt yalnızca 20'yi okur; okuma konumu ⏎'in önünde durur. nextLine ⏎'e kadar okur ve boş dizgi döndürür. Son nextLine satırın tamamını okur: “Ada Yılmaz”. Okunan karakterler soluk gösterilmiştir.Tablonun üçüncü satırı her şeyi açıklar. nextInt() sayının rakamlarını okur ve rakam olmayan ilk karakterde durur. Bu karakter kullanıcının bastığı Enter'dır ve tamponda kalır. nextLine() ise “satırın geri kalanını, satır sonuna kadar” okur. Bir sonraki satır sonu hemen okuma konumundadır; bu yüzden ilk nextLine() hiç beklemeden boş dizgi döndürür ve satır sonunu tüketir. Bu çağrının döndürdüğü değer bir yere atanmaz; çağrının tek amacı tamponu temizlemektir. Son nextLine() boş tamponla karşılaşır ve kullanıcıyı bekler. Kullanıcının yazdığı “Ada Yılmaz” boşluğuyla birlikte okunur; satır sonu tüketilir ama döndürülen dizgiye eklenmez.
6.5.3Satır sonu tüketilmezse
Tamponu temizleyen satır unutulursa ne olur? Sunumdaki ikinci program bu durumu gösterir:
Scanner klavye = new Scanner(System.in);
System.out.print("Yaş: ");
int yas = klavye.nextInt();
System.out.print("Ad: ");
String ad = klavye.nextLine();
System.out.println("[" + ad + "]");Kullanıcı 20 yazıp Enter'a bastığında ekranda şu görünür ve program biter:
Yaş: 20
Ad: []nextInt'ten kalan ⏎ tampondadır. nextLine beklemeden bu ⏎'e kadar okur ve boş dizgi döndürür; kullanıcıya ad sorulamaz.Program adı sormadan bitmiş gibi görünür. Gerçekte “Ad: ” istemi yazılmış, nextLine() çağrılmış ve tamponda bekleyen satır sonunu bularak hemen dönmüştür. ad değişkeni boş dizgidir; köşeli parantezlerin arasında hiçbir şey yoktur. Kullanıcı ikinci bir şey yazmaya fırsat bulamaz. Çözüm, sunumdaki ilk programdaki gibi nextInt()'ten sonra fazladan bir klavye.nextLine(); çağırmaktır. Bir başka çözüm, sayıları da satır olarak okuyup dönüştürmektir: int yas = Integer.parseInt(klavye.nextLine());.
Aynı etki, kullanıcı sayıyla adı aynı satıra yazdığında da görülür. Girdi “20 Ada Yılmaz⏎” ise nextInt() 20'yi, sonraki nextLine() ise satırın geri kalanını okur: “ Ada Yılmaz”. Baştaki boşluk da dizgiye dahildir. next() ile nextLine() arasındaki fark da benzer biçimde görülür: “Ada Yılmaz⏎” girdisinde next() yalnızca “Ada”yı, ardından gelen nextLine() ise “ Yılmaz”ı okur.
nextInt(), nextDouble() ya da next() çağrısından hemen sonra nextLine() ile bir satır okumaya çalışmak. İlk nextLine(), önceki satırdan kalan satır sonunu okur ve boş dizgi döndürür. Sayı okuduktan sonra satır okunacaksa araya tamponu temizleyen bir klavye.nextLine(); konur.
6.5.4Girdi doğrulama
Kullanıcının her zaman beklenen değeri gireceği varsayılamaz. Yaş sorulan bir programa “yirmi” yazılabilir, bir hata sonucu −5 ya da 200 girilebilir. nextInt(), sayı beklediği yerde sayı olmayan bir kelimeyle karşılaşınca programı durdurur:
Yaş: yirmi
Exception in thread "main" java.util.InputMismatchExceptionProgramın bu tür girdilere dayanıklı olması için girdi doğrulama (input validation) yapılır. Doğrulama iki katmanlıdır. Önce tip denetlenir: girilen şey gerçekten bir tam sayı mı? Sonra aralık ve iş kuralı denetlenir: tam sayı makul bir yaş mı?
Scanner, tip denetimi için hasNextInt() gibi metotlar sunar. Bu metot girdiyi okumadan bakar: sıradaki parça bir tam sayıysa true, değilse false döndürür. Aşağıdaki program iki denetimi de if deyimleriyle yapar. if (koşul) { … } else { … } yapısı, koşul doğruysa ilk bloğu, yanlışsa else'ten sonraki bloğu çalıştırır.
Scanner klavye = new Scanner(System.in);
System.out.print("Yaş: ");
if (klavye.hasNextInt()) {
int yas = klavye.nextInt();
if (yas >= 0 && yas <= 150) {
System.out.println("Kabul: " + yas);
} else {
System.out.println("Hata: yaş 0 ile 150 arasında olmalı.");
}
} else {
System.out.println("Hata: tam sayı bekleniyor (örnek: 20).");
}Program üç farklı girdiyle çalıştırıldığında şu çıktıları verir:
Yaş: yirmi
Hata: tam sayı bekleniyor (örnek: 20).
Yaş: 200
Hata: yaş 0 ile 150 arasında olmalı.
Yaş: 20
Kabul: 20“yirmi” girildiğinde hasNextInt() false döndürür; nextInt() hiç çağrılmadığı için program çökmez ve anlaşılır bir ileti yazar. 200 girildiğinde tip denetimi geçilir ama aralık denetimi başarısız olur. 20 her iki denetimi de geçer. Hata iletisinin beklenen biçimi açıkça söylediğine dikkat edilmelidir: “Geçersiz girdi” yerine “tam sayı bekleniyor (örnek: 20)” yazmak, kullanıcıya ne yapacağını gösterir. Gerçek bir programda yanlış girdiden sonra değer yeniden sorulur; bu tekrar, bir döngüyle yapılır.
6.5.5Biçimli çıktı: printf
println değerleri olduğu gibi yazar: 87.456 değeri 87.456, 2.0/3 değeri 0.6666666666666666 olarak görünür. Bir not çizelgesinde ise ortalamaların iki basamakla ve sütunlar hizalı yazılması istenir. Bu iş için biçimli çıktı (formatted output) üreten printf metodu kullanılır.
String ad = "Ada";
double ortalama = 87.456;
System.out.printf("%-10s %6.2f%n", ad, ortalama);Ada 87.46printf'in ilk argümanı bir biçim dizgisidir (format string). Biçim dizgisindeki sıradan karakterler aynen yazılır. % ile başlayan parçalar ise biçim belirteçleridir (format specifier): her biri, sıradaki argümanın nasıl yazılacağını tarif eder ve argümanları sırayla tüketir.
%-10s %6.2f%n biçim dizgisinin çözümlenmesi. %-10s “Ada”yı 10 karakterlik alana sola yaslı yazar (3 harf + 7 boşluk). Sonra bir boşluk gelir. %6.2f, 87.456'yı iki basamağa yuvarlar ve 6 karakterlik alana sağa yaslar: “ 87.46”.Bir biçim belirteci % işaretiyle başlar ve bir dönüşüm harfiyle biter. Aradaki üç parça isteğe bağlıdır. Bayraklar hizalamayı ve dolguyu belirler: - sola yaslar, 0 boşluk yerine sıfırla doldurur, , binlik ayırıcı ekler. Genişlik, alanın en az kaç karakter olacağını söyler. Kesinlik nokta ile yazılır ve ondalık basamak sayısını verir. En sık kullanılan dönüşüm harfleri şunlardır:
| Belirteç | Anlam | Örnek | Çıktı |
|---|---|---|---|
%d | tam sayı | printf("[%d]", 42) | [42] |
%5d | 5 karakter, sağa yaslı | printf("[%5d]", 42) | [ 42] |
%-5d | 5 karakter, sola yaslı | printf("[%-5d]", 42) | [42 ] |
%05d | 5 karakter, soldan sıfırlı | printf("[%05d]", 42) | [00042] |
%,d | binlik ayırıcılı | printf("[%,d]", 1234567) | [1,234,567] |
%f | kayan noktalı, 6 basamak | printf("[%f]", Math.PI) | [3.141593] |
%.3f | 3 ondalık basamak | printf("[%.3f]", 2.0005) | [2.001] |
%e | bilimsel gösterim | printf("[%e]", 12345.678) | [1.234568e+04] |
%s | dizgi (her değer metne çevrilir) | printf("[%s]", 3.5) | [3.5] |
%c | karakter | printf("[%c]", 'Z') | [Z] |
%b | boolean | printf("[%b]", false) | [false] |
%n | satır sonu | printf("a%nb") | a ve b ayrı satırda |
%% | % işaretinin kendisi | printf("%%%d indirim", 20) | %20 indirim |
Tablodaki örnekler aşağıdaki programdan alınmıştır. Program aynı zamanda iki ayrıntıyı daha gösterir. Genişlik bir alt sınırdır: %3s belirteci 8 harflik bir adı kesmez, olduğu gibi yazar; %2d de 12345'i kesmez. Ayrıca String.format metodu aynı biçimlendirmeyi yapar ama sonucu yazmak yerine bir dizgi olarak döndürür.
String ad = "Ada";
double ortalama = 87.456;
System.out.printf("%-10s %6.2f%n", ad, ortalama);
System.out.printf("[%-10s][%6.2f]%n", ad, ortalama);
System.out.printf("%-8s|%6.2f|%05d%n", "Ada", 3.14159, 42);
System.out.printf("%-6s|%7.3f|%04d%n", "Can", 2.71828, 7);
System.out.printf("[%5d][%-5d][%05d][%d]%n", 42, 42, 42, 42);
System.out.printf("[%.0f][%.1f][%.3f][%f]%n", 2.5, 2.45, 2.0005, Math.PI);
System.out.printf("[%e][%.2e]%n", 12345.678, 0.000123);
System.out.printf("[%,d][%,.2f]%n", 1234567, 1234567.891);
System.out.printf("[%s][%s][%s][%b][%c]%n", 42, 3.5, true, false, 'Z');
System.out.printf("%%%d indirim%n", 20);
System.out.printf("[%3s][%2d]%n", "Uzunİsim", 12345);
System.out.printf("[%.1f][%.1f][%.1f][%.1f]%n", 0.25, 0.35, 0.05, 2.25);
String s = String.format("%d:%02d:%02d", 1, 2, 5);
System.out.println(s);Ada 87.46
[Ada ][ 87.46]
Ada | 3.14|00042
Can | 2.718|0007
[ 42][42 ][00042][42]
[3][2.5][2.001][3.141593]
[1.234568e+04][1.23e-04]
[1,234,567][1,234,567.89]
[42][3.5][true][false][Z]
%20 indirim
[Uzunİsim][12345]
[0.3][0.4][0.1][2.3]
1:02:05%.1f ile yazılan 0.25, 0.35, 0.05 ve 2.25 değerlerinin hepsi yukarı yuvarlanır: 0.3, 0.4, 0.1, 2.3. printf yuvarlarken sayının ondalık yazılışına bakar ve yarımları yukarı yuvarlar.
Sunumdaki ikinci örnek üç belirteci tek satırda kullanır:
System.out.printf("%-8s|%6.2f|%05d%n",
"Ada", 3.14159, 42);Ada | 3.14|00042| Parça | Argüman | Kural | Üretilen |
|---|---|---|---|
%-8s | "Ada" | 8 karakter, - sola yaslar: 3 harf + 5 boşluk | Ada␣␣␣␣␣ |
| | – | düz karakter, aynen yazılır | | |
%6.2f | 3.14159 | 2 basamağa yuvarla (3.14), 6 karaktere sağa yasla: 2 boşluk | ␣␣3.14 |
| | – | düz karakter | | |
%05d | 42 | 5 karakter, 0 bayrağı soldan sıfırla doldurur | 00042 |
%n | – | satır sonu | ⏎ |
Belirteç sayısı ile argüman sayısı ve tipleri uyuşmalıdır. Uyuşmazlık derleme sırasında değil, program çalışırken fark edilir. printf("%d%n", 3.5) çağrısı IllegalFormatConversionException fırlatır, çünkü %d tam sayı bekler ve 3.5 bir double'dır. printf("%s ve %s%n", "Ada") çağrısı ise ilk belirteci doldurur, ikinci belirteç için argüman bulamaz ve MissingFormatArgumentException ile durur.
Biçimli çıktının son bir ayrıntısı yerel ayardır (locale). printf, ondalık ayırıcıyı ve binlik ayırıcıyı bilgisayarın dil ve bölge ayarına göre seçer. Türkçe yerel ayarlı bir bilgisayarda aynı program farklı çıktı verir:
String ad = "Ada";
double ortalama = 87.456;
System.out.printf("%-10s %6.2f%n", ad, ortalama);
System.out.printf("%-8s|%6.2f|%05d%n", "Ada", 3.14159, 42);
System.out.printf("[%,d][%,.2f]%n", 1234567, 1234567.891);
System.out.printf(Locale.US, "%6.2f%n", ortalama);
System.out.println(ortalama);Ada 87,46
Ada | 3,14|00042
[1.234.567][1.234.567,89]
87.46
87.456Türkçe ayarda ondalık ayırıcı virgül, binlik ayırıcı noktadır: 87,46 ve 1.234.567,89. İlk argüman olarak Locale.US verilirse ayar o çağrı için geçersiz kılınır ve nokta kullanılır. println ise yerel ayardan etkilenmez; her zaman nokta kullanır. Derste ve sunumlarda çıktılar Locale.US biçiminde verilir.
Yerel ayar Scanner'ı da etkiler ve bu etki daha can sıkıcıdır. Türkçe yerel ayarda nextDouble(), ondalık sayının virgülle yazılmasını bekler. Kullanıcı “37.5” yazarsa InputMismatchException fırlatılır; “37,5” yazarsa 37.5 okunur. Locale.US ayarında durum tam tersidir. Programın her bilgisayarda aynı davranması isteniyorsa Scanner oluşturulduktan sonra ayar sabitlenir: klavye.useLocale(Locale.US);. Bu satırdan sonra Türkçe ayarlı bir bilgisayarda da “37.5” girdisi okunur.
Ödev programında ondalık sayı girildiğinde InputMismatchException alıp kodu hatalı sanmak. Kod doğru olabilir; sorun, bilgisayarın Türkçe yerel ayarda nokta yerine virgül beklemesidir. Ya girdi virgülle yazılır ya da Scanner'a useLocale(Locale.US) uygulanır. (Locale sınıfı da java.util paketindedir.)
6.6Örnekler
Bu kesimdeki küçük programlar, notun bütün araçlarını bir arada kullanır: değişkenler, tipler, dönüşümler, operatörler, Scanner ve printf. Her programda önce kod, sonra adım adım hesap ve en son gerçek çıktı verilir.
6.6.1Math sınıfı
Karekök, üs alma, mutlak değer gibi işlemler için Java'nın hazır Math sınıfı kullanılır. Math, java.lang paketindedir. Bu paket her Java programına kendiliğinden dahil edildiği için import satırı gerekmez. Math'in metotları bir nesne oluşturmadan, sınıf adıyla çağrılır: Math.sqrt(16).
| İfade | Sonuç | Açıklama |
|---|---|---|
Math.PI | 3.141592653589793 | π sabiti |
Math.sqrt(16) | 4.0 | karekök (double) |
Math.pow(2, 10) | 1024.0 | üs alma (double) |
Math.abs(-7) | 7 | mutlak değer |
Math.max(3, 8) | 8 | büyük olan (min: küçük) |
Math.round(2.5) | 3 | en yakın tam sayı (long) |
Math.round(-2.5) | -2 | yarım değer yukarı yuvarlanır |
sqrt ve pow her zaman double döndürür; tam kare olan 16'nın karekökü bile 4 değil 4.0'dır. abs, max ve min ise argümanın tipini korur: Math.abs(-7) int 7, Math.abs(-7.5) double 7.5 verir. Argümanlardan biri double ise sonuç da double'dır: Math.max(3, 8.0) 8.0 verir.
Math.round, en yakın tam sayıyı bulur. Sayı iki tam sayının tam ortasındaysa (2.5 gibi) yukarıya, yani pozitif sonsuz yönüne yuvarlar. Bu yüzden 2.5 → 3 ama −2.5 → −2 olur; −2, −3'ten büyüktür. Okulda öğretilen “yarımı sıfırdan uzağa yuvarla” kuralı −2.5 için −3 verir; Java'nın kuralı bundan farklıdır. Math sınıfının başka metotları da aşağıdaki programda görülür:
System.out.println(Math.PI);
System.out.println(Math.E);
System.out.println(Math.sqrt(16));
System.out.println(Math.pow(2, 10));
System.out.println(Math.abs(-7) + " " + Math.abs(-7.5));
System.out.println(Math.max(3, 8) + " " + Math.min(3, 8));
System.out.println(Math.round(2.5) + " " + Math.round(-2.5) + " " + Math.round(2.4) + " " + Math.round(-2.6));
System.out.println(Math.floor(2.7) + " " + Math.ceil(2.1) + " " + Math.floor(-2.7) + " " + Math.ceil(-2.1));
System.out.println(Math.pow(2, 0.5) + " " + Math.sqrt(2));
System.out.println(Math.pow(2, 31) + " " + (int) Math.pow(2, 31));
System.out.println(Math.max(3, 8.0));
System.out.println(Math.sqrt(2) * Math.sqrt(2));
System.out.println(Math.hypot(3, 4));3.141592653589793
2.718281828459045
4.0
1024.0
7 7.5
8 3
3 -2 2 -3
2.0 3.0 -3.0 -2.0
1.4142135623730951 1.4142135623730951
2.147483648E9 2147483647
8.0
2.0000000000000004
5.0Math.floor sayıyı aşağıya (negatif sonsuza doğru), Math.ceil yukarıya (pozitif sonsuza doğru) yuvarlar ve sonucu double olarak verir: floor(−2,7) = −3,0, ceil(−2,1) = −2,0. (int) dönüşümünün sıfıra doğru kestiği hatırlanırsa, negatif sayılarda floor ile (int)'in farklı sonuç verdiği görülür. Math.pow(2, 31) 2.147483648E9'dur; bu değer int'e çevrilince sınırda sıkışır ve 2147483647 olur. √2 · √2 ise kayan nokta hatası yüzünden tam 2 değil 2.0000000000000004'tür. Math.hypot(3, 4) dik üçgenin hipotenüsünü verir: √(9 + 16) = 5.
6.6.2İki değişkeni takas etme
İki değişkenin değerlerini değiştirmek, yani takas (swap) etmek, sıralama gibi pek çok algoritmanın temel adımıdır. İlk akla gelen çözüm yanlıştır:
int a = 5, b = 8;
a = b; // yanlış deneme
b = a;
System.out.println(a + " " + b);
a = 5; b = 8; // baştan
int gecici = a; // doğru yöntem
a = b;
b = gecici;
System.out.println(a + " " + b);8 8
8 5| Deyim | a | b | gecici | Açıklama |
|---|---|---|---|---|
int a = 5, b = 8; | 5 | 8 | – | hedef: a = 8, b = 5 |
a = b; | 8 | 8 | – | a'nın eski değeri 5 silindi |
b = a; | 8 | 8 | – | 5 artık hiçbir kutuda yok |
println | 8 | 8 | – | çıktı: 8 8 (başarısız) |
a = 5; b = 8; | 5 | 8 | – | baştan |
int gecici = a; | 5 | 8 | 5 | a'nın değeri güvende |
a = b; | 8 | 8 | 5 | |
b = gecici; | 8 | 5 | 5 | |
println | 8 | 5 | 5 | çıktı: 8 5 (tamam) |
a = b ve b = a sonrasında iki kutu da 8'dir. Altta: geçici değişken 5'i korur ve b = gecici takası tamamlar.Yanlış denemenin sorunu, atamanın eski değeri silmesidir. a = b çalıştığında a'daki 5 kaybolur; sonraki b = a yalnızca 8'i geri kopyalar. İki bardaktaki suyu yer değiştirmek için üçüncü bir bardak gerektiği gibi, takas da üçüncü bir kutu ister. Bu kutuya geçici değişken (temporary variable) denir. Doğru yöntem üç atama ve bir geçici değişken kullanır.
Sayılarda geçici değişken olmadan da takas yapılabilir: a = a + b; b = a - b; a = a - b;. a = 5, b = 8 için a önce 13 olur, sonra b = 13 − 8 = 5, en son a = 13 − 5 = 8 bulunur. Bu yöntem taşmaya karşı şaşırtıcı biçimde dayanıklıdır:
int a = 5, b = 8;
a = a + b;
b = a - b;
a = a - b;
System.out.println(a + " " + b);
int x = 2_000_000_000, y = 1_500_000_000;
x = x + y;
System.out.println("ara: " + x);
y = x - y;
x = x - y;
System.out.println(x + " " + y);8 5
ara: -794967296
1500000000 2000000000İki milyar ile bir buçuk milyarın toplamı int'i taşar ve ara değer −794.967.296 olur. Yine de sonuç doğru çıkar: taşma sırasında eklenen 232, sonraki çıkarmalarda geri alınır. Buna karşın bu hile okunabilirliği bozar ve yalnızca sayılarla çalışır; günlük programlamada geçici değişkenli takas tercih edilir.
6.6.3Celsius'tan Fahrenheit'a
Celsius cinsinden bir sıcaklığı Fahrenheit'a çeviren formül F = C · 9 / 5 + 32'dir. Program, sıcaklığı klavyeden okur ve sonucu bir ondalık basamakla yazar:
Scanner klavye = new Scanner(System.in);
double c = klavye.nextDouble();
double f = c * 9 / 5 + 32;
System.out.printf("%.1f C = %.1f F%n", c, f);| Girdi | c | c * 9 | … / 5 | … + 32 = f | Çıktı |
|---|---|---|---|---|---|
| 100 | 100.0 | 900.0 | 180.0 | 212.0 | 100.0 C = 212.0 F |
| 37 | 37.0 | 333.0 | 66.6 | 98.6 | 37.0 C = 98.6 F |
| -40 | -40.0 | -360.0 | -72.0 | -40.0 | -40.0 C = -40.0 F |
Kullanıcı 100 yazsa da nextDouble() değeri double olarak okur: c = 100.0. Formül soldan sağa hesaplanır. c * 9 double ile int'in çarpımıdır ve double'dır; bölme de toplama da kayan noktalı yapılır. %.1f belirteci her iki değeri bir ondalık basamakla yazar. Üçüncü çalıştırma ilginç bir gerçeği gösterir: −40 derece iki ölçekte de aynı sıcaklıktır.
Formülün yazılış sırası önemlidir. Aynı formül 9 / 5 * c + 32 biçiminde yazılsaydı ilk işlem 9 / 5 olurdu. İki int'in bölümü 1,8 değil 1'dir. Sonuç c = 37 için 98.6 yerine 1 · 37 + 32 = 69 çıkar:
double c = 37;
double f1 = 9 / 5 * c + 32;
double f2 = 9.0 / 5 * c + 32;
double f3 = c * 9 / 5 + 32;
System.out.println(f1 + " " + f2 + " " + f3);
System.out.println(9 / 5 + " " + 9.0 / 5);69.0 98.60000000000001 98.6
1 1.89.0 / 5 yazılınca bölme kayan noktalı olur ve sonuç doğru çıkar. Bu yazılışta değer 98.60000000000001'dir; işlem sırası farklı olduğu için kayan nokta hatası başka bir basamakta görünür. printf ile bir basamağa yuvarlanınca iki yazılış da 98.6 verir.
6.6.4Dairenin alanı ve çevresi
double r = 2.5;
double alan = Math.PI * r * r;
double cevre = 2 * Math.PI * r;
System.out.printf("Alan: %.2f%n", alan);
System.out.printf("Çevre: %.2f%n", cevre);Alan: 19.63
Çevre: 15.71Alan π · r · r = 3,14159… · 2,5 · 2,5'tir ve alan kutusunda 19.634954084936208 değeri durur. Çevre 2 · π · r'dir: 15.707963267948966. π değeri elle 3.14 diye yazılmaz; Math.PI 16 anlamlı basamaklı hazır bir sabittir. %.2f belirteci değerleri iki basamağa yuvarlar: 19.634… → 19.63 ve 15.7079… → 15.71. Yuvarlama yalnızca yazdırmayı etkiler; değişkenlerdeki değerler değişmez ve sonraki hesaplarda tam değerler kullanılır. Kare almak için Math.pow(r, 2) da yazılabilir; r * r hem daha kısa hem daha hızlıdır.
6.6.5Saniyeyi saate çevirme
Bir süre saniye cinsinden verildiğinde onu saat:dakika:saniye biçimine çevirmek, tam sayı bölmesi ile kalan operatörünün birlikte kullanıldığı tipik bir problemdir.
int toplam = 3725;
int saat = toplam / 3600;
int kalan = toplam % 3600;
int dakika = kalan / 60;
int saniye = kalan % 60;
System.out.printf("%d:%02d:%02d%n",
saat, dakika, saniye);1:02:05| Deyim | Hesap | Sonuç | Anlamı |
|---|---|---|---|
saat = toplam / 3600 | 3725 / 3600 | 1 | kaç tam saat |
kalan = toplam % 3600 | 3725 % 3600 | 125 | saatlerden artan saniye |
dakika = kalan / 60 | 125 / 60 | 2 | kaç tam dakika |
saniye = kalan % 60 | 125 % 60 | 5 | dakikalardan artan saniye |
printf("%d:%02d:%02d%n", …) | 1, 2, 5 | 1:02:05 | %02d: en az 2 hane, soldan 0 |
Yöntem büyük birimden küçüğe ilerler. Önce kaç tam saat olduğu bulunur, sonra kalan saniyeler dakika ve saniyeye ayrılır. Sonuç bir sağlamayla doğrulanır: 1 · 3600 + 2 · 60 + 5 = 3725. %02d belirteci, tek basamaklı dakika ve saniyeleri önüne 0 koyarak iki haneye tamamlar; çıktı 1:2:5 yerine 1:02:05 olur. Aynı yöntem başka sürelerde de çalışır: 86399 saniye 23:59:59, 7384 saniye 2:03:04'tür. Formüller tek satırda da yazılabilir. toplam % 3600 / 60 ifadesinde % ve / aynı önceliktedir ve soldan sağa işlenir; önce kalan, sonra dakika bulunur.
Kuruş cinsinden verilen bir tutar lira ve kuruşa nasıl ayrılır? 1575 ve 1005 kuruş için sonuç nedir?
int tutar = 1575;
System.out.println(tutar / 100 + " lira " + tutar % 100 + " kuruş");
tutar = 1005;
System.out.println(tutar / 100 + " lira " + tutar % 100 + " kuruş");
System.out.printf("%d,%02d TL%n", tutar / 100, tutar % 100);15 lira 75 kuruş
10 lira 5 kuruş
10,05 TL1 lira 100 kuruştur. 1575 / 100 = 15 tam lira, 1575 % 100 = 75 kuruş artar. 1005 kuruşta 1005 / 100 = 10 ve 1005 % 100 = 5 bulunur. Tutar Türkçe biçimde yazılırken kuruş iki haneye tamamlanmalıdır; aksi hâlde 10,05 TL yerine 10,5 TL yazılır ve okuyan 50 kuruş anlar. Son satırdaki %02d bu tamamlamayı yapar. Tutarı kuruş cinsinden int ya da long olarak tutmak, double'ın kayan nokta hatalarını da baştan önler.
6.7Yaygın hatalar
Bu bölümün konularında yapılan hataların çoğu birkaç kalıba girer. Sunumdaki tablo bunların beşini bir araya getirir:
| Hata | Örnek | Ne olur? |
|---|---|---|
| = ile == karışır | if (x = 5) | Derleme hatası: int, boolean değil |
| Tam sayı bölmesi | double o = 7 / 2; | o = 3.0, 3.5 değil |
| Kapsam dışı kullanım | { int k = 1; } k++; | k blok dışında tanımsız |
| Yanlış noktalı virgül | if (x > 0); | Boş deyim: koşul hiçbir şeyi denetlemez |
| String ve == | s1 == s2 | İçeriği değil, referansı karşılaştırır → s1.equals(s2) |
= ile == karışması. Koşulda eşitlik denetlenmek istenirken tek eşittir yazılırsa ifade bir atama olur. x = 5 ifadesinin değeri 5'tir ve tipi int'tir; if ise boolean bir koşul ister. Java bu hatayı derleme sırasında yakalar:
error: incompatible types: int cannot be converted to boolean
if (x = 5) {
^
1 errorC gibi dillerde bu satır derlenir ve koşul her zaman doğru sayılır. Java'nın int ile boolean'ı karıştırmaması, bu hatayı zararsız bir derleme hatasına çevirir.
Tam sayı bölmesi. double o = 7 / 2; satırında hedef double'dır ama bölme iki int arasında yapılır: 7 / 2 = 3. Sonuç double'a genişler ve o = 3.0 olur. Taşma kesimindeki long sonuc = a * b hatasıyla aynı kökten gelir: ifadenin tipi, sonucun yazılacağı yere değil, işlenenlere bağlıdır.
Kapsam dışı kullanım. Bir değişken, bildirildiği süslü parantez bloğu boyunca geçerlidir. Bu alana değişkenin kapsamı (scope) denir. Blok kapanınca değişken ortadan kalkar:
{
int k = 1;
}
k++;error: cannot find symbol
k++;
^
symbol: variable k
location: class Kapsam
1 errorYanlış noktalı virgül. if koşulundan hemen sonra konan noktalı virgül, Java için kendi başına bir deyimdir: hiçbir şey yapmayan boş deyim (empty statement). Koşul yalnızca bu boş deyimi denetler; arkasındaki blok koşuldan bağımsız olarak her zaman çalışır:
int x = -4;
if (x > 0);
{
System.out.println("pozitif");
}pozitifx = −4 olduğu hâlde “pozitif” yazılır. Program derlenir ve hiçbir uyarı vermez; bu yüzden bu hata zor bulunur.
String ve ==. Dizgi birleştirme kesiminde görüldüğü gibi, == iki String değişkeninin aynı nesneyi gösterip göstermediğine bakar. İçerik karşılaştırması equals ile yapılır.
Notun önceki kesimlerinde ayrıntılı ele alınan beş hata da bu listeye eklenebilir:
- tipe sığmayan sonucun sessizce taşması,
- kayan noktalı sayıların
==ile karşılaştırılması, (int)dönüşümünün yuvarlama sanılması,"Sonuç: " + 2 + 3ifadesinde toplama yapılacağının sanılması,nextInt()'ten sonra satır sonunun tüketilmemesi.
6.8Özet
- Değişken, belirli tipteki bir değer için bellekte ayrılmış adlı bir kutudur; adı, tipi ve değeri vardır. Bildirim tipi ve adı belirtir, ilk değer verme başlangıç değerini verir. Yerel değişken okunmadan önce kesin olarak değer almış olmalıdır;
finaldeğişken yalnızca bir kez atanır. - Atama önce sağ tarafı hesaplar, sonra sonucu soldaki değişkene yazar; eski değer silinir. Bileşik atama (
+=,-=…) değişkenin eski değerini kullanarak günceller ve sağ tarafı bir bütün olarak hesaplar. - Tanımlayıcı harf, _ ya da $ ile başlar; boşluk içermez, anahtar kelime olamaz ve büyük-küçük harfe duyarlıdır. Değişkenler lowerCamelCase, sınıflar UpperCamelCase, sabitler BÜYÜK_HARF ile adlandırılır.
- Java'nın sekiz ilkel tipi vardır: byte (8), short (16), int (32), long (64), float (32), double (64), char (16) ve boolean. Varsayılan seçimler int ve double'dır. long sabitleri L, float sabitleri F soneki ister.
- Tam sayılar ikiye tümleyen gösterimle saklanır. Aralığı aşan sonuç taşar ve sessizce öbür uca sarar:
Integer.MAX_VALUE + 1→ −2147483648. Taşmayı yakalamak içinMath.addExactgibi metotlar istisna fırlatır. İfadenin tipi işlenenlerden belirlenir;long s = a * byine int ile çarpar. - Kayan noktalı sayılar 0.1 gibi onluk kesirleri tam saklayamaz:
0.1 + 0.20.30000000000000004'tür. double değerler==ile değil bir eşikle karşılaştırılır. Para hesabında tam sayı kuruş ya da BigDecimal kullanılır. - char bir UTF-16 kod birimini sayı olarak tutar ('A' = 65); aritmetikte int'e yükselir. boolean yalnızca true ya da false alır ve sayılarla yer değiştirmez.
- Genişletme otomatik ve kayıpsızdır (int → double). Daraltma açık dönüşüm ister:
(int) 9.89 verir; kesir yuvarlanmaz, sıfıra doğru kesilir. Tam sayı daraltmasında alt bitler kalır:(byte) 200−56'dır. Yuvarlama içinMath.roundkullanılır. - İki tam sayının bölümü tam sayı bölmesidir:
5 / 22'dir. İşlenenlerden biri double ise öbürü yükseltilir ve5.0 / 22.5 olur.(double) (5 / 2)ise kaybolan kesri geri getirmez. byte, short ve char işlemlerde önce int'e yükselir. %kalanı verir ve işaretini bölünenden alır. Tam sayıyı sıfıra bölmekArithmeticExceptionfırlatır; kayan noktalı bölme ise Infinity ya da NaN üretir.- Sonek
i++eski değeri, önek++iyeni değeri üretir. Bileşik atama sonucu sol tarafın tipine örtük olarak daraltır:b += 5derlenir,b = b + 5derlenmez. - Karşılaştırma operatörleri boolean üretir;
&&,||ve!boolean değerleri birleştirir. İşlem önceliği: parantez, tekli, çarpımsal, toplamsal, sıra, eşitlik, &&, ||, atama. Aynı düzeyde soldan sağa gidilir. Derleyici ifadeyi bir ifade ağacına çevirir. - Kısa devre:
&&'de sol false ise,||'de sol true ise sağ taraf hesaplanmaz. Koruyucu koşul (b != 0) sola yazılır. +işlenenlerden biri dizgiyse birleştirme yapar ve soldan sağa işler:"Sonuç: " + 2 + 3“Sonuç: 23”,"Sonuç: " + (2 + 3)“Sonuç: 5” verir. Dizgi içeriğiequalsile karşılaştırılır.- Scanner klavyeden okur; girdi bir tamponda bekler.
nextInt()satır sonunu tamponda bırakır; ardından satır okunacaksa önce birnextLine()ile tüketilir. Girdi önce tip, sonra aralık açısından doğrulanır. - printf, biçim dizgisindeki biçim belirteçlerini (%d, %f, %s, %n …) argümanlarla sırayla doldurur; genişlik, kesinlik ve bayraklar (-, 0, ,) çıktıyı hizalar. Ondalık ayırıcı yerel ayara bağlıdır; Türkçe ayarda printf virgül yazar, nextDouble virgül bekler.
6.9Kavram listesi
Terimlerin İngilizce karşılıkları ve öteki sunumlardaki kullanımları terim sözlüğünde bulunur.
- değişken variable
- tip type
- bildirim declaration
- ilk değer verme initialization
- atama assignment
- yerel değişken local variable
- sabit constant / literal
- bileşik atama compound assignment
- tanımlayıcı identifier
- anahtar kelime keyword
- adlandırma naming convention
- ilkel tip primitive type
- referans reference
- ikiye tümleyen two's complement
- işaret biti sign bit
- taşma overflow
- istisna exception
- kayan noktalı sayı floating-point number
- eşik epsilon (tolerance)
- dizgi string
- tip dönüşümü type conversion
- genişletme widening conversion
- daraltma narrowing conversion
- açık dönüşüm cast
- örtük dönüşüm implicit conversion
- kesme truncation
- tam sayı bölmesi integer division
- tip yükseltme type promotion
- operatör operator
- işlenen operand
- ifade expression
- kalan remainder
- bölünen dividend
- bölüm quotient
- artırma / azaltma increment / decrement
- önek prefix
- sonek postfix
- karşılaştırma operatörü comparison operator
- mantıksal operatör logical operator
- işlem önceliği operator precedence
- ifade ağacı expression tree
- kısa devre değerlendirme short-circuit evaluation
- dizgi birleştirme string concatenation
- istem prompt
- tampon buffer
- satır sonu newline
- girdi doğrulama input validation
- biçimli çıktı formatted output
- biçim dizgisi format string
- biçim belirteci format specifier
- yerel ayar locale
- ondalık ayırıcı decimal separator
- takas swap
- geçici değişken temporary variable
- kapsam scope
- boş deyim empty statement
6.10Kendini Dene
int x = 1_000_000; int y = x * 3000; long z = x * 3000L;satırlarından sonraSystem.out.println(y + " " + z);ne yazar? y neden yanlıştır?Cevap
Çıktı
-1294967296 3000000000'dır. 1.000.000 · 3.000 = 3.000.000.000, int sınırı 2.147.483.647'yi aşar. İki işlenen de int olduğu için çarpma int ile yapılır ve taşar: 3.000.000.000 − 232 = −1.294.967.296. z'de 3000L bir long sabitidir; x long'a yükselir ve çarpma long ile yapılır.(byte) 300,(int) -7.9,Math.round(-7.5)ve(int) Math.round(7.5)ifadelerinin değerleri nedir?Cevap
Sırasıyla 44, −7, −7 ve 8. 300'ün alt 8 biti tutulur: 300 − 256 = 44 (işaret biti 0 olduğu için pozitiftir).
(int) -7.9sıfıra doğru keser: −7. Math.round yarımları pozitif yöne yuvarlar: −7.5 → −7, 7.5 → 8; ikincisi long döndürdüğü için int'e çevrilmiştir.int a = 7, b = 8; double o = (a + b) / 2;satırından sonra o kaçtır? Doğru ortalamayı veren iki yazılış nedir?Cevap
o = 7.0'dır.
a + b15'tir ve int'tir; 15 / 2 tam sayı bölmesiyle 7 olur, sonra double'a genişleyerek 7.0 yazılır. Doğru yazılışlar(a + b) / 2.0ve(double) (a + b) / 2'dir; ikisi de 7.5 verir.(double) ((a + b) / 2)ise yine 7.0 verir, çünkü dönüşüm bölmeden sonra yapılır.int i = 10; int a = i-- - 3; int b = --i * 2;satırlarından sonra i, a ve b kaçtır?Cevap
i = 8, a = 7, b = 16. Sonek
i--eski değeri (10) üretir: a = 10 − 3 = 7; ardından i 9 olur. Önek--iönce i'yi 8 yapar, sonra yeni değeri kullanır: b = 8 · 2 = 16.short s = 10; s *= 2.6;derlenir mi? Derlenirse s kaç olur?s = s * 2.6;için ne söylenebilir?Cevap
Derlenir ve s = 26 olur. Bileşik atama
s = (short) (s * 2.6)anlamına gelir: 10 · 2,6 = 26,0 (double) hesaplanır ve short'a daraltılır.s = s * 2.6;ise derlenmez; sağ taraf double'dır ve double'dan short'a örtük dönüşüm yoktur (“possible lossy conversion from double to short”).5 + 4 % 3 * 2 - 8 / 3ve!(3 > 2) || 4 >= 4 && 1 != 1ifadelerinin değerleri nedir? Hesap hangi sırayla yapılır?Cevap
İlki 5'tir. *, / ve % aynı düzeydedir ve soldan sağa işlenir: 4 % 3 = 1, 1 · 2 = 2, 8 / 3 = 2; sonra 5 + 2 − 2 = 5. İkincisi false'tur. Parantez içi 3 > 2 true'dur ve ! ile false olur.
&&,||'den önce gelir: 4 >= 4 true, 1 != 1 false, true && false = false. Sonuç false || false = false.System.out.println(1 + 2 + "3" + 4 + 5);veSystem.out.println('a' + 2 * 3 + "");ne yazar?Cevap
İlki “3345”tir: soldan sağa önce iki int toplanır (3), sonra “3” dizgisiyle birleşir (“33”); bundan sonra her işlem birleştirmedir: “334”, “3345”. İkincisi “103”tür: 2 · 3 = 6 önce hesaplanır; 'a' int'e yükselir ve 97 + 6 = 103 bulunur; en son boş dizgiyle birleşir.
Kullanıcı ilk satıra
3 4.5, ikinci satıraAli Veliyazıyor (Locale.US). Program sırasıylanextInt(),nextDouble(),nextLine()venextLine()çağırıyor. Dört çağrı ne döndürür?Cevap
3, 4.5, "" (boş dizgi) ve "Ali Veli". nextInt ve nextDouble aynı satırdaki iki sayıyı okur; ilk satırın sonundaki satır sonu tamponda kalır. İlk nextLine bu satır sonuna kadar okur ve boş dizgi döndürür. İkinci nextLine ikinci satırı okur. Türkçe yerel ayarda ise nextDouble “4.5” girdisinde InputMismatchException fırlatırdı.
System.out.printf("%-5s|%8.3f|%,d%n", "Kalem", 12.34567, 1500000);satırı Locale.US ayarında ne yazar? Her alan kaç karakter tutar?Cevap
Kalem| 12.346|1,500,000. “Kalem” tam 5 harftir;%-5sboşluk eklemez.%8.3f12.34567'yi üç basamağa yuvarlar (12.346, 6 karakter) ve 8 karaktere tamamlamak için önüne 2 boşluk koyar.%,dbinlik ayırıcı ekler; Türkçe ayarda bu alan 1.500.000, ikinci alan 12,346 olurdu.
6.11İleri okuma
- R. Sedgewick, K. Wayne, Computer Science: An Interdisciplinary Approach (Addison-Wesley, 2016), 1.2 Built-in Types of Data: Java'nın ilkel tiplerini, tip dönüşümünü ve kayan nokta sorunlarını kısa programlarla anlatır; kitabın sitesinde (introcs.cs.princeton.edu) bölümün özeti ve alıştırmaları vardır.
- Oracle, The Java Tutorials, Language Basics: Variables ve Operators: ilkel tipleri, sabitlerin yazılışını, operatör öncelik tablosunu ve bileşik atamayı resmî kaynaktan, örneklerle verir.
- Y. D. Liang, Introduction to Java Programming and Data Structures (13. baskı, Pearson, 2024), Bölüm 2 Elementary Programming ve Bölüm 4: Scanner ile girdi, sayısal tipler, printf ve Math sınıfı üzerine çok sayıda çözülmüş örnek içerir.
- Oracle, Java Language Specification, Java SE 21: Bölüm 5 (Conversions and Contexts) ve Bölüm 15 (Expressions): bu notta anlatılan dönüşüm, yükseltme ve değerlendirme sırası kurallarının kesin tanımlarıdır; ileri düzeyde merak edilen bir ayrıntının son sözü buradadır.
6.12Kaynaklar
- P. Deitel, H. Deitel, Java: How to Program, Early Objects, 11. baskı, Pearson, 2018.
- Y. D. Liang, Introduction to Java Programming and Data Structures, 13. baskı, Pearson, 2024.
- Oracle, The Java Tutorials: Variables, Operators.
- Oracle, Java Language Specification, Java SE 21 Edition: Bölüm 4, Types, Values, and Variables; Bölüm 15, Expressions.
- Oracle, Java SE 21 API: java.util.Scanner, java.lang.Math, java.util.Formatter (biçim belirteçleri).
- R. Sedgewick, K. Wayne, Computer Science: An Interdisciplinary Approach, Addison-Wesley, 2016: 1.2 Built-in Types of Data.
İlgili sayfalar: Değişkenler, Veri Tipleri ve Operatörler sunumu · Çalışma soruları · İfade değerlendirici · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.