Java ile Nesne Yönelimli Programlama · Ders Notu
Bölüm 3.4: Dizgi
Programların büyük kısmı metinle çalışır. Bir kullanıcı adı, bir e-posta adresi, bir dosya yolu ya da ekrana yazılan bir ileti hep karakterlerden oluşan bir metindir. Java'da bu tür metinler String sınıfının nesneleriyle saklanır. Bu not dizgilerin nasıl oluşturulduğunu, + ile birleştirmenin hangi sırayla yürüdüğünü ve karakterlere indisle nasıl erişildiğini anlatır. Ardından en sık kullanılan dizgi metotları gelir. Dizgilerin değiştirilemez olması, Türkçe harflerde yerel ayarın etkisi ve döngüde birleştirme için StringBuilder ayrı kesimlerde işlenir. Scanner ile klavyeden metin okumanın inceliklerinden sonra dizgilerin karşılaştırılması ele alınır: == ile equals() arasındaki fark, dizgi havuzu ve compareTo. Son kesimler printf ile biçimli çıktıyı, palindrom ve kelime frekansı uygulamalarını ve sık yapılan hataları kapsar. Sunumdaki bütün kodlar JDK 21 ile çalıştırılmış, çıktıları ve hata iletileri gerçek çalıştırmadan alınmıştır.
Öğrenme çıktıları
- Dizgi oluşturmak
Stringnesnesini dizgi sabitiyle venewile oluşturmak;+birleştirmesinin soldan sağa nasıl işlendiğini açıklamak.- İndis ve metotlar
charAt,length,indexOfvesubstringile dizginin parçalarına erişmek; dizgiyi döngüyle karakter karakter dolaşmak.- Değiştirilemezlik
- Dizgi metotlarının yeni nesne döndürdüğünü bellek çizimiyle göstermek; yerel ayarın büyük-küçük harf dönüşümüne etkisini bilmek; döngüde
StringBuilderkullanmak. - Kullanıcı girişi
next(),nextLine()venextInt()arasındaki farkı açıklamak; satır sonu tuzağından kaçınmak.- Karşılaştırma
==ileequals()farkını dizgi havuzuyla açıklamak;compareTove öteki karşılaştırma metotlarını doğru kullanmak.- Biçimli çıktı
printfveString.formatile genişlik, hizalama ve hassasiyeti ayarlamak.
3.4.1Dizgi kavramı
Bilgisayar her veriyi sayı olarak saklar. Bir harf de bellekte bir sayıdır; char tipi tek bir karakterin kodunu tutar. Gerçek programlar ise tek harfle değil, kelimelerle ve cümlelerle çalışır. Bir öğrencinin adı, bir ürünün açıklaması ya da bir sunucudan gelen yanıt, ardışık karakterlerden oluşan birer metindir. Java bu tür metinler için hazır bir sınıf sunar. Bu kesim o sınıfı tanıtır, dizginin nasıl oluşturulduğunu ve karakterlerine nasıl erişildiğini anlatır.
3.4.1.1Dizgi nedir?
Dizgi (string), karakterlerden oluşan bir metni saklayan nesnedir. Java'da dizgilerin tipi java.lang.String sınıfıdır. Kaynak kodda çift tırnak arasına yazılan "Tarkan" gibi bir metin dizgi sabiti (string literal) adını alır.
Tanımdaki birkaç ayrıntı ilk bakışta gözden kaçar. Birincisi, String bir sınıftır, ilkel tip değildir. Bu yüzden adı büyük harfle başlar: int, double, char küçük harfle yazılır, String ise büyük S ile. Bir String değişkeni metnin kendisini değil, metni tutan nesnenin bellekteki yerini, yani bir referansı saklar. İkincisi, String sınıfı java.lang paketindedir. Bu paket her programa kendiliğinden eklenir, dolayısıyla import satırı gerekmez. Üçüncüsü, dizgi sabiti yazmak için new kullanılmaz. String ad = "Tarkan"; satırı nesneyi kendisi oluşturur.
Java karakterleri Unicode ile saklar. Unicode, dünyadaki yazı sistemlerinin karakterlerine birer numara veren standarttır. Bu sayede ç, ğ, ı, İ, ö, ş ve ü gibi Türkçe harfler dizgide sorunsuz durur. Dizgi aslında sıralı karakter kutularından oluşur. Her kutunun bir sıra numarası vardır ve numaralandırma 0'dan başlar (Şekil 3.4.1).
"Tarkan" dizgisi altı karakter kutusundan oluşur. Kutuların altındaki 0–5 sayıları indislerdir.Tek karakter ile tek karakterlik dizgi farklı şeylerdir. 'T' tek tırnakla yazılır ve tipi char'dır; bellekte 84 sayısını tutan iki baytlık bir değerdir. "T" ise çift tırnakla yazılır ve uzunluğu 1 olan bir String nesnesidir. İkisi birbirinin yerine kullanılamaz. char c = "T"; satırı derlenmez, String s = 'T'; satırı da derlenmez.
Sunumdaki ilk örnek üç dizgi oluşturur ve yazdırır:
String ad = "Tarkan";
String adSoyad = "Mustafa Sandal";
String nokta = "(" + 3 + ", " + 5 + ")";
System.out.println(ad);
System.out.println(adSoyad);
System.out.println(nokta);Tarkan
Mustafa Sandal
(3, 5)İlk iki satırda değişkenlere dizgi sabitleri atanır. Boşluk da bir karakterdir: "Mustafa Sandal" 14 karakterlik bir dizgidir. Üçüncü satır sayılardan bir dizgi kurar. "(" bir dizgi olduğu için ardından gelen 3 metne çevrilir ve eklenir; aynı kural ", ", 5 ve ")" için de işler. Sonuçta nokta değişkeni "(3, 5)" metnini gösterir. Bu birleştirme kuralı biraz aşağıda ayrıntılı ele alınıyor, çünkü yeni başlayanların en sık şaşırdığı yer burasıdır.
3.4.1.2Dizgi oluşturmak
Bir dizgi birkaç yolla oluşturulur. En yaygını dizgi sabitidir. Bunun yanında String sınıfının yapıcıları, sayıyı dizgiye çeviren String.valueOf metodu ve iki dizgiyi uç uca ekleyen concat metodu da kullanılır. Sunumdaki kodun ilk yarısı bu yolları bir arada gösterir:
String s0 = "Giresun"; // deyim
char[] harfler = {'J', 'a', 'v', 'a'};
String s1 = new String(harfler); // char[]
String s2 = String.valueOf(42); // sayıdan
String s3 = "Giresun".concat(" Üni.");
System.out.println(s1 + " " + s2 + " " + s3);
System.out.println("Toplam: " + 3 + 5);
System.out.println("Toplam: " + (3 + 5));
System.out.println(3 + 5 + " elma");
System.out.println('a' + 'b' + "c");
System.out.println("" + 'a' + 'b');Java 42 Giresun Üni.
Toplam: 35
Toplam: 8
8 elma
195c
abKodun ilk altı satırı şöyle çalışır:
String s0 = "Giresun";en sık görülen biçimdir: dizgi sabiti doğrudan değişkene atanır.new String(harfler)birchardizisindeki karakterlerden yeni bir dizgi kurar. Dizi{'J', 'a', 'v', 'a'}olduğu içins1"Java"olur. Bu yapıcı, karakterleri tek tek hesaplanan bir metni dizgiye dönüştürmek için kullanışlıdır.String.valueOf(42)42 sayısının metin karşılığını, yani"42"dizgisini döndürür.valueOfbir sınıf metodudur: bir nesne üzerinden değil, sınıf adıyla çağrılır."Giresun".concat(" Üni.")iki dizgiyi birleştirir ve"Giresun Üni."döndürür. Burada metot doğrudan bir dizgi sabiti üzerinden çağrılır; sabit de bir nesne olduğu için bu geçerlidir.
Yazdırma satırı bu üç dizgiyi aralarına boşluk koyarak birleştirir: Java 42 Giresun Üni.. Kodun ikinci yarısı ise + operatörünün davranışını gösterir. Çıktının ikinci satırındaki Toplam: 35 çoğu öğrenciyi şaşırtır; nedeni bir sonraki alt kesimin konusudur.
new String("Giresun") biçimi de derlenir ve çalışır, ama gereksiz bir nesne üretir. Bu biçim karşılaştırma kesiminde dizgi havuzunu anlatmak için kullanılacak. Gündelik kodda bir sabitten dizgi oluşturmak için new yazmanın bir yararı yoktur.
3.4.1.3Birleştirme ve + operatörünün sırası
Dizgi birleştirme (string concatenation), iki değeri uç uca ekleyerek yeni bir dizgi üretmektir. Java'da + operatörünün işlenenlerinden en az biri String ise öteki işlenen de dizgiye çevrilir ve iki metin birleştirilir. İki işlenen de sayıysa + sayısal toplama yapar.
Aynı + işareti iki ayrı iş yapar: toplama ve birleştirme. Hangisinin yapılacağına her işlemde ayrı ayrı karar verilir. Karar için yalnız o anki iki işlenenin tipine bakılır. Ayrıca + soldan sağa işlenir. Yani a + b + c ifadesi (a + b) + c diye hesaplanır. Bu iki kural birlikte düşünülünce sunumdaki çıktıların hepsi açıklanır:
"Toplam: " + 3 + 5: önce"Toplam: " + 3hesaplanır. Sol yan dizgi olduğu için 3 metne çevrilir:"Toplam: 3". Ardından"Toplam: 3" + 5gelir ve yine birleştirme yapılır. Sonuç Toplam: 35'tir."Toplam: " + (3 + 5): parantez önce hesaplanır. İçerideki iki işlenen deintolduğu için toplama yapılır, 8 çıkar. Sonra 8 dizgiye eklenir: Toplam: 8.3 + 5 + " elma": soldaki iki işlenen sayıdır, önce toplanır ve 8 olur. Ancak ondan sonra dizgiyle karşılaşılır: 8 elma.'a' + 'b' + "c":charbir sayı tipidir. İkichartoplanınca kodları toplanır: 97 + 98 = 195. Sonra"c"eklenir: 195c."" + 'a' + 'b': baştaki boş dizgi her şeyi değiştirir."" + 'a'zaten bir dizgidir,"a". Ardından'b'metin olarak eklenir: ab.
Kural kısaca şudur: ifade soldan sağa okunur. Bir dizgiyle karşılaşılana kadar sayılar toplanır. Bir dizgi ortaya çıktığı andan sonraki her + birleştirmedir. Sayıların toplanması isteniyorsa parantez kullanılır.
Sunumdaki izleme slaytı aynı kuralı değişkenlerle gösterir:
int a = 4, b = 2;
String s1 = "Sonuç: " + a + b;
String s2 = "Sonuç: " + (a + b);
String s3 = a + b + " kalem";
String s4 = 'x' + 'y' + "z";
String s5 = "" + 'x' + 'y';
System.out.println(s1 + " | " + s2);
System.out.println(s3 + " | " + s4 + " | " + s5);Sonuç: 42 | Sonuç: 6
6 kalem | 241z | xyTablo 3.4.1'deki her satır bir + işlemini gösterir. Sütunlar, işlenenlerin tipini ve o adımın sonucunu verir.
| Değişken | İşlem | Sol işlenen | Sağ işlenen | Sonuç |
|---|---|---|---|---|
s1 | "Sonuç: " + a | String | int | birleştirme: "Sonuç: 4" |
… + b | String | int | birleştirme: "Sonuç: 42" | |
s2 | (a + b) | int | int | toplama: 6 |
"Sonuç: " + 6 | String | int | birleştirme: "Sonuç: 6" | |
s3 | a + b | int | int | toplama: 6 |
6 + " kalem" | int | String | birleştirme: "6 kalem" | |
s4 | 'x' + 'y' | char | char | toplama: 120 + 121 = 241 |
241 + "z" | int | String | birleştirme: "241z" | |
s5 | "" + 'x' | String | char | birleştirme: "x" |
"x" + 'y' | String | char | birleştirme: "xy" |
s1 ile s2 arasındaki fark yalnız bir çift paranteze dayanır, ama sonuç 42 ile 6 kadar farklıdır. Şekil 3.4.2 bu iki satırın ilk adımlarını sunumdaki çizimlerle gösterir. Üstteki kutuların altında işlenenlerin tipi, alttaki kutuda ise o adımın sonucu ve tipi yazılıdır.
s1 ve s2 satırlarının adımları. (a) Sol yan String olduğu için 4 metne eklenir. (b) Sonuç hâlâ String'dir; 2 de metin olarak eklenir ve "Sonuç: 42" çıkar. (c) Parantez içinde iki int toplanır, 6 olur. (d) 6 dizgiye eklenir.s4 satırı ayrı bir dikkat ister. char değerleri metin gibi görünse de Java onları sayı olarak işler. 'x' karakterinin Unicode kodu 120, 'y''nin kodu 121'dir. İki char yan yana toplandığında sonuç bir int olur, 241. Bu sayı ancak "z" ile karşılaşınca metne çevrilir. s5'te ise baştaki "" ilk işlemi birleştirmeye çevirir ve iki harf metin olarak yan yana gelir. Karakterleri birleştirmek için başa boş dizgi koymak yaygın bir yoldur. Aynı iş String.valueOf('x') + 'y' ile de yapılır.
Ekrana bir toplam yazdırırken parantezi unutmak: System.out.println("Toplam: " + x + y); satırı x ile y'yi toplamaz, yan yana yazar. x = 10, y = 5 iken çıktı Toplam: 105 olur. Doğrusu "Toplam: " + (x + y)'dir. Hata derleyicinin gözünden kaçar, çünkü iki yazım da geçerli Java'dır.
Bir ayrıntı daha var: birleştirme her seferinde yeni bir String nesnesi üretir. "Sonuç: " + a + b ifadesinde mantıksal olarak iki ara dizgi oluşur. Az sayıda parça için bunun bir önemi yoktur. Bir döngüde binlerce kez birleştirme yapıldığında ise maliyet hızla büyür. Değiştirilemezlik kesiminde bu maliyet hesaplanıyor.
3.4.1.4İndisleme
İndis (index), bir karakterin dizgideki sıra numarasıdır. İlk karakterin indisi 0, son karakterin indisi length() - 1'dir. Bir karaktere indisiyle erişmeye indisleme (indexing) denir.
İndislemenin iki temel metodu vardır. length() dizgideki karakter sayısını döndürür. charAt(i) ise i indisindeki karakteri bir char olarak verir. "Tarkan" altı karakterlidir; indisleri 0'dan 5'e kadar gider (Şekil 3.4.3). Uzunluk 6 olduğu hâlde 6 numaralı bir karakter yoktur. Numaralandırma 0'dan başladığı için son indis her zaman uzunluğun bir eksiğidir.
charAt(0), son karakter charAt(length() - 1), yani charAt(5) ile alınır.Sunumdaki örnek bu metotları "Tarkan" üzerinde dener:
String isim = "Tarkan";
System.out.println(isim.length());
System.out.println(isim.charAt(0));
System.out.println(isim.charAt(isim.length() - 1));
char c = isim.charAt(2);
System.out.println(c + 1);
System.out.println((char) (c + 1));6
T
n
115
sİlk satır uzunluğu, 6'yı yazar. charAt(0) ilk harfi, T'yi verir. Son harf için indis elle yazılmaz, uzunluktan hesaplanır: isim.length() - 1 = 5 ve charAt(5) = n. Bu yazım her uzunluktaki dizgide çalışır. Sonraki satırda c, indis 2'deki 'r' karakterini alır.
Son iki satır char'ın bir sayı olduğunu gösterir. 'r' karakterinin Unicode kodu 114'tür. c + 1 ifadesinde char ile int toplanır ve sonuç int olur: 115. Ekrana harf değil sayı çıkar. Bir sonraki harfi görmek için sonuç (char) ile geri çevrilir: 115 kodlu karakter s'dir. Harfleri kaydırmaya dayanan basit şifreleme örnekleri bu dönüşümü kullanır.
length() bir metottur, yani sonunda parantez vardır. Dizilerde ise uzunluk bir alandır ve parantezsiz yazılır: dizi.length. İki yazım sık karıştırılır ve ikisi de derleme hatası verir:
Uzun.java:4: error: cannot find symbol
int n = s.length;
^
symbol: variable length
location: variable s of type StringDiziUzun.java:4: error: cannot find symbol
int n = d.length();
^
symbol: method length()
location: variable d of type int[]İletilerdeki symbol satırı farkı açıkça söyler. Birincide derleyici String içinde length adlı bir değişken arar ve bulamaz. İkincide int[] içinde length() adlı bir metot arar ve yine bulamaz. Bir başka karışıklık da dizgiye dizi gibi köşeli parantezle erişmeye çalışmaktır. s[0] yazımı C ve Python'da çalışır ama Java'da derlenmez:
Arr.java:4: error: array required, but String found
char c = s[0];
^Geçersiz bir indis derleme sırasında yakalanmaz, çünkü indisin değeri çoğu zaman program çalışırken belli olur. Dizginin dışına taşan bir indis çalışma zamanında bir istisnaya yol açar:
String s = "Java";
System.out.println(s.charAt(4));Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
Index 4 out of bounds for length 4"Java" dört karakterlidir; geçerli indisler 0, 1, 2 ve 3'tür. charAt(4) çağrısı StringIndexOutOfBoundsException fırlatır ve program durur. İletideki “Index 4 out of bounds for length 4” sözü sorunun yerini kesin olarak verir: 4 numaralı indis istendi, uzunluk ise 4. Negatif indis de aynı istisnayı doğurur; "Java".charAt(-1) için ileti “Index -1 out of bounds for length 4” olur.
Son karakteri s.charAt(s.length()) ile almaya çalışmak. Uzunluk, son indisten bir fazladır ve bu çağrı her dizgide StringIndexOutOfBoundsException verir. Aynı hata döngüde i <= s.length() koşuluyla da yapılır. Doğru koşul i < s.length()'tir.
3.4.1.5Karakterleri döngüyle dolaşmak
Bir dizginin bütün karakterlerine tek tek bakmak için for döngüsü kullanılır. Döngü değişkeni 0'dan başlar ve uzunluğa ulaşmadan durur. Sunumdaki program kullanıcıdan bir kelime alır ve bu kelimenin tek sıralı harflerini yazar; döngü değişkeni her turda 2 artar:
Scanner scanner = new Scanner(System.in);
System.out.print("Bir kelime giriniz: ");
String kelime = scanner.nextLine();
for (int i = 0; i < kelime.length(); i += 2) {
char harf = kelime.charAt(i);
System.out.println((i + 1) + ". harf: " + harf);
}
scanner.close();Program Scanner ile klavyeden bir satır okur. Scanner kullanmak için dosyanın başına import java.util.Scanner; satırı eklenir; okuma metotlarının ayrıntısı kullanıcı girişi kesimindedir. print, println'den farklı olarak alt satıra geçmez. Bu yüzden kullanıcının yazdığı kelime istemle aynı satırda görünür. Kullanıcı Giresun yazıp Enter'a bastığında çıktı şöyle olur:
Bir kelime giriniz: Giresun
1. harf: G
3. harf: r
5. harf: s
7. harf: nTablo 3.4.2 döngünün her turunu verir. "Giresun" yedi karakterlidir; indisleri 0'dan 6'ya kadar gider. Döngü değişkeni 0, 2, 4, 6 değerlerini alır. Ekrana yazılan sıra numarası insanların alıştığı biçimde 1'den başlasın diye i + 1 yazdırılır. Buradaki parantez gereklidir: (i + 1) + ". harf: " önce toplamayı yapar. Bu satırda parantez olmasa da sonuç aynı çıkardı, çünkü ifade bir sayıyla başlar. Yine de parantez, toplamanın birleştirmeden önce yapıldığını açıkça gösterir. "Harf " + i + 1 gibi dizgiyle başlayan bir ifadede ise parantezsiz yazım i = 0 iken Harf 1 yerine Harf 01 üretir.
| Tur | i | i < 7 | charAt(i) | Yazılan satır |
|---|---|---|---|---|
| 1 | 0 | doğru | 'G' | 1. harf: G |
| 2 | 2 | doğru | 'r' | 3. harf: r |
| 3 | 4 | doğru | 's' | 5. harf: s |
| 4 | 6 | doğru | 'n' | 7. harf: n |
| — | 8 | yanlış | — | döngü biter |
Son satır önemlidir. i 8 olduğunda koşul yanlış çıkar ve charAt(8) hiç çağrılmaz. Koşul i < kelime.length() biçiminde yazıldığı için döngü her uzunlukta güvenle durur. Kelime çift uzunlukta olsaydı, örneğin "Ordu", döngü 0 ve 2 indislerini okur ve i = 4'te biterdi. Boş bir satır girilirse uzunluk 0'dır ve gövde hiç çalışmaz.
Döngü sonunda scanner.close() çağrılır. Bu çağrı Scanner'ın kullandığı kaynağı serbest bırakır. Klavyeden okuyan bir Scanner kapatıldığında System.in da kapanır ve programın geri kalanında klavyeden yeniden okunamaz. Bu yüzden close() ancak bütün okumalar bittikten sonra çağrılır.
3.4.2Dizgi metotları
String sınıfı metinle ilgili gündelik işlerin çoğu için hazır metot sunar: bir kelimeyi aramak, metnin bir parçasını almak, harfleri büyütmek, boşlukları temizlemek, metni parçalara bölmek. Bu metotların hepsi bir dizgi nesnesi üzerinden, nokta ile çağrılır: s.length(), s.indexOf("a"). Ortak bir özellikleri daha var: hiçbiri üzerinde çağrıldığı dizgiyi değiştirmez. Sonuç her zaman yeni bir değer olarak döner.
3.4.2.1Temel metotlar
Tablo 3.4.3 en sık kullanılan metotları s = "Java Programlama" dizgisi üzerinde gösterir. Bu dizgi 16 karakterlidir; dördüncü indisteki boşluk da bir karakter sayılır. Tablodaki bütün sonuçlar JDK 21 ile çalıştırılarak alınmıştır.
| Metot | Ne yapar? | Örnek ve sonuç |
|---|---|---|
length() | Karakter sayısını döndürür. | s.length() → 16 |
charAt(i) | i indisindeki karakteri (char) döndürür. | s.charAt(0) → 'J' |
indexOf(str) | Alt dizginin ilk başladığı indis; bulunamazsa −1. | s.indexOf("Programlama") → 5s.indexOf("C++") → −1 |
indexOf(str, bas) | Aramaya bas indisinden başlar. | s.indexOf("a", 4) → 10 |
lastIndexOf(str) | Son geçtiği yerin indisi. | s.lastIndexOf('a') → 15 |
substring(i1, i2) | i1 dahil, i2 hariç aradaki alt dizgi. | s.substring(5, 12) → "Program" |
substring(i1) | i1'den sona kadar. | s.substring(5) → "Programlama" |
toLowerCase() | Küçük harfli yeni dizgi. | "java programlama" |
toUpperCase() | Büyük harfli yeni dizgi. | "JAVA PROGRAMLAMA" |
Alt dizgi (substring), bir dizginin ardışık karakterlerinden oluşan parçasıdır. s.substring(a, b), s'nin a indisinden başlayıp b - 1 indisinde biten parçasını döndürür. Parçanın uzunluğu b - a'dır.
indexOf bir arama metodudur. Dizgiyi baştan sona tarar ve aranan metnin ilk geçtiği yerin başlangıç indisini verir. Aranan metin hiç geçmiyorsa −1 döndürür. −1 geçerli bir indis olamayacağı için “bulunamadı” anlamını güvenle taşır. Bu yüzden arama sonucu kullanılmadan önce çoğu zaman if (s.indexOf(x) != -1) biçiminde denetlenir. Metodun iki parametreli sürümü aramaya belli bir indisten başlar. s.indexOf("a", 4) çağrısı 1 ve 3'teki a harflerini atlar ve ilk olarak indis 10'daki a'yı bulur. lastIndexOf ise dizgiyi sondan başa tarar ve son eşleşmeyi verir. indexOf ve lastIndexOf hem dizgi hem tek karakter ('a') kabul eder.
substring metodunun en çok karıştırılan yanı bitiş indisinin hariç olmasıdır. Bu kuralın iki pratik yararı vardır. Birincisi, parçanın uzunluğu iki indisin farkıdır: substring(5, 12) 12 − 5 = 7 karakter verir. İkincisi, ardışık parçalar kolayca yan yana gelir: s.substring(0, k) + s.substring(k) her k için s'nin kendisini verir. Tek parametreli substring(5) ise substring(5, s.length()) ile aynıdır.
Geçersiz aralık verilirse substring de StringIndexOutOfBoundsException fırlatır. Bitiş, uzunluktan büyük olamaz; başlangıç da bitişten büyük olamaz. "Java".substring(2, 6) çağrısının iletisi aralığı matematikteki yarı açık aralık gösterimiyle verir:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
Range [2, 6) out of bounds for length 4İletideki [2, 6) yazımı “2 dahil, 6 hariç” demektir. Bu gösterim substring'in kuralını da özetler. Buna karşılık s.substring(s.length()) geçerlidir ve boş dizgi döndürür. Başlangıç ile bitişin eşit olması boş bir aralık anlamına gelir.
3.4.2.2indexOf ve substring adım adım
Sunumdaki izleme slaytı tablodaki metotları sırayla çalıştırır ve her değişkenin aldığı değeri karakter kutuları üzerinde gösterir:
String str = "Java Programlama";
int indeks = str.indexOf("Programlama");
int uzunluk = str.length();
String alt = str.substring(5, 12);
String son = str.substring(5);
String kucuk = str.toLowerCase();
String buyuk = str.toUpperCase();Kodda hiçbir şey yazdırılmaz; önemli olan her satırdan sonra değişkenlerin aldığı değerlerdir. Tablo 3.4.4 bu değerleri sırayla verir.
| Satır | Değişken | Değer | Açıklama |
|---|---|---|---|
| 2 | indeks | 5 | "Programlama" 5. indiste başlar |
| 3 | uzunluk | 16 | indisler 0 … 15 |
| 4 | alt | "Program" | indis 5 … 11; 12 − 5 = 7 karakter |
| 5 | son | "Programlama" | indis 5'ten sona kadar |
| 6 | kucuk | "java programlama" | yeni dizgi; str değişmedi |
| 7 | buyuk | "JAVA PROGRAMLAMA" | yeni dizgi; str değişmedi |
Şekil 3.4.4 iki önemli adımı çizimle gösterir. Vurgulu kutular, metodun eriştiği karakterlerdir. substring(5, 12) adımında 12 numaralı kutu (l) vurgulanmaz; bitiş indisinin hariç olduğu çizimde de görülür.
indexOf("Programlama") eşleşmenin başladığı indisi, 5'i verir. (b) substring(5, 12) 5–11 indislerini alır: "Program".Son iki satırda toLowerCase() ve toUpperCase() çağrılır. İkisi de yeni bir dizgi döndürür ve bu dizgi başka bir değişkene atanır. str değişkeni program boyunca "Java Programlama" olarak kalır. Bir metodun “dizgiyi değiştirdiği” izlenimi yanıltıcıdır; bunun nedeni değiştirilemezlik kesiminde açıklanıyor.
Bir e-posta adresini kullanıcı adı ve alan adı diye ikiye ayıralım. Adres e = "ogrenci@giresun.edu.tr" olsun. @ işaretinin yeri int at = e.indexOf('@'); ile bulunur. "ogrenci" yedi harflidir; harfler 0–6 indislerini kaplar ve @ indis 7'dedir. Kullanıcı adı @'dan önceki kısımdır: e.substring(0, at) = e.substring(0, 7) = ogrenci. Bitiş indisi hariç olduğu için @ parçaya girmez. Alan adı @'dan sonraki kısımdır: e.substring(at + 1) = e.substring(8) = giresun.edu.tr. Burada + 1 unutulsaydı sonuç @giresun.edu.tr olurdu. Yöntem adres uzunluğundan bağımsızdır; indisler elle sayılmaz, indexOf ile bulunur.
3.4.2.3Diğer yararlı metotlar
Temel metotların yanında birkaç metot daha gündelik kodda sık geçer. Sunumdaki örnek bunları bir arada dener:
String s = " Merhaba Dünya ";
System.out.println("[" + s.trim() + "]");
System.out.println("[" + s.replace('a', 'e') + "]");
String[] meyveler = "elma,armut,kiraz".split(",");
System.out.println(meyveler.length);
System.out.println(meyveler[1]);
System.out.println(String.join(" - ", meyveler));
System.out.println("ab".repeat(3)); // Java 11+
System.out.println("".isEmpty());
System.out.println(" ".isBlank()); // Java 11+
char[] dizi = "Java".toCharArray();
System.out.println(dizi.length + " " + dizi[0]);
System.out.println("Giresun".replace("sun", "SUN"));
System.out.println(String.valueOf(3.5) + 1);
System.out.println(Integer.parseInt("42") + 1);[Merhaba Dünya]
[ Merhebe Dünye ]
3
armut
elma - armut - kiraz
ababab
true
true
4 J
GireSUN
3.51
43Satırları sırayla açıklayalım:
trim()dizginin başındaki ve sonundaki boşlukları atar. Aradaki boşluklara dokunmaz:" Merhaba Dünya "→ [Merhaba Dünya]. Köşeli parantezler boşlukların görünmesi için eklendi. Kullanıcı girdisini işlemeden öncetrim()çağırmak yaygın bir alışkanlıktır. Java 11 ile gelenstrip()aynı işi Unicode'daki bütün boşluk karakterleri için yapar.replace('a', 'e')bütünaharflerinieyapar: [ Merhebe Dünye ]. Yalnız ilk eşleşmeyi değil, hepsini değiştirir. Boşluklar korunmuştur, çünküreplaceyeni dizgiyis'nin kendisinden üretir. Önceki satırdakitrim()sonucu hiçbir yere atanmamıştı.split(",")dizgiyi verilen ayraçtan böler ve parçaları birString[]dizisi olarak döndürür."elma,armut,kiraz"üç parçaya ayrılır;meyveler.length3,meyveler[1]ise armut'tur.String.join(" - ", meyveler)bölmenin tersini yapar: parçaları aralarına ayraç koyarak birleştirir. Sonuç elma - armut - kiraz'dır."ab".repeat(3)dizgiyi üç kez yan yana yazar: ababab. Bu metot Java 11'de eklendi.isEmpty()uzunluk 0 isetruedöndürür.isBlank()(Java 11) dizgi boşsa ya da yalnız boşluk karakterlerinden oluşuyorsatruedöndürür." "boş değildir ama boşluktan ibarettir:isEmpty()false,isBlank()trueverir.toCharArray()dizginin karakterlerini yeni birchardizisine kopyalar. Dizinin uzunluğu 4, ilk öğesi'J''dir. Dizi değiştirilebilir; bu yüzden karakterler üzerinde yer değiştirme gibi işler dizi üzerinde yapılıp sonuçnew String(dizi)ile geri çevrilebilir.replacetek karakterle sınırlı değildir; dizgi de alır:"Giresun".replace("sun", "SUN")→ GireSUN.- Son iki satır sayı ile dizgi arasındaki dönüşümü gösterir.
String.valueOf(3.5)bir dizgidir,"3.5"; ona 1 eklemek birleştirmedir ve sonuç 3.51 olur.Integer.parseInt("42")ise dizgiyi gerçek birint'e çevirir; 1 eklenince 43 çıkar. Ondalıklı sayılar içinDouble.parseDoublekullanılır.
parseInt yalnız rakamlardan (ve başta isteğe bağlı bir işaretten) oluşan bir dizgiyi kabul eder. Boşluk ya da harf içeren bir metin verilirse NumberFormatException fırlatır:
Exception in thread "main" java.lang.NumberFormatException:
For input string: " 12 "Bu yüzden kullanıcıdan ya da bir dosyadan gelen sayı metni önce trim() ile temizlenir: Integer.parseInt(metin.trim()). Bölme ile birlikte kullanıldığında bu adım daha da önem kazanır. " 3, 12 " gibi bir satır virgülden bölününce parçaların başında ve sonunda boşluk kalır.
split'in iki inceliği daha vardır. Ardışık iki ayraç arasında boş bir parça oluşur. Dizginin sonundaki boş parçalar ise atılır. Ayrıca split'in parametresi bir düzenli ifadedir (regular expression): metin aramada kullanılan bir kalıp dilidir. " +" kalıbı “bir ya da daha çok boşluk” anlamına gelir:
System.out.println(Arrays.toString("a b c".split(" ")));
System.out.println(Arrays.toString("a b c".split(" +")));
System.out.println(Arrays.toString("a,,b,".split(",")));[a, b, , c]
[a, b, c]
[a, , b]İlk satırda iki ardışık boşluk boş bir parça üretir ve dizi dört öğeli olur. İkinci satır birden çok boşluğu tek ayraç sayar. Üçüncü satırda ortadaki boş parça kalır, sondaki atılır; dizi üç öğelidir. Nokta (.) ve dikey çizgi (|) düzenli ifadelerde özel anlam taşır. Bu yüzden "a.b".split(".") beklenen sonucu vermez; noktadan bölmek için split("\\.") yazılır.
3.4.3Değiştirilemezlik
Önceki kesimlerde her metodun yeni bir değer döndürdüğü birkaç kez vurgulandı. Bu bir rastlantı değil, String sınıfının temel tasarım kararıdır. Bu kesim kararın ne anlama geldiğini, bellekte nasıl göründüğünü ve doğurduğu maliyeti anlatır. Türkçe metinlerde büyük-küçük harf dönüşümünün tuzakları ve çok sayıda birleştirme için tasarlanan StringBuilder sınıfı da bu kesimdedir.
3.4.3.1Değiştirilemez nesne
Değiştirilemez (immutable) nesne, oluşturulduktan sonra durumu hiç değişmeyen nesnedir. String nesneleri değiştirilemezdir: bir dizginin karakterleri ve uzunluğu nesne oluştuğu anda belirlenir ve sonra değişmez. toUpperCase, replace, substring, concat ve + orijinal nesneye dokunmaz; sonucu yeni bir nesne olarak döndürür.
Bir benzetme yardımcı olur. Dizgi bir taş tablete benzer. Tabletteki yazı kazınıp düzeltilemez. Değişiklik gerekirse yeni bir tablet yazılır ve eskisinin yerine konur. Sunumdaki örnek bu davranışı gösterir:
String s = "Giresun Üniversitesi";
s.toUpperCase(); // sonuç kaybolur
System.out.println(s);
s = s.toUpperCase(); // sonuç s'ye atanır
System.out.println(s);Giresun Üniversitesi
GİRESUN ÜNİVERSİTESİİkinci satırdaki s.toUpperCase(); çağrısı çalışır ve büyük harfli yeni bir dizgi üretir. Ancak sonuç hiçbir değişkene atanmaz ve kaybolur. Bu yüzden ilk println orijinal metni yazar. Dördüncü satırda sonuç s'ye atanır. Burada da eski nesne değişmez; s değişkeni artık yeni nesneyi gösterir. Değişen nesne değil, değişkenin gösterdiği yerdir.
Şekil 3.4.5 bu iki durumu bellek çizimiyle verir. Sol bölümdeki yığın (stack), metodun yerel değişkenlerini tutar; s burada durur. Sağdaki heap ise nesnelerin yaşadığı bölgedir. s değişkeninden çıkan ok, gösterdiği nesneyi işaret eder.
toUpperCase() yeni bir nesne üretir, ama sonuç atanmadığı için s eski nesneyi göstermeyi sürdürür. (b) İkinci çağrı yine yeni bir nesne üretir ve bu kez referansı s'ye atanır. (c) s artık büyük harfli nesneyi gösterir; eski nesne değişmeden durur.İlk çağrının ürettiği nesneyi hiçbir değişken göstermez. Kimsenin göstermediği bu tür nesneler çöp toplayıcıya (garbage collector) kalır. Çöp toplayıcı, hiçbir referansın göstermediği nesnelerin belleğini zamanı geldiğinde geri alan JVM bileşenidir. Programcının bu belleği elle serbest bırakması gerekmez.
Çıktının ikinci satırındaki GİRESUN ÜNİVERSİTESİ, noktalı büyük İ harfleriyle yazılmıştır. Bu sonuç Türkçe yerel ayarlı bir bilgisayarda alınır. İngilizce ayarlı bir bilgisayarda aynı program GIRESUN ÜNIVERSITESI yazar. Nedeni bir sonraki alt kesimde açıklanıyor.
Değiştirilemezliğin bir bedeli vardır: her değişiklik yeni bir nesne demektir. Java'yı tasarlayanlar bu bedeli şu yararlar için göze almıştır:
- Güvenli paylaşım. Aynı dizgiyi gösteren iki değişken birbirini bozamaz. Bir metoda verilen dizginin o metot içinde değiştirilmesinden korkmak gerekmez.
- Dizgi havuzu. İçeriği aynı dizgi sabitleri tek bir nesneyi paylaşabilir. Paylaşım ancak nesne değişmiyorsa güvenlidir. Havuz karşılaştırma kesiminde ayrıntılı işleniyor.
- İş parçacıkları arasında güvenlik. Aynı anda çalışan program parçaları değişmeyen bir nesneyi ek önlem almadan ortak kullanabilir.
Dizgi metodunun sonucunu atamayı unutmak: ad.trim();, ad.toUpperCase(); ya da ad.replace(" ", ""); satırları ad'ı değiştirmez. Derleyici uyarı vermez, program çalışır ama hiçbir şey olmaz. Doğru yazım ad = ad.trim(); biçimindedir. Aynı nedenle s.charAt(0) = 'j'; gibi bir karakteri yerinde değiştirme girişimi derlenmez.
3.4.3.2Türkçe harfler ve yerel ayar
Yerel ayar (locale), bir dil ve bölgeye özgü biçim kurallarının bütünüdür: büyük-küçük harf dönüşümü, ondalık ayırıcı, binlik ayırıcı, tarih biçimi. Java'da java.util.Locale sınıfıyla temsil edilir. Türkiye Türkçesi için yerel ayar tr-TR'dir.
Türkçe alfabede i harfinin noktalı ve noktasız iki biçimi vardır ve ikisinin de büyük harfi ayrıdır: i ↔ İ, ı ↔ I. İngilizcede ise yalnız i ↔ I çifti bulunur. Parametresiz toUpperCase() ve toLowerCase() bilgisayarın varsayılan yerel ayarını kullanır. Aynı program bu yüzden farklı makinelerde farklı sonuç verebilir:
import java.util.Locale;
String s = "Giresun Üniversitesi";
System.out.println(s.toUpperCase());
System.out.println(s.toUpperCase(Locale.ROOT));
System.out.println("TITLE".toLowerCase());
System.out.println("TITLE".toLowerCase().equals("title"));
System.out.println("TITLE".toLowerCase(Locale.ROOT));GİRESUN ÜNİVERSİTESİ
GIRESUN ÜNIVERSITESI
tıtle
false
titleÇıktı Türkçe ayarlı bir bilgisayarda alınmıştır. İlk satırda varsayılan ayar Türkçe olduğu için i harfleri İ olur. İkinci satırda Locale.ROOT verilir. Bu, dilden bağımsız, temel Unicode kurallarını kullanan yerel ayardır; i harfleri I olur. Üçüncü satır tuzağı gösterir: "TITLE" Türkçe kurallarla küçültülünce I harfleri ı olur ve sonuç tıtle çıkar. Dördüncü satırdaki karşılaştırma bu yüzden false verir. Son satırda Locale.ROOT ile beklenen title elde edilir. Tablo 3.4.5 dört harfin iki ayardaki dönüşümünü özetler.
| Dönüşüm | Türkçe (tr) | ROOT / İngilizce |
|---|---|---|
i → büyük | İ | I |
I → küçük | ı | i |
ı → büyük | I | I |
İ → küçük | i | i̇ (i + birleşen nokta; iki karakter) |
Tablonun son satırı daha az bilinen bir durumdur. İ harfi Türkçe dışındaki kurallarla küçültülünce tek bir i değil, i ile üstüne konan bir “birleşen nokta” karakteri çıkar. Görünüşte fark yoktur ama dizginin uzunluğu artar. "İSTANBUL".toLowerCase(Locale.ROOT) 9 karakterlik bir dizgi verir; "İki".toLowerCase(Locale.ROOT).equals("iki") ise false'tur.
Pratik kural şöyledir. Program içi metinlerde, yani komut adı, dosya uzantısı, protokol anahtarı gibi makinenin okuyacağı metinlerde Locale.ROOT kullanılır. Kullanıcıya gösterilecek Türkçe metinlerde ise Türkçe ayar açıkça verilir: metin.toUpperCase(Locale.forLanguageTag("tr-TR")). Böylece program hangi bilgisayarda çalışırsa çalışsın aynı sonucu üretir. Locale sınıfı java.util paketindedir ve import java.util.Locale; satırını gerektirir.
Kullanıcı girdisini girdi.toLowerCase().equals("exit") ile denetlemek. Türkçe ayarlı bir bilgisayarda kullanıcı EXIT yazarsa sonuç "exıt" olur ve koşul tutmaz. Hata yalnız bazı makinelerde ortaya çıktığı için bulunması zordur. Büyük-küçük harf farkını yok saymak için equalsIgnoreCase ya da toLowerCase(Locale.ROOT) kullanılır.
3.4.3.3Döngüde birleştirmenin maliyeti
Değiştirilemezliğin bedeli en çok döngüde birleştirme yapılırken hissedilir. Sunumdaki kodun ilk bölümü 1'den 5'e kadar sayıları bir dizgide toplar:
String sonuc = "";
for (int i = 1; i <= 5; i++) {
sonuc += i + " "; // her turda yeni String
}
System.out.println(sonuc);
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append(i).append(' '); // aynı nesne büyür
}
String metin = sb.toString();
System.out.println(metin);
StringBuilder ters = new StringBuilder("Giresun");
ters.reverse();
System.out.println(ters);
StringBuilder k = new StringBuilder("Java");
k.insert(0, ">> ").append('!');
k.setCharAt(3, 'j');
System.out.println(k + " " + k.length());1 2 3 4 5
1 2 3 4 5
nuseriG
>> java! 8İlk döngüde sonuc += i + " "; satırı her turda yeni bir String nesnesi üretir. Eski içerik ve yeni parça bu nesneye kopyalanır, eski nesne de çöpe kalır. Bir turda kopyalanan karakter sayısı, o ana kadar biriken metnin uzunluğu kadardır. Her tur bir karakter ekliyorsa kopyalanan toplam karakter sayısı 1 + 2 + … + n = n(n + 1)/2 olur, yani yaklaşık n²/2. Örneğin bin turluk bir döngüde 500.500 karakter kopyalanır. Bu büyüme O(n²) ile gösterilir: tur sayısı iki katına çıkınca iş dört katına çıkar.
Beş turluk bu örnekte fark hissedilmez. Her tur iki karakter (rakam ve boşluk) ekler; toplam 2 + 4 + 6 + 8 + 10 = 30 karakter kopyalanır. Ama on binlerce satırlık bir rapor ya da büyük bir dosyanın içeriği bu yolla kurulursa program belirgin biçimde yavaşlar.
3.4.3.4StringBuilder
StringBuilder, değiştirilebilir (mutable) bir karakter tamponudur (buffer). İçinde karakterleri tutan bir dizi ve bu dizinin kaç hücresinin dolu olduğunu gösteren bir uzunluk bulunur. append, insert, reverse, setCharAt ve deleteCharAt gibi metotlar yeni nesne üretmez; aynı nesnenin içeriğini değiştirir. İş bitince toString() ile bir String elde edilir.
Kodun ikinci bölümü aynı metni StringBuilder ile kurar. sb.append(i).append(' ') her turda tamponun sonuna bir sayı ve bir boşluk ekler. Tampon tek bir nesnedir ve gerektikçe büyür. Döngü bitince sb.toString() içerikten bir String üretir. İki yöntemin çıktısı aynıdır: 1 2 3 4 5 (sonda bir boşlukla).
append metodu tamponun kendisini döndürür. Bu yüzden çağrılar zincir gibi art arda yazılabilir: sb.append(i).append(' '). İlk append sb'yi döndürür, ikinci append yine sb üzerinde çalışır. append'in her ilkel tip ve String için ayrı sürümü vardır; sayı, karakter, mantıksal değer hepsi metne çevrilerek eklenir.
Kodun son bölümü diğer metotları dener. new StringBuilder("Giresun") içeriği "Giresun" olan bir tampon oluşturur. reverse() tamponu yerinde ters çevirir ve nuseriG yazılır. println'e bir StringBuilder verildiğinde toString() kendiliğinden çağrılır. Son örnekte "Java" tamponunun başına insert(0, ">> ") ile üç karakter eklenir, sonuna '!' konur ve indis 3'teki J setCharAt ile j yapılır. Sonuç >> java!, uzunluk 8'dir. String ile bu işlem mümkün değildir; karakteri değiştirmek için her seferinde yeni bir dizgi kurmak gerekir.
Sunumdaki izleme slaytı tamponun içini hücre hücre gösterir:
StringBuilder sb = new StringBuilder("Java");
sb.append(' ').append(8);
sb.insert(0, ">> ");
sb.setCharAt(3, 'j');
sb.deleteCharAt(sb.length() - 1);
sb.append("programlama dili");
String s = sb.toString();
System.out.println(s + " " + sb.length() + " " + sb.capacity());>> java programlama dili 24 42Bir StringBuilder'ın iki ölçüsü vardır. Uzunluk (length()) dolu hücre sayısıdır. Kapasite (capacity()) ise içerideki dizinin toplam hücre sayısıdır. Bir dizgiden oluşturulan tamponun kapasitesi, dizginin uzunluğuna 16 eklenerek bulunur. "Java" için bu 4 + 16 = 20'dir. Boş bir new StringBuilder()'ın kapasitesi ise 16'dır. Tablo 3.4.6 her satırdan sonraki durumu verir; değerler length() ve capacity() yazdırılarak doğrulanmıştır.
| Satır | İçerik | Uzunluk | Kapasite | Açıklama |
|---|---|---|---|---|
new StringBuilder("Java") | [Java] | 4 | 20 | 16 + 4 |
append(' ').append(8) | [Java 8] | 6 | 20 | sona eklenir, nesne aynı |
insert(0, ">> ") | [>> Java 8] | 9 | 20 | eski karakterler 3 hücre sağa kayar |
setCharAt(3, 'j') | [>> java 8] | 9 | 20 | indis 3 yerinde değişir |
deleteCharAt(length() - 1) | [>> java ] | 8 | 20 | sondaki 8 silinir |
append("programlama dili") | [>> java programlama dili] | 24 | 42 | 24 > 20: tampon büyür |
toString() | — | 24 | 42 | içerikten yeni bir String |
insert ve deleteCharAt ucuz işlemler değildir. Başa bir şey eklemek için sonraki bütün karakterlerin sağa kaydırılması gerekir; ortadan silmek de sola kaydırma ister. Bu kaydırmalar tampon uzunluğuyla orantılı iş yapar. append ise yer olduğu sürece yalnız sona yazar ve sabit zamanda biter.
Son append 16 karakter ekler ve uzunluk 24'e çıkar. 24 karakter 20 hücreye sığmaz. Bu durumda StringBuilder daha büyük yeni bir dizi ayırır, eski içeriği oraya kopyalar ve eski diziyi bırakır. JDK'daki kural yeni kapasiteyi eskisinin iki katının iki fazlası olarak seçer: 2 × 20 + 2 = 42. Sunumdaki (20 + 1) × 2 yazımı da aynı sayıyı verir. Gereken uzunluk bundan da büyükse doğrudan o uzunluk alınır. Şekil 3.4.6 büyüme anını gösterir.
StringBuilder tamponu. (a) insert(0, ">> ") sonrası: eski karakterler üç hücre sağa kaymıştır. (b) 24 karakter 20 hücreye sığmaz; kapasite 42'ye çıkar ve içerik yeni diziye kopyalanır.Büyüme sırasında kopyalama yapıldığı hâlde StringBuilder neden hızlıdır? Kapasite her büyümede yaklaşık iki katına çıkar. Boş bir tampona karakterler tek tek eklendiğinde kapasite 16, 34, 70, 142, 286, 574, 1150 diye ilerler. Bin karakter eklemek için yalnız altı büyüme gerekir. Bu büyümelerde kopyalanan toplam karakter sayısı, eklenen karakter sayısının birkaç katını geçmez. Bu yüzden n karakter eklemenin toplam maliyeti O(n)'dir; += ile yapılan birleştirmenin O(n²) maliyetiyle karşılaştırıldığında fark çok büyüktür.
Bu, + operatörünün hiç kullanılmaması gerektiği anlamına gelmez. Birkaç parçalık birleştirmelerde + okunaklıdır ve yeterince hızlıdır; Java derleyicisi tek bir ifadedeki birleştirmeleri zaten verimli biçimde çevirir. StringBuilder, bir döngü içinde çok sayıda parça birleştirilirken tercih edilir. Yaklaşık uzunluk baştan biliniyorsa new StringBuilder(1000) gibi bir başlangıç kapasitesi vermek büyüme sayısını azaltır.
3.4.4Kullanıcı girişi: Scanner
Bir program çoğu zaman kullanıcıdan bilgi ister: bir ad, bir yaş, bir cümle. Klavyeden gelen her şey önce karakter olarak gelir. Bu karakterlerin kelimeye, satıra ya da sayıya dönüştürülmesi Scanner sınıfının işidir. Okuma metotları birbirine benzese de davranışları ince noktalarda ayrılır. Bu ayrımlar bilinmezse programlar kullanıcının sorusunu atlamak gibi tuhaf hatalar verir.
3.4.4.1Scanner ve okuma metotları
java.util.Scanner, bir kaynaktaki metni parçalara ayırarak okuyan sınıftır. new Scanner(System.in) klavyeden okuyan bir nesne oluşturur. Klavyede yazılan satır Enter'a basılınca bir girdi tamponuna girer; satır sonu karakteri (⏎) de tampona eklenir. Scanner tamponda bir imleç tutar ve her okuma imleci okunan kısmın sonuna taşır.
Dört okuma metodu sık kullanılır:
next()önce baştaki boşlukları (boşluk, sekme, satır sonu) atlar, sonra bir sonraki boşluğa kadar okur. Sonuç tek bir kelimedir.nextLine()imlecin bulunduğu yerden satır sonuna kadar her şeyi okur. Boşlukları atlamaz. Satır sonu karakterini tüketir ama sonuca katmaz.nextInt()venextDouble()baştaki boşlukları atlar ve bir sayı okur. Sayının arkasındaki satır sonunu tamponda bırakır. Sayı yerine harf girilirseInputMismatchExceptionfırlatır.
Klavyeden okuyan bir program dosyanın başında import java.util.Scanner; satırını içerir. Okumalar bitince klavye.close() çağrılır. Sunumdaki örnek bir satırı önce iki kelime, sonra satırın geri kalanı olarak okur:
Scanner klavye = new Scanner(System.in);
System.out.println("bir metin giriniz");
String girdi = klavye.next();
System.out.println(girdi); // bilgisayar
girdi = klavye.next();
System.out.println(girdi); // ve
girdi = klavye.nextLine();
System.out.println(girdi); // programlamaya girişKullanıcı şu satırı yazar ve Enter'a basar:
bilgisayar ve programlamaya girişProgram şunu yazar:
bir metin giriniz
bilgisayar
ve
programlamaya girişÇıktının son satırındaki baştaki boşluk dikkat çeker. “ve” kelimesinden sonra gelen boşluk satırın bir parçasıdır ve nextLine() boşlukları atlamaz. Bu yüzden okunan metin " programlamaya giriş" olur.
3.4.4.2next() ve nextLine() adım adım
Tablo 3.4.7 her okumada imlecin nereden nereye ilerlediğini verir. Tampondaki karakterlerin indisleri 0'dan başlar: "bilgisayar" 0–9, ilk boşluk 10, "ve" 11–12, ikinci boşluk 13, kalan metin 14–32 indislerini kaplar ve satır sonu 33'tedir.
| Çağrı | İmleç önce | Okunan | İmleç sonra | Açıklama |
|---|---|---|---|---|
next() | 0 | "bilgisayar" | 10 | ilk boşluğa kadar okur; boşluk tamponda kalır |
next() | 10 | "ve" | 13 | önce 10'daki boşluğu atlar |
nextLine() | 13 | " programlamaya giriş" | 34 | boşluğu atlamaz; ⏎'yi tüketir, sonuca katmaz |
Şekil 3.4.7 ikinci ve üçüncü okumanın sonunda tamponun durumunu gösterir. Okunmuş karakterler soluk, imlecin yeri bir üçgenle işaretlidir.
next() boşluğu atlayıp "ve"'yi okur; imleç yine bir boşluğun önündedir. (b) nextLine() boşluğu atlamadan satır sonuna kadar okur ve ⏎'yi tüketir.Programcı açısından sonuç şudur: tek kelimelik bir yanıt (ad, şehir, komut) için next(), boşluk içerebilen bir yanıt (ad soyad, adres, cümle) için nextLine() kullanılır. next() ile ad soyad okunursa yalnız ad alınır, soyad tamponda kalır ve bir sonraki okumaya karışır.
3.4.4.3nextInt() sonrası nextLine() tuzağı
Sayı okuyan metotlarla nextLine() art arda kullanıldığında yeni başlayanların neredeyse hepsinin karşılaştığı bir hata ortaya çıkar. Sunumdaki program önce yaşı, sonra adı sorar:
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 + "] " + yas);Kullanıcı 19 yazıp Enter'a basar. Program adı sormadan biter:
Yaş: 19
Ad: [] 19Neden böyle olduğu tampona bakınca anlaşılır (Şekil 3.4.8). Tamponda 19⏎ vardır. nextInt() yalnız 19'u okur ve imleci ⏎'nin önünde bırakır. Ardından gelen nextLine() görevini yerine getirir: imleçten satır sonuna kadar okur. İmleç ile satır sonu arasında hiçbir karakter olmadığı için sonuç boş dizgidir. nextLine() ⏎'yi tüketir ve hemen döner. Kullanıcı daha adını yazmaya fırsat bulamadan ad değişkeni "" olmuştur.
nextInt() 19'u okuduktan sonra imleç satır sonu karakterinin önünde bekler.Çözüm, sayıyı okuduktan sonra artakalan satır sonunu ayrı bir nextLine() çağrısıyla tüketmektir:
int yas = klavye.nextInt();
klavye.nextLine(); // ⏎'yi at
System.out.print("Ad: ");
String ad = klavye.nextLine();Yaş: 19
Ad: Ayşe
[Ayşe] 19Araya eklenen klavye.nextLine(); çağrısının sonucu kullanılmaz; tek görevi ⏎'yi tamponda temizlemektir. Bundan sonraki nextLine() kullanıcının yeni satırını bekler. İkinci bir çözüm, bütün girdiyi nextLine() ile okuyup sayıları kendisi çevirmektir: int yas = Integer.parseInt(klavye.nextLine().trim());. Bu yöntemde tamponda hiçbir zaman artık satır sonu kalmaz.
Kullanıcıdan önce yaş, sonra ad-soyad isteyen ve sonucu printf ile yazan program, tuzağa düşmeden şöyle yazılır:
Scanner k = new Scanner(System.in);
System.out.print("Yaş: ");
int yas = k.nextInt();
k.nextLine(); // satır sonunu at
System.out.print("Ad: ");
String ad = k.nextLine();
System.out.printf("Ad: %s, Yaş: %d%n", ad, yas);Kullanıcı 20 ve Ali Veli girdiğinde konsol şöyle görünür:
Yaş: 20
Ad: Ali Veli
Ad: Ali Veli, Yaş: 20nextInt() 20'yi okur, ⏎ tamponda kalır. k.nextLine(); bu ⏎'yi tüketir. Son nextLine() kullanıcının yeni satırını bekler ve "Ali Veli"'yi boşluğuyla birlikte okur. Ad soyad için next() kullanılsaydı yalnız "Ali" okunurdu. printf satırı biçimli çıktı kesiminde açıklanıyor.
3.4.5Dizgileri karşılaştırmak
İki dizginin aynı olup olmadığını sormak basit görünür. Ama Java'da bu sorunun iki ayrı anlamı vardır: “iki değişken aynı nesneyi mi gösteriyor?” ve “iki nesnenin içeriği aynı mı?”. İlk soru == ile, ikincisi equals() ile sorulur. Bu kesim iki sorunun farkını, farkı bazen gizleyen dizgi havuzunu ve sıralama için kullanılan compareTo metodunu anlatır.
3.4.5.1== ile karşılaştırma
int gibi ilkel tiplerde == iki değeri karşılaştırır. String değişkenleri ise değer değil referans tutar. Bu yüzden == iki dizginin referanslarını, yani bellekteki adreslerini karşılaştırır, içeriklerini değil. İki değişken aynı nesneyi gösteriyorsa sonuç true'dur. İçerik aynı olsa bile nesneler farklıysa sonuç false'tur. Sunumdaki örnek:
String str1 = "Giresun";
String str2 = "Giresun";
String str3 = new String("Giresun");
System.out.println(str1 == str2); // true
System.out.println(str1 == "Giresun"); // true
System.out.println(str1 == str3); // false
System.out.println(str3 == "Giresun"); // falsetrue
true
false
falseDört karşılaştırmada içerik hep "Giresun"'dur, ama sonuçların ikisi false'tur. str1 == str2'nin true çıkması bir şans değildir; dizgi havuzunun sonucudur. str3 ise new ile oluşturulmuş ayrı bir nesnedir.
3.4.5.2Dizgi havuzu
Dizgi havuzu (string pool), JVM'in dizgi sabitlerini tuttuğu özel bölgedir. Kaynak kodda içeriği aynı olan bütün dizgi sabitleri havuzdaki tek bir nesneyi paylaşır. new String(...) ise havuza bakmaz; her çağrısında heap'te yeni bir nesne oluşturur.
Havuzun amacı bellek tasarrufudur. Bir programda "Giresun" sabiti yüz farklı yerde geçebilir. Her geçişte yeni bir nesne oluşturmak yerine hepsi aynı nesneyi gösterir. Bu paylaşım ancak dizgiler değiştirilemez olduğu için güvenlidir. Bir yerdeki kod "Giresun" nesnesini değiştirebilseydi öteki doksan dokuz yer de etkilenirdi.
Tablo 3.4.8 sunumdaki havuz izlemesini satır satır verir. Nesneler #1 ve #2 diye numaralandırılmıştır.
| Satır | str1 | str2 | str3 | Açıklama |
|---|---|---|---|---|
String str1 = "Giresun"; | #1 | sabit havuzdaki #1'i verir | ||
String str2 = "Giresun"; | #1 | #1 | aynı sabit: yeni nesne oluşmaz | |
String str3 = new String("Giresun"); | #1 | #1 | #2 | new havuz dışında yeni nesne üretir |
str1 == str2 | #1 ile #1 | true | ||
str1 == "Giresun" | #1 ile #1 | true | ||
str1 == str3 | #1 ile #2 | false | ||
str3 == "Giresun" | #2 ile #1 | false | ||
str1 ve str2 havuzdaki aynı nesneyi, str3 havuz dışındaki ayrı bir nesneyi gösterir.Havuza giren dizgiler yalnız kaynak koddaki sabitlerle sınırlı değildir. Derleme zamanında hesaplanabilen ifadeler de sabit sayılır. "Gire" + "sun" ifadesinin iki yanı da sabittir; derleyici onu doğrudan "Giresun" olarak yazar ve sonuç havuzdaki nesneyi gösterir. Bir yanında değişken bulunan birleştirme ise çalışma zamanında yapılır ve yeni bir nesne üretir. final ile tanımlanıp sabitle başlatılan bir değişken ise derleyici için bir sabittir:
String str1 = "Giresun";
String a = "Gire";
final String fa = "Gire";
System.out.println((a + "sun") == str1); // false
System.out.println((fa + "sun") == str1); // trueintern() metodu bir dizginin havuzdaki eşini döndürür. Havuzda aynı içerikli bir nesne varsa o nesnenin referansı, yoksa dizgi havuza eklenir ve kendisi döner. str3.intern() == str1 bu yüzden true'dur. intern() gündelik kodda nadiren gerekir; varlığı havuzun nasıl çalıştığını anlamaya yardım eder.
Havuz ve değiştirilemezlik adım adım izlenmek istenirse ders sitesindeki “Dizgi Havuzu” simülatörü kullanılabilir. Simülatör, sabitlerin havuza girişini, new String ile oluşan ayrı nesneleri ve StringBuilder tamponunun yerinde değişmesini görsel olarak gösterir.
3.4.5.3equals() ile içerik karşılaştırma
a.equals(b), a ve b dizgilerinin içeriğini karakter karakter karşılaştırır. Uzunluklar ve her konumdaki karakter aynıysa true döndürür. Nesnelerin aynı olup olmadığına bakmaz. b null ise false döndürür.
Sunumdaki örnek aynı üç değişkeni bu kez equals ile karşılaştırır ve birkaç durum ekler:
String str1 = "Giresun";
String str2 = "Giresun";
String str3 = new String("Giresun");
System.out.println(str1.equals(str2));
System.out.println(str1.equals("Giresun"));
System.out.println(str1.equals(str3));
System.out.println(str3.equals("Giresun"));
System.out.println(str3.intern() == str1);
String a = "Gire";
String b = a + "sun"; // yeni nesne
System.out.println(b == str1);
System.out.println(b.equals(str1));true
true
true
true
true
false
trueİlk dört satır, nesne hangi yolla oluşmuş olursa olsun true verir; içerik hep "Giresun"'dur. Beşinci satır intern()'ün havuzdaki eşi döndürdüğünü gösterir. Son iki satırda b, a değişkeniyle "sun" sabitinin çalışma zamanında birleştirilmesiyle oluşur. Ortaya çıkan nesne yenidir: b == str1 false, b.equals(str1) true'dur.
Pratikte en tehlikeli durum kullanıcı girdisidir. Scanner'dan okunan bir dizgi çalışma zamanında oluşur ve her zaman yeni bir nesnedir. Bu yüzden girdi == "evet" koşulu, kullanıcı tam olarak evet yazsa bile neredeyse her zaman false verir. Hata sinsidir: dizgi sabitleriyle yapılan denemelerde havuz yüzünden == doğru çalışıyor gibi görünür.
equals çağrısının bir de null yönü vardır. null, hiçbir nesneyi göstermeyen referans değeridir. null bir referans üzerinden metot çağrılamaz:
String cevap = null;
System.out.println("evet".equals(cevap)); // false
System.out.println(cevap + "!"); // null!
System.out.println(cevap.equals("evet")); // istisnaİlk satırda metot "evet" sabiti üzerinden çağrılır; sabit hiçbir zaman null olmadığı için çağrı güvenlidir ve null argümanla false döner. İkinci satırda birleştirme null'ı "null" metnine çevirir; istisna oluşmaz ve null! yazılır. Üçüncü satır ise NullPointerException fırlatır. Java 15'ten beri bu istisnanın iletisi varsayılan olarak hangi çağrının neden başarısız olduğunu açıkça söyler:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.equals(Object)" because "cevap" is nullİletideki değişken adı ("cevap"), sınıf dosyası hata ayıklama bilgisiyle derlendiğinde görünür. Geliştirme ortamları bu bilgiyi varsayılan olarak ekler. Komut satırında yalnız javac ile derlenen bir programda ise ad yerine yerel değişkenin sıra numarası yazılır:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.equals(Object)" because "<local1>" is nulljavac -g ile derlenince ileti yine "cevap" adını gösterir. İki ileti de aynı hatayı anlatır: null olan bir değişken üzerinden equals çağrılmıştır. Güvenli yazım, sabiti öne koymaktır: "evet".equals(cevap).
3.4.5.4Karşılaştırma metotları
equals dışında dizgiler için birkaç karşılaştırma metodu daha vardır. Tablo 3.4.9 bunları döndürdükleri tiple birlikte listeler.
| Metot | Döndürdüğü | Açıklama |
|---|---|---|
equals(str) | boolean | İçerik birebir aynı mı? |
equalsIgnoreCase(str) | boolean | Büyük-küçük harf farkı yok sayılınca aynı mı? |
startsWith(str) | boolean | Dizgi verilen metinle başlıyor mu? |
endsWith(str) | boolean | Dizgi verilen metinle bitiyor mu? |
contains(str) | boolean | Verilen metin dizginin içinde geçiyor mu? |
isEmpty() / isBlank() | boolean | Uzunluk 0 mı? / Yalnız boşluktan mı oluşuyor? (isBlank: Java 11+) |
compareTo(str) | int | Sözlük sırası: negatif (önce), 0 (eşit), pozitif (sonra). |
compareToIgnoreCase(str) | int | Harf büyüklüğünü yok sayan compareTo. |
Adında IgnoreCase geçmeyen metotlar büyük-küçük harfe duyarlıdır. equals ve equalsIgnoreCase, null argüman alınca istisna fırlatmaz, false döndürür. Sunumdaki örnek bu metotları "Giresun Üni." dizgisi üzerinde dener; yorumlardaki sonuçlar programın gerçek çıktısıdır:
String s = "Giresun Üni.";
System.out.println(s.startsWith("Gir")); // true
System.out.println(s.startsWith("Gır")); // false
System.out.println(s.endsWith("Üni.")); // true
System.out.println(s.endsWith("Üni")); // false
System.out.println(s.equalsIgnoreCase("Giresun Üni.")); // true
System.out.println(s.equalsIgnoreCase("Gıresun üni.")); // true
System.out.println(s.equalsIgnoreCase("GIresun Uni.")); // false
System.out.println(s.contains("Üni")); // trueSonuçların bazıları ilk bakışta şaşırtıcıdır:
startsWith("Gır")false'tur.ıileiayrı karakterlerdir; Unicode kodları 305 ve 105'tir.endsWith("Üni")false'tur, çünkü dizgi noktayla biter. Son karakter'.''dır.equalsIgnoreCase("Gıresun üni.")true'dur. Metot karakterleri karşılaştırırken her iki yanı da büyük harfe çevirir.ıilei'nin büyük biçimleri aynıdır,I; bu yüzden iki dizgi eşit sayılır.equalsIgnoreCaseyerel ayara bakmaz; her makinede aynı sonucu verir.equalsIgnoreCase("GIresun Uni.")false'tur.UileÜayrı harflerdir ve hiçbir büyük-küçük dönüşümü onları eşitlemez.
startsWith, contains ve endsWith metotlarının eşleştiği bölgeler.Boş dizgiyle yapılan karşılaştırmalar da bilinmeye değer. Her dizgi boş dizgiyle başlar ve onu içerir: s.startsWith("") ve s.contains("") her zaman true'dur. s.indexOf("") ise 0 döndürür.
3.4.5.5compareTo ve sözlük sırası
a.compareTo(b), iki dizgiyi sözlük sırasına (lexicographic order) göre karşılaştırır. a önce geliyorsa negatif, eşitse 0, sonra geliyorsa pozitif bir int döndürür. Karşılaştırma baştan başlar ve ilk farklı karakterde biter; sonuç bu iki karakterin Unicode kodlarının farkıdır. Farklı karakter yoksa ve biri ötekinin önekiyse sonuç uzunlukların farkıdır.
Sunumdaki örnek kuralın bütün durumlarını içerir:
System.out.println("elma".compareTo("armut"));
System.out.println("armut".compareTo("elma"));
System.out.println("elma".compareTo("elma"));
System.out.println("Elma".compareTo("elma"));
System.out.println("Elma".compareToIgnoreCase("elma"));
System.out.println("ev".compareTo("evet"));
System.out.println("çay".compareTo("dut"));4
-4
0
-32
0
-2
131Satırları kurala göre okuyalım:
"elma".compareTo("armut"): ilk karakterler farklıdır.'e'−'a'= 101 − 97 = 4. Pozitif: “elma” “armut”tan sonra gelir."armut".compareTo("elma"): aynı karakterler ters sırada, −4. Yer değiştirince işaret değişir.- Eşit dizgiler 0 verir.
"Elma".compareTo("elma"):'E'−'e'= 69 − 101 = −32. Unicode'da büyük harfler küçük harflerden önce gelir.compareToIgnoreCaseharf büyüklüğünü yok sayar ve 0 verir."ev".compareTo("evet"): ilk iki karakter aynıdır ve"ev"biter. Biri ötekinin önekidir; sonuç uzunluk farkıdır: 2 − 4 = −2."çay".compareTo("dut"):'ç''nin kodu 231,'d''nin kodu 100'dür. 231 − 100 = 131. Pozitif bir sonuç, “çay”ın “dut”tan sonra geldiği anlamına gelir.
Son satır önemli bir sınırı gösterir: compareTo Türkçe alfabe sırasını bilmez. Türkçede ç, c ile d arasındadır. Ama Unicode'da Türkçeye özgü harflerin kodları Latin harflerinden büyüktür: ç 231, ı 305, ş 351, ü 252, z ise 122. Unicode sırasına göre sıralanan bir Türkçe kelime listesinde ç, ı, ş ile başlayan kelimeler z'den sonraya düşer. Doğru Türkçe sıralama için java.text.Collator kullanılır:
import java.text.Collator;
import java.util.Arrays;
import java.util.Locale;
String[] m = {"zeytin", "çilek", "Elma", "armut", "ıhlamur", "incir"};
String[] m2 = m.clone();
Arrays.sort(m); // Unicode sırası
System.out.println(Arrays.toString(m));
Collator tr = Collator.getInstance(Locale.forLanguageTag("tr-TR"));
Arrays.sort(m2, tr); // Türkçe alfabe sırası
System.out.println(Arrays.toString(m2));[Elma, armut, incir, zeytin, çilek, ıhlamur]
[armut, çilek, Elma, ıhlamur, incir, zeytin]Arrays.sort dizgileri compareTo ile sıralar. Sonuçta büyük harfle başlayan “Elma” en başa, “çilek” ve “ıhlamur” en sona düşer. Türkçe Collator ise alfabe sırasını uygular ve büyük-küçük harf farkını sıralamada ikinci plana atar. Kullanıcıya gösterilecek listelerde ikinci yol seçilir.
Sunumdaki izleme slaytı compareTo'nun adımlarını üç dizgi çifti üzerinde gösterir:
String a = "masa", b = "mavi";
int r1 = a.compareTo(b);
int r2 = "kar".compareTo("karne");
int r3 = "Zeki".compareTo("ali");
System.out.println(r1 + " " + r2 + " " + r3);-3 -2 -7| Çift | k | Karakterler (kodlar) | Durum |
|---|---|---|---|
"masa", "mavi" | 0 | 'm' (109), 'm' (109) | eşit, devam |
| 1 | 'a' (97), 'a' (97) | eşit, devam | |
| 2 | 's' (115), 'v' (118) | farklı: 115 − 118 = −3 | |
"kar", "karne" | 0–2 | hepsi eşit; "kar" biter | önek: 3 − 5 = −2 |
"Zeki", "ali" | 0 | 'Z' (90), 'a' (97) | farklı: 90 − 97 = −7 |
"masa" ile "mavi" indis 2'de ayrılır; sonuç kodların farkıdır, 115 − 118 = −3.Üçüncü çift, büyük harf kuralının ne kadar ileri gittiğini gösterir: Z bile a'dan önce gelir. Sonucun büyüklüğü genellikle bir anlam taşımaz; önemli olan işaretidir. Bu yüzden karşılaştırma if (a.compareTo(b) < 0) biçiminde yazılır, == -1 biçiminde yazılmaz. < ve > operatörleri dizgilerle çalışmaz; derleyici “bad operand types” hatası verir:
Kiyas.java:4: error: bad operand types for binary operator '<'
if (a < b) System.out.println(a);
^
first type: String
second type: String3.4.6Biçimli çıktı
println değeri olduğu gibi yazar. Bir not çizelgesi, bir fatura ya da bir ölçüm tablosu yazdırılırken bu yetmez. Sütunların hizalı olması, ondalıklı sayıların belli sayıda basamakla yazılması gerekir. Java bu iş için printf metodunu ve onunla aynı kuralları kullanan String.format'ı sunar.
3.4.6.1printf ve biçim belirteçleri
System.out.printf(biçim, parametreler…) bir biçim dizgisi ve ardından istenen sayıda parametre alır. Biçim dizgisi, düz metnin arasına yerleştirilmiş biçim belirteçleri (format specifier) içerir. Her belirteç % ile başlar ve bir tip harfiyle biter; aradaki isteğe bağlı kısım bayrak, genişlik ve hassasiyeti verir. Parametreler belirteçlerin yerine sırayla yerleşir.
Bir belirtecin genel biçimi %[bayrak][genişlik][.hassasiyet]tip'tir (Şekil 3.4.12). %-8.2f belirtecinde - bayrağı sola yaslar, 8 genişliği, .2 hassasiyeti, f ise tipi verir.
%), bayrak, genişlik, hassasiyet ve tip.Tablo 3.4.11 en sık kullanılan tip harflerini verir. Ondalık ayırıcı yerel ayara bağlıdır. Türkçe ayarda virgül, Locale.US ile nokta kullanılır.
| Belirteç | Tip | Örnek → çıktı |
|---|---|---|
%d | Tam sayı (byte, short, int, long) | 42 → 42 |
%f | Ondalıklı (float, double) | %.1f: 3.14159 → 3,1 |
%s | Dizgi (her değer metne çevrilir) | %5s: "ab" → ␣␣␣ab |
%c | Karakter | 'Z' → Z |
%b | Mantıksal | true → true |
%n | Satır sonu | — |
printf kendiliğinden alt satıra geçmez. Satır sonu için biçim dizgisinin sonuna \n ya da %n eklenir. %n işletim sisteminin satır sonu karakterini kullanır; Windows'ta iki karakterlik \r\n, Linux ve macOS'ta \n yazar. Bu yüzden taşınabilir kodda %n tercih edilir.
Genişlik, değerin kaplayacağı en az karakter sayısıdır. Değer daha kısaysa boşlukla doldurulur: %Wd sağa yaslar, %-Wd sola yaslar. Değer genişlikten uzunsa kesilmez, alan büyür. Hassasiyet ondalıklı sayılarda virgülden sonraki basamak sayısıdır ve son basamak yuvarlanır: %.2f iki basamak yazar. %8.2f ise iki ondalık basamakla birlikte toplam 8 karakterlik bir alan ayırır.
3.4.6.2Genişlik ve hassasiyet
Sunumdaki örnek aynı üç değeri dört farklı biçimle yazar:
int i = 3;
float f = -17;
String s = "java";
System.out.printf("x: %d y: %f s: %s\n", i, f, s);
System.out.printf("x: %4d y: %5.2f s: %6s\n", i, f, s);
System.out.printf("x: %-4d y: %-7.2f s: %-6s\n", i, f, s);
System.out.printf("x: %-4d y: %-6.3f s: %-6s\n", i, f, s);x: 3 y: -17,000000 s: java
x: 3 y: -17,00 s: java
x: 3 y: -17,00 s: java··
x: 3 y: -17,000 s: java··Çıktı Türkçe ayarda alınmıştır. Satır sonundaki boşluklar görünsün diye · ile gösterildi. Değişkenlerin ikisi açıklama ister: f bir float'tır ve -17 tam sayısı ona atanınca -17,0 olur. %f'ye hassasiyet verilmezse 6 ondalık basamak yazılır. Satırları sırayla inceleyelim:
- Genişlik verilmemiştir. Her değer kendi uzunluğu kadar yer kaplar: 3, -17,000000, java.
%4d3'ü dört karakterlik alana sağa yaslar: üç boşluk ve 3.%5.2fiki ondalıkla -17,00 yazar; bu 6 karakterdir ve 5 genişliğe sığmaz, alan büyür.%6sdört harfli java'nın soluna iki boşluk koyar.- Eksi bayrağı sola yaslar; boşluklar değerin sağına eklenir.
%-4d→ 3···,%-7.2f→ -17,00·,%-6s→ java··. %-6.3füç ondalıkla -17,000 yazar. Değer 7 karakterdir ve 6 genişlikten uzundur; dolgu yapılmaz.
Şekil 3.4.13 ikinci satırı karakter karakter gösterir. Her belirtecin kapladığı alan alt kısımda işaretlidir.
"x: %4d y: %5.2f s: %6s" çıktısı karakter karakter. %4d 4, %5.2f 6 (sığmadığı için büyüyen alan), %6s 6 karakter kaplar.Genişlik en çok tablo yazdırırken işe yarar. Her satırda aynı belirteçler kullanılınca sütunlar alt alta düzgün hizalanır. Metin sütunları genellikle sola (%-10s), sayı sütunları sağa (%6d, %8.2f) yaslanır. Böylece sayıların birler basamağı alt alta gelir.
Aşağıdaki satırın Türkçe ayardaki çıktısını karakter karakter bulalım:
String ad = "Ali";
double not1 = 72.456;
System.out.printf("[%-5s][%3d][%7.2f]%n", ad, 7, not1);%-5s: "Ali" üç harflidir, beş karakterlik alana sola yaslanır ve sağına iki boşluk eklenir: Ali··. %3d: 7 tek basamaklıdır, soluna iki boşluk gelir: ··7. %7.2f: 72,456 iki ondalığa yuvarlanır, 72,46 olur. Bu metin 5 karakterdir; yedi karakterlik alanı doldurmak için başa iki boşluk eklenir: ··72,46. Sonuç:
[Ali ][ 7][ 72,46]Yuvarlama kuralı da bilinmeye değer. printf, sayının ondalık gösterimine bakar ve yarımı sıfırdan uzağa yuvarlar (HALF_UP). Math.round'dan farklı olarak negatif yarımlarda da sıfırdan uzaklaşır:
System.out.printf("[%.2f][%.2f][%.0f][%.0f][%.0f]%n", 2.345, 2.355, 0.5, 1.5, -2.5);
System.out.printf(Locale.US, "%,.2f %08.3f %+d %X%n", 1234567.891, 3.14159, 5, 255);[2,35][2,36][1][2][-3]
1,234,567.89 0003.142 +5 FF2,345 ve 2,355 beklendiği gibi yukarı yuvarlanır. 0,5 → 1, 1,5 → 2 ve −2,5 → −3 olur. İkinci satırdaki bayraklar sonraki alt kesimde açıklanıyor.
3.4.6.3Diğer biçimler ve String.format
System.out.printf("|%5s|%-5s|%05d|%.1f|%n", "ab", "ab", 42, 3.14159);
System.out.printf("%c %b %e %x %%%n", 'Z', true, 1234.5, 255);
String mesaj = String.format("%s: %d puan", "Ayşe", 85);
System.out.println(mesaj);
System.out.printf("%,d%n", 1234567);
System.out.printf(Locale.US, "%.2f%n", 2.5); // import java.util.Locale;
System.out.println("%s = %d".formatted("x", 7)); // Java 15+| ab|ab |00042|3,1|
Z true 1,234500e+03 ff %
Ayşe: 85 puan
1.234.567
2.50
x = 7Bu örnekteki yeni öğeler şunlardır:
%05d:0bayrağı alanı boşluk yerine sıfırla doldurur: 00042. Fatura ve öğrenci numarası gibi sabit uzunluklu numaralarda kullanılır.%ebilimsel gösterimdir: 1234,5 = 1,2345 × 103, çıktıda 1,234500e+03.%xonaltılık tabanda yazar: 255 → ff; büyük harf için%X.%%yüzde işaretinin kendisini yazar.String.formataynı kuralları kullanır ama ekrana yazmaz; biçimlenmiş metni birStringolarak döndürür. Sonuç bir değişkende saklanabilir, bir dosyaya yazılabilir ya da başka bir metne eklenebilir.%,dbinlik ayırıcı koyar. Türkçe ayarda ayırıcı noktadır: 1.234.567. ABD ayarında virgüldür.printf'in ilk parametresi birLocaleolabilir.Locale.USverilince ondalık ayırıcı nokta olur: 2.50. Başka bir programın okuyacağı veriler (CSV dosyası gibi) bu yolla yazılır.- Java 15 ile gelen
formattedmetodu biçim dizgisi üzerinden çağrılır:"%s = %d".formatted("x", 7)ileString.format("%s = %d", "x", 7)aynı sonucu verir.
Biçim dizgisi derleyici için sıradan bir metindir. Belirteç ile parametrenin tipi uyuşmazsa hata derleme sırasında değil, çalışırken ortaya çıkar. printf("%d", 3.5) çağrısı %d'nin beklediği tam sayı yerine bir double alır ve istisna fırlatır:
Exception in thread "main" java.util.IllegalFormatConversionException:
d != java.lang.Doubleİleti “d belirteci Double ile kullanılamaz” demektir. printf dönüşüm yapmaz; sayıyı kesip 3 yazmaz. Doğrusu %f ya da %.0f kullanmaktır. Belirteç sayısı parametre sayısından fazlaysa MissingFormatArgumentException fırlatılır. %s ise her tiple çalışır, çünkü her değer metne çevrilebilir.
3.4.7Uygulamalar
Önceki kesimlerin araçları, yani indisleme, döngü, karşılaştırma ve birleştirme, birlikte kullanılınca gerçek problemler çözülür. Bu kesim sunumdaki iki uygulamayı ve alıştırma slaytlarındaki küçük metotları ayrıntılı işler.
3.4.7.1Palindrom denetimi
Palindrom, tersten okunuşu düzden okunuşuyla aynı olan metindir: “kayak”, “ana”, “kek”. Cümlelerde boşluk ve noktalama işaretleri yok sayılır.
Bir metnin palindrom olup olmadığını anlamanın en doğrudan yolu metni ters çevirip orijinaliyle karşılaştırmaktır:
String metin = "Kayak";
metin = metin.toLowerCase();
String ters = "";
for (int i = metin.length() - 1; i >= 0; i--) {
ters += metin.charAt(i);
}
System.out.println(ters);
System.out.println(metin.equals(ters));kayak
trueKod üç adımda çalışır. Önce metin küçük harfe çevrilir; çevrilmeseydi K ile k farklı sayılırdı. toLowerCase() yeni bir dizgi döndürdüğü için sonuç yeniden metin'e atanır. İkinci adımda boş bir ters dizgisi hazırlanır ve döngü son indisten 0'a doğru iner. Her turda o indisteki karakter ters'in sonuna eklenir. Üçüncü adımda iki dizgi equals ile karşılaştırılır. Tablo 3.4.12 döngünün turlarını verir.
i | i >= 0 | charAt(i) | ters (tur sonunda) |
|---|---|---|---|
| 4 | doğru | 'k' | "k" |
| 3 | doğru | 'a' | "ka" |
| 2 | doğru | 'y' | "kay" |
| 1 | doğru | 'a' | "kaya" |
| 0 | doğru | 'k' | "kayak" |
| −1 | yanlış | — | döngü biter |
metin ve ters. İçerikler aynıdır; equals true döndürür.Karşılaştırmanın equals ile yapılması zorunludur. ters çalışma zamanında birleştirmeyle kurulmuş yeni bir nesnedir; metin == ters her zaman false verirdi. Döngüdeki ters += … satırı her turda yeni bir dizgi üretir. Kısa metinlerde bunun önemi yoktur, ama aynı iş new StringBuilder(metin).reverse().toString() ile hem kısa hem verimli yapılır.
Cümlelerde önce harf olmayan karakterler silinir. metin.replaceAll("[^\\p{L}]", "") çağrısı düzenli ifade kullanır: \p{L} Unicode'daki her harfi, [^…] ise “bunlar dışındaki her karakteri” anlatır. Böylece yalnız harfler kalır. “Ey Edip Adana'da pide ye” cümlesi temizlenip küçültülünce "eyedipadanadapideye" olur ve bu bir palindromdur.
Ters çevirme yöntemi metnin bir kopyasını oluşturur. Bellek harcamadan aynı denetim iki indisle de yapılır: biri baştan, biri sondan başlar ve ortada buluşana kadar karakterleri karşılaştırır. Bir eşitsizlik bulunduğu anda sonuç false'tur; geri kalan karakterlere bakmaya gerek kalmaz. İki yöntemin de maliyeti metnin uzunluğuyla doğru orantılıdır.
3.4.7.2Kelime frekansı
Bir metinde her kelimenin kaç kez geçtiğini saymak, metin işlemenin en temel problemlerinden biridir. Sunumdaki çözüm yalnız dizilerle ve iç içe iki döngüyle çalışır:
String metin = "Bir iki bir üç İki bir";
String[] kelimeler = metin.toLowerCase().split(" ");
boolean[] sayildi = new boolean[kelimeler.length];
for (int i = 0; i < kelimeler.length; i++) {
if (sayildi[i]) {
continue; // bu kelime daha önce sayıldı
}
int frekans = 1;
for (int j = i + 1; j < kelimeler.length; j++) {
if (kelimeler[i].equals(kelimeler[j])) {
frekans++;
sayildi[j] = true;
}
}
System.out.println(kelimeler[i] + ": " + frekans);
}bir: 3
iki: 2
üç: 1Kod şu adımları izler:
metin.toLowerCase().split(" ")önce metni küçültür, sonra boşluklardan böler. Küçültme sayesinde “Bir/bir” ve “İki/iki” aynı kelime sayılır. Türkçe ayardaİküçültülünceiolur. Dilden bağımsız kurallarla iseİ,iile birleşen noktaya dönüşür ve “İki” ayrı bir kelime gibi sayılır; çıktı Türkçe ayara göredir.sayildidizisi her kelime için bir bayrak tutar.booleandizisi oluşturulduğunda bütün öğelerfalse'tur.- Dış döngü her kelimeye bir kez bakar. Kelimenin bayrağı
trueise o kelime daha önce sayılmıştır;continueturun geri kalanını atlar. - İç döngü yalnız sağdaki kelimeleri tarar. Eşleşen her kelime için sayaç artar ve eşleşenin bayrağı kaldırılır. Böylece aynı kelime ikinci kez raporlanmaz.
Kelimeler equals ile karşılaştırılır. split'in ürettiği parçalar çalışma zamanında oluşan ayrı nesnelerdir. == kullanılsaydı hiçbir eşleşme bulunmaz ve her kelimenin frekansı 1 çıkardı. Bu durum çalıştırılarak doğrulandı: == ile “bir” kelimesi yalnız bir kez sayılır.
Sunumdaki izleme slaytı aynı kodu "elma Armut elma kiraz ARMUT" metni üzerinde çalıştırır:
String metin = "elma Armut elma kiraz ARMUT";
String[] k = metin.toLowerCase().split(" ");
boolean[] sayildi = new boolean[k.length];
for (int i = 0; i < k.length; i++) {
if (sayildi[i]) continue;
int frekans = 1;
for (int j = i + 1; j < k.length; j++) {
if (k[i].equals(k[j])) {
frekans++;
sayildi[j] = true;
}
}
System.out.println(k[i] + ": " + frekans);
}elma: 2
armut: 2
kiraz: 1i | k[i] | Eşleşen j | frekans | sayildi | Çıktı |
|---|---|---|---|---|---|
| 0 | elma | 2 | 2 | F F T F F | elma: 2 |
| 1 | armut | 4 | 2 | F F T F T | armut: 2 |
| 2 | elma | — | — | F F T F T | bayrak açık, continue |
| 3 | kiraz | yok | 1 | F F T F T | kiraz: 1 |
| 4 | armut | — | — | F F T F T | bayrak açık, continue |
i = 0 turunun sonunda kelime dizisi ve bayraklar. İndis 2'deki “elma” sayıldı olarak işaretlenmiştir.Algoritmanın maliyeti kelime sayısının karesiyle büyür. n kelime için iç döngü en kötü durumda (n − 1) + (n − 2) + … + 1 = n(n − 1)/2 karşılaştırma yapar. Birkaç cümlelik bir metin için bu çok azdır. Bir kitabın bütün kelimeleri sayılacaksa anahtar-değer çiftleri tutan bir koleksiyon (HashMap) çok daha hızlı sonuç verir.
3.4.7.3Küçük dizgi metotları
Sunumun alıştırma ve örnek problem slaytları, dizgi metotlarını birleştiren birkaç kısa metot içerir. Bu metotlar indisleme ve döngünün ortak kalıplarını gösterir.
Ters çevirme. tersCevir metodu palindrom örneğindeki döngüyü bir metoda taşır:
static String tersCevir(String s) {
String ters = "";
for (int i = s.length() - 1; i >= 0; i--) {
ters += s.charAt(i);
}
return ters;
}tersCevir("Giresun") çağrısı nuseriG döndürür. Büyük G sona geçmiştir; metot harf büyüklüğüne dokunmaz.
Karakter sayma. say metodu bir karakterin kaç kez geçtiğini bulur:
static int say(String s, char aranan) {
int adet = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == aranan) {
adet++;
}
}
return adet;
}s.charAt(i) == aranan karşılaştırması doğrudur, çünkü iki yan da char'dır ve char ilkel bir tiptir. == burada referansı değil değeri karşılaştırır. say("Karadeniz", 'a') 2 döndürür: K büyük harftir ve 'a' ile eşleşmez; eşleşen yalnız indis 1 ve 3'teki a'lardır.
Baş harfi büyütme. basHarfBuyuk ilk harfi büyük, kalanını küçük yapar:
static String basHarfBuyuk(String kelime) {
return kelime.substring(0, 1).toUpperCase()
+ kelime.substring(1).toLowerCase();
}Metot iki parçayı ayrı ayrı işleyip birleştirir: substring(0, 1) ilk harf, substring(1) kalanıdır. basHarfBuyuk("jAVA") → Java. Türkçe ayarda basHarfBuyuk("istanbul") → İstanbul, basHarfBuyuk("ılgaz") → Ilgaz verir. Boş dizgiyle çağrılırsa substring(0, 1) geçersiz bir aralık ister ve StringIndexOutOfBoundsException fırlatılır. Sağlam bir sürüm başta if (kelime.isEmpty()) return kelime; denetimi yapar.
Sesli harf sayma. Bir karakterin bir kümede olup olmadığını anlamak için indexOf kullanılabilir:
String kelime = "Ordu";
String sesliler = "aeıioöuüAEIİOÖUÜ";
int sayac = 0;
for (int i = 0; i < kelime.length(); i++) {
char c = kelime.charAt(i);
if (sesliler.indexOf(c) != -1) {
sayac++;
}
}
System.out.println("Sesli: " + sayac);Sesli: 2sesliler dizgisi Türkçenin sekiz sesli harfini büyük ve küçük biçimleriyle içerir. Her karakter için sesliler.indexOf(c) çağrılır; sonuç −1 değilse karakter seslidir. "Ordu"'da O ve u sesli harftir: Sesli: 2. Bu kalıp sekiz dallı bir if zincirinden hem kısa hem okunaklıdır.
Kelime sayısı. Kelimeler arasında tek boşluk varsa kelime sayısı tek satırda bulunur: cumle.split(" ").length. "Java ile nesne yönelimli programlama" için sonuç 5'tir. Kelimeler arasında birden çok boşluk olabiliyorsa cumle.trim().split(" +").length yazılır.
Sunumdaki ilk örnek problem değiştirilemezlik, substring, indexOf ve birleştirme sırasını bir arada sınar:
String s = "Karadeniz";
s.toUpperCase();
String t = s.substring(2, 5);
System.out.println(t + " " + t.length());
System.out.println(s.indexOf("de"));
System.out.println(s.indexOf("x"));
System.out.println(1 + 2 + "3" + 4 + 5);rad 3
4
-1
3345s.toUpperCase() sonucu atanmadığı için s değişmez. "Karadeniz"'in indisleri K0 a1 r2 a3 d4 e5 n6 i7 z8'dir. substring(2, 5) 2, 3, 4 indislerini alır: "rad", uzunluk 3. "de" indis 4'te başlar. "x" dizgide yoktur, −1. Son satır soldan sağa hesaplanır: 1 + 2 = 3 (iki sayı), 3 + "3" = "33", ardından "334" ve "3345".
İkinci problem havuzu sınar:
String a = "ders";
String b = "de" + "rs";
String c = new String("ders");
String d = c;
System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);
System.out.println(a.equals(c));true
false
true
true"de" + "rs" derleme zamanında hesaplanan bir sabittir ve havuzdaki a ile aynı nesneyi gösterir: a == b true. c new ile oluşmuş ayrı bir nesnedir: a == c false. d = c referansı kopyalar; ikisi aynı nesnedir: true. İçerikler aynı olduğu için a.equals(c) true'dur.
Üçüncü problem baş harfleri ve e-posta parçalarını çıkarır:
public static String bas(String ad, String soyad) {
return ad.charAt(0) + "." + soyad.charAt(0) + ".";
}
System.out.println(bas("Sercan", "Külcü"));
String e = "ogrenci@giresun.edu.tr";
int at = e.indexOf('@');
System.out.println(e.substring(0, at));
System.out.println(e.substring(at + 1));S.K.
ogrenci
giresun.edu.trad.charAt(0) + "." ifadesinde sağ yan bir dizgi olduğu için birleştirme yapılır: "S.". İki char doğrudan toplansaydı sonuç bir sayı olurdu; 'S' + '.' 83 + 46 = 129 verir. E-posta kısmı daha önce çözülen örneğin aynısıdır.
3.4.8Sık yapılan hatalar
Dizgilerle ilgili hataların bir kısmını derleyici yakalar; bunların iletisi okunursa düzeltmesi kolaydır. Bir kısmı ise derlenir ve çalışırken yanlış sonuç verir ya da istisna fırlatır. Tablo 3.4.14 ilk grubu, Tablo 3.4.15 ikinci grubu özetler. İletiler JDK 21'in javac'ından alınmıştır.
| Hata | Derleyici iletisi | Düzeltme |
|---|---|---|
String s = 'Merhaba'; | unclosed character literal | Tek tırnak yalnız tek karakter içindir; dizgi çift tırnakla yazılır. |
char c = "a"; | incompatible types: String cannot be converted to char | char c = 'a'; ya da "a".charAt(0). |
s.length | cannot find symbol / symbol: variable length | Dizgide length() metottur. Dizide tersi: d.length. |
string s = …; | cannot find symbol / symbol: class string | Sınıf adı String'dir; Java büyük-küçük harfe duyarlıdır. |
int n = "42"; | incompatible types: String cannot be converted to int | Integer.parseInt("42"). |
s.charAt(0) = 'j'; | unexpected type / required: variable | Dizgi değiştirilemez; yeni dizgi ya da StringBuilder.setCharAt. |
s[0] | array required, but String found | s.charAt(0). |
if (a < b) (dizgiler) | bad operand types for binary operator '<' | a.compareTo(b) < 0. |
İlk satırdaki hata için javac aslında üç ileti verir: iki kez unclosed character literal ve bir kez not a statement. Derleyici tek tırnağı bir karakter sabitinin başlangıcı sanar, tırnağın kapanmadığını görür ve ardından gelenleri anlamlandıramaz. Bu tür zincirleme iletilerde ilk ileti asıl nedeni gösterir:
Tirnak.java:3: error: unclosed character literal
String s = 'Merhaba';
^| Hata | Belirti | Düzeltme |
|---|---|---|
İçeriği == ile karşılaştırmak | Kullanıcı girdisi doğru olsa da koşul false. | "evet".equals(girdi) |
| Sonucu atamayı unutmak | s.toUpperCase(); sonrası s değişmez. | s = s.toUpperCase(); |
Son indisi length() sanmak | StringIndexOutOfBoundsException: Index 4 out of bounds for length 4 | s.charAt(s.length() - 1); döngüde i < s.length() |
nextInt sonrası nextLine | Satır boş okunur, soru atlanır. | Araya bir klavye.nextLine(); |
null dizgide metot çağırmak | NullPointerException: Cannot invoke "String.equals(Object)" | Sabiti öne yazmak: "evet".equals(cevap) |
substring'in bitişini dahil sanmak | s.substring(5, 12) 8 değil 7 karakter verir. | Uzunluk = bitiş − başlangıç. |
| Parantezsiz toplama | "Toplam: " + x + y → Toplam: 105 | "Toplam: " + (x + y) |
| Varsayılan yerel ayarla harf dönüşümü | "TITLE".toLowerCase() Türkçe ayarda tıtle. | toLowerCase(Locale.ROOT) ya da equalsIgnoreCase |
printf'te yanlış belirteç | IllegalFormatConversionException | Tam sayıya %d, ondalıklıya %f. |
İkinci tablodaki hataların ortak noktası derleyicinin onları görememesidir. == ve equals ikisi de geçerli Java'dır. Sonucu atanmayan bir metot çağrısı da geçerli bir deyimdir. Bu hatalara karşı en iyi koruma, programı farklı girdilerle denemek ve çıktıyı beklenen değerle karşılaştırmaktır. Özellikle boş dizgi, tek karakterli dizgi, Türkçe harfler ve baştaki-sondaki boşluklar iyi birer deneme girdisidir.
3.4.9Özet
- String karakter dizisini saklayan, değiştirilemez bir nesnedir. Dizgi sabiti (
"…") ya danewile oluşur;java.langpaketinde olduğu içinimportgerektirmez. - Birleştirme:
+soldan sağa işlenir. İşlenenlerden biri dizgiyse birleştirme, ikisi de sayıysa toplama yapılır.charbir sayıdır:'a' + 'b'= 195. Sayıları toplamak için parantez kullanılır. - İndis 0'dan
length() - 1'e gider.charAt(i)birchardöndürür. Dışarı taşan indisStringIndexOutOfBoundsExceptionfırlatır. Dizgidelength()metottur, dizidelengthalandır. - Metotlar:
indexOf(bulunamazsa −1),substring(a, b)(bhariç, uzunlukb − a),toUpperCase,replace,trim,split,join; hepsi yeni değer döndürür ve sonucun atanması gerekir. - Yerel ayar: parametresiz büyük-küçük harf dönüşümü makinenin ayarına bağlıdır. Türkçe ayarda
i ↔ İ,ı ↔ I. Program içi metinlerdeLocale.ROOTkullanılır. - StringBuilder: değiştirilebilir tampondur. Döngüde
+=ile birleştirme O(n²),appendile O(n) iş yapar. Kapasite dolunca yaklaşık iki katına çıkar (20 → 42). - Scanner:
next()bir kelime,nextLine()satırın kalanını okur.nextInt()sonrası ⏎ tamponda kalır; ardından gelennextLine()boş dizgi döndürür. ==/equals:==aynı nesne mi diye,equalsaynı içerik mi diye sorar. Dizgilerin içeriği her zamanequalsile karşılaştırılır;null'a karşı sabit öne yazılır.- Dizgi havuzu: içeriği aynı dizgi sabitleri tek nesneyi paylaşır.
newve çalışma zamanındaki birleştirme yeni nesne üretir;intern()havuzdaki eşi verir. - compareTo: ilk farklı karakterin kod farkını, önek durumunda uzunluk farkını döndürür. Unicode sırasını kullanır; Türkçe alfabe sırası için
Collatorgerekir. - printf:
%d,%f,%s,%c,%b,%n.%Wdsağa,%-Wdsola yaslar;%.2fiki ondalık basamak yazar. Sığmayan değer alanı büyütür. Ayırıcılar yerel ayara bağlıdır.
3.4.10Kavram listesi
- dizgi string
- dizgi sabiti string literal
- dizgi birleştirme string concatenation
- karakter (char) character
- Unicode Unicode
- indis index
- indisleme indexing
- değiştirilemez immutable
- referans reference
- heap heap
- çöp toplayıcı garbage collector
- yerel ayar locale
- tampon buffer
- dizgi havuzu string pool
- null null reference
- biçim belirteci format specifier
- derleme zamanı / çalışma zamanı compile time / run time
- çalışma zamanı hatası runtime error
- istisna exception
- sabit değer literal
- referans tipi reference type
3.4.11Kendini Dene
System.out.println(2 + 3 + "x" + 2 + 3);ne yazdırır? Sondaki iki sayının toplamının yazılması için ifade nasıl değiştirilir?Cevap
5x23 yazar. Soldaki iki sayı dizgiyle karşılaşılmadan önce toplanır (5).
"x"'ten sonraki her+birleştirmedir. Sondaki toplam için parantez gerekir:2 + 3 + "x" + (2 + 3)→ 5x5.String s = "Karadeniz Teknik";içins.indexOf(' '),s.substring(s.indexOf(' ') + 1)ves.lastIndexOf('e')kaçtır?Cevap
“Karadeniz” dokuz harflidir; boşluk indis 9'dadır.
substring(10)boşluktan sonrasını verir: Teknik.Tindis 10'da,eindis 11'dedir ve sonrasında başkaeyoktur:lastIndexOf('e')= 11.StringBuilder sb = new StringBuilder("abc"); sb.append("de").insert(2, "X").reverse();satırlarından sonrasb'nin içeriği ve uzunluğu nedir? Aynı işlemlerStringile yapılsaydı kaç nesne oluşurdu?Cevap
"abc"→append→"abcde"→insert(2, "X")→"abXcde"→reverse→ edcXba, uzunluk 6. Bütün işlemler aynı nesne üzerinde yapılır.Stringile her adım yeni bir dizgi üretirdi: başlangıç dizgisinin yanında üç yeni nesne daha."Ankara".compareTo("Antalya")ve"abc".compareTo("ab")ne döndürür? Bu iki dizgi alfabetik sıraya konursa hangisi önce gelir?Cevap
İlk çiftte indis 2'de
'k'(107) ile't'(116) ayrılır: 107 − 116 = −9. Negatif olduğu için “Ankara” önce gelir. İkinci çiftte"ab","abc"'nin önekidir; sonuç uzunluk farkıdır: 3 − 2 = 1. “ab” önce gelir.String x = "Ada"; String y = "A" + "da"; String z = new String("Ada"); String w = z.intern();satırlarından sonrax == y,x == z,x == wvez.equals(y)ne verir?Cevap
true false true true.
"A" + "da"derleme zamanında"Ada"sabitine dönüşür ve havuzdaki nesneyi gösterir.zhavuz dışında ayrı bir nesnedir.intern()havuzdaki eşi, yanix'in nesnesini döndürür. İçerikler aynı olduğu içinequalstrueverir.Türkçe ayarlı bir bilgisayarda
System.out.printf("[%6.1f][%-4s][%3d]%n", 9.96, "ok", 12345);çıktısı nedir?Cevap
[ 10,0][ok ][12345]. 9,96 bir ondalığa yuvarlanınca 10,0 olur; bu 4 karakterdir ve başa iki boşluk eklenir.
"ok"sola yaslanır, sağa iki boşluk gelir. 12345 beş basamaklıdır ve 3 genişliğe sığmaz; alan büyür, sayı kesilmez.Klavyeden önce ␣␣42␣␣Ayşe Yılmaz⏎, sonra Son⏎ girilir.
int n = k.nextInt(); String r = k.nextLine(); String t = k.next();satırlarından sonran,rvetnedir?Cevap
nextInt()baştaki boşlukları atlar ve 42'yi okur.nextLine()boşlukları atlamaz; satırın kalanını alır:r = " Ayşe Yılmaz"(başta iki boşluk). ⏎ tüketildiği içinnext()ikinci satırdaki kelimeyi okur:t = "Son".String u = "Merhaba"; u.replace('a', 'e'); u.concat("!"); System.out.println(u);ne yazdırır? Programcı Merhebe! görmek istiyorsa kod nasıl düzeltilir?Cevap
Merhaba yazar. İki metot da yeni dizgi döndürür ama sonuçlar atanmadığı için kaybolur. Düzeltilmiş hâli:
u = u.replace('a', 'e').concat("!");ya da iki ayrı atama."a-b--c".split("-")kaç öğeli bir dizi döndürür?"KIRMIZI".toLowerCase().equals("kirmizi")Türkçe ayarlı bir bilgisayarda ne verir?Cevap
Ardışık iki tire arasında boş bir parça oluşur:
["a", "b", "", "c"], 4 öğe. İkinci ifadefalseverir: Türkçe kurallarlaIküçültülünceıolur ve sonuç"kırmızı"'dır.toLowerCase(Locale.ROOT)ya daequalsIgnoreCasetrueverir.
3.4.12İleri okuma
- Oracle, The Java Tutorials, “Numbers and Strings” kesimi:
String,StringBuilderve biçimli sayı çıktısını kısa örneklerle anlatan resmî öğretici. - Oracle, Java SE 21 API Documentation,
java.util.Formattersınıfı:printf'te kullanılabilen bütün bayrakların, genişlik ve hassasiyet kurallarının ve tip harflerinin eksiksiz listesi. - J. Gosling vd., The Java Language Specification, Java SE 21, §3.10.5 ve §15.18.1: dizgi sabitlerinin havuzda paylaşılmasını ve
+ile birleştirmenin kurallarını kesin biçimde tanımlar. - C. S. Horstmann, Core Java, Volume I: Fundamentals, Bölüm 3.6: dizgileri,
StringBuilder'ı ve Unicode'un Java'daki karşılığını deneyimli bir programcının gözünden işler.
3.4.13Kaynaklar
- Oracle, The Java Tutorials: “Strings”, “Comparing Strings and Portions of Strings”, “The StringBuilder Class”, “Formatting Numeric Print Output”.
- Oracle, Java SE 21 API Documentation:
java.lang.String,java.lang.StringBuilder,java.util.Scanner,java.util.Formatter,java.util.Locale. - J. Gosling vd., The Java Language Specification, Java SE 21 Edition (§3.10.5 String Literals, §15.18.1 String Concatenation Operator +).
- P. Deitel, H. Deitel, Java: How to Program, 11. baskı, Pearson, 2017 (Bölüm 14: Strings, Characters and Regular Expressions).
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, 2022 (Bölüm 3.6: Strings).
- A. B. Downey, C. Mayfield, Think Java, 2. baskı, O'Reilly, 2019 (Bölüm 6: Loops and Strings).
İlgili sayfalar: Bölüm 3.4 sunumu · Çalışma soruları · Dizgi Havuzu · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.