Java; ad, adres, makale içeriği gibi metinsel bilgilerin temsil edilmesinde kullanılmak üzere iki başlık altında incelenebilecek seçenekler sunar: dizi temelli çözümler metni eleman türü
İşimize neden üç sınıf bulunduğuna açıklık getirerek başlayalım.
Bu noktada, sadece
Not: Yazımızın devamında yukarıdaki farklılıkları dışında işlevsel olarak birbirlerinin aynısı olan
Karakter katarlarını temsil etmek için neden üç tane sınıf sağlandığına açıklık getirdikten sonra, dışsallaştırma desteğine işaret eden
Her üç karakter katarı sınıfında da bulunan diğer iletiler, hedef nesnenin içeriğini sorgulamaya yarayan iletilerdir. Bunlardan
Ortak olan bir diğer ileti grubu, karakterlerin Java temsilinde kullanılan UTF-16 kodlamasının bir özelliğini hatırlamamızı gerektiriyor: Her ne kadar modern dillere ait tüm simgeler iki sekizli genişliğindeki
Karakter katarı sınıflarına ait nesnelerin yaratılması için kullanabileceğimiz yapıcılara bakarak devam edelim. Her üç sınıf da varsayılan yapıcıya ek olarak argümanındaki
Karakter katarı sabitleri için geçerli olan içrekleştirme—yani, yerin içrek bellekten ayrılması—
Gelelim iki Java programı arasında gidip gelen
Alışageldiğimiz eşitlik denetimi (
Tabii, bazılarınız
Eşitlik denetimini andıran
Biraz nefes alarak, yolumuza
Programlamanın
İçeriği güncelleyen iletilerin değişiklikleri hedef nesnede yaptığı ve ileti alıcıyı döndürdüğünün bilinmesi zincirleme ileti gönderimini ve dolayısıyla daha akıcı okunabilen kod yazımını olanaklı kılacaktır. Örnek olarak aşağıdaki kod parçasının 7. satırını ele alalım. Bu satır, zincirleme ileti gönderme özelliği sayesinde
şekline dönüştürülmesi daha iyi bir çözüm olacaktır. Çünkü, katar sonuna ekleme yaptığı için karakterlerın kaydırılmasına neden olmayan
char
olan diziler olarak ele alırken, karakter katarı kavramını soyutlayan sınıflar—üç tane—0 veya daha fazla sayıda karakter içeren katarların kullanımını kolaylaştıran iletiler sağlar. Bizim de bu yazıda yapacağımız, ikinci grupta geçen seçeneklere bakmak olacak.İşimize neden üç sınıf bulunduğuna açıklık getirerek başlayalım.
StringBuilder
sınıfının varlık nedeni, içeriği değişmeyen karakter katarlarını temsil eden String
sınıfının aşağıdaki kullanımının çalışma hızına dönük yorumla açıklanabilir.public class SBuilder { ... public static String ekle(String katar, char[] ek) { String sonuç = katar; for (int i = 0; i < ek.length; i++) sonuç = sonuç + ek[i]; return sonuç; } // String ekle(String, char[]) ... } // SBuilder sınıfının sonu ... String ktr = "Ali"; char[] dizi = {'V', 'e', 'l', 'i'}; ktr = SBuilder.ekle(ktr, dizi);İlk argümanındaki karakter katarına ikinci argümanındaki karakter dizisinin eklenmesi ile elde edilen sonucu döndüren
ekle
, işaretlenmiş satırdan dolayı işini yüksek maliyetli bir biçimde yapmaktadır. Benzer bir gözlem, son satırdaki atama için de geçerlidir. Bunun sebebi, String
nesnelerinin değişmez içeriğe sahip olması ve içerik değişikliği etkisinin bitiştirme işleci (+) sonucunun aynı tutacağı güncellemesi yoluyla sağlanmasında yatar. Ne kastedildiğini döngü içindeki komutun işlemesi sırasında arka planda olup bitenleri açarak anlamaya çalışalım. sonuç
tarafından gösterilenString
nesnesinedizi
'nin döngü değişkeni ile gösterilen indisteki elemanının eklenmesi ile elde edilen yeni birString
nesnesi yaratılır.- Yapılan atama sonrasında
sonuç
yeni yaratılan nesneyi göstermeye başlar. Bu işlemin bir diğer sonucu olarak,sonuç
tutacağının göstermekte olduğu eski nesne erişilmez olur ve çöp haline gelir.
StringBuilder
sınıfı tarafından sağlanır. Yani, özetlemek gerekirse, String
içeriği değişmeyen karakter katarlarını soyutlarken, StringBuilder
içeriği değişen, uzayıp kısalabilen karakter katarlarını soyutlar.Bu noktada, sadece
StringBuilder
sınıfı ile yetinip içeriği değişmeyen karakter katarlarının kullanıcının disiplinli programlamasına bırakılmasıyla tek bir sınıfın yetebileceğini düşünenleriniz çıkabilir. Doğru ya, neden durup dururken karakter katarlarını içeriği değişen ve değişmeyen diye ayıralım ki? Bu haklı sorunun yanıtı, sabit içeriğin çok izlekli≝li (İng., thread) programlarda sağladığı avantajda yatar. Birden çok denetim akışının aynı nesneyi kullanması durumunun ortaya çıktığı bu tür programlarda, nesne içeriğinin değişkenlik göstermesi halinde söz konusu nesneye değişik izlekler içinden eşgüdümlü ve sırasallaştırılmış bir biçimde erişilmesi gerekir. Her zaman doğru yerine getirilmeyen bu koşul, çalışma hızını olumsuz etkilediği gibi programlamayı da zorlaştırır. Sabit içerikli nesnelerde ise durum farklıdır; içeriğin güncellenmeyeceği bilindiği için, söz konusu nesne izlekler içinden herhangi bir sırada eşgüdüm kaygısı olmadan okunabilir. Dolayısıyla, hem programlama daha kolay olacaktır hem de ortaya çıkan program daha hızlı çalışacaktır. Bu nedenle, String
ve StringBuilder
sınıflarının her ikisine de gereksinim vardır. Peki, ya üçüncü sınıf StringBuffer
? StringBuilder
ile tamamıyla aynı işlevselliğe sahip olan bu sınıf, StringBuilder
'ın performans kaygıları nedeniyle sağlamadığı birden çok izlek içinden güvenli kullanım garantisini ve String
'in desteklemediği içerik değişkenliği özelliklerini birleştirir.Değişken içerik | Çok izleklilik | |
String | ✘ | ✔ |
StringBuffer | ✔ | ✔ |
StringBuilder | ✔ | ✘ |
Not: Yazımızın devamında yukarıdaki farklılıkları dışında işlevsel olarak birbirlerinin aynısı olan
StringBuilder
ve StringBuffer
sınıflarını ayrı ayrı incelemeyeceğiz; birisi için yapacağımız açıklamalar diğeri için de geçerli olacak.Karakter katarlarını temsil etmek için neden üç tane sınıf sağlandığına açıklık getirdikten sonra, dışsallaştırma desteğine işaret eden
java.io.Serializable
arayüzüne ek olarak her üç sınıfa da ortak olan CharSequence
arayüzündeki iletilerin açıklamaları ile devam edelim. Object
sınıfındaki ilişkin metodun gerçekleştiren sınıflarda ezilmesini garanti etmek için toString
'i içeren bu arayüz, katar içindeki karakter sayısını döndüren length
ve argümanındaki indiste bulunan karakteri döndüren charAt
iletilerine ilaveten hedef nesnenin argümanlarda belirtilen aralıktaki dilimini CharSequence
tutacağı ile gösterilecek şekilde döndüren subSequence
iletisini içerir.Her üç karakter katarı sınıfında da bulunan diğer iletiler, hedef nesnenin içeriğini sorgulamaya yarayan iletilerdir. Bunlardan
substring
, hedef nesnenin belirtilen dilimi içindeki karakterlerden oluşan bir String
döndürür ve iki uyarlamaya sahiptir. Uyarlamalardan ilki argümanlarda verilen sınırlar dahilindeki dilimi döndürürken, tek argümanlı olan ikinci uyarlama yegâne argümanda geçirilen indiste başlayıp katarın sonuna kadar tüm karakterleri kapsayan dilimi döndürür.alıcı.substring(i, alıcı.length()) ≡ alıcı.substring(i)
Ortak olan bir diğer ileti grubu, karakterlerin Java temsilinde kullanılan UTF-16 kodlamasının bir özelliğini hatırlamamızı gerektiriyor: Her ne kadar modern dillere ait tüm simgeler iki sekizli genişliğindeki
char
değerlerle temsil ediliyorlarsa da, kimi özel ilgi gruplarının yararlandığı simgeler dört sekizliyle temsil edilir. (Ayrıntı için, buraya🔎 bakınız) Bir diğer deyişle, çoğu karakter (simge) tek bir char
ile temsil edilirken kimileri ardışık iki char
ile temsil edilebilir. Bu tür karakterleri içeren katarlara charAt
iletisinin gönderilmesi, beklenen sonucu vermeyecektir; sorgulamanın başarıyla yapılması karaktere dair her iki char
değerin birlikte okunmasıyla mümkün olur ki, incelediğimiz sınıflar bu amaçla charAt
yerine kullanılmak üzere codePointAt
iletisini sağlar. Argümanındaki indiste bulunan char
değeri okuyan bu ileti, okuduğu değerin özel gösterimli karakterlerden birine ait olması durumunda ikinci bir char
daha okur ve sonucu olarak okuduğu değeri—duruma göre bir veya iki char
—bir int
olarak döndürür. Benzer bir şekilde çalışan codePointBefore
, kendisine geçirilen indisin öncesindeki karakteri döndürür. Bu noktada, bir hatırlatmanın yapılması yerinde olacaktır: geçirilen indisin değeri codePointAt
için 0 ile katardaki char
sayısının bir eksiği aralığında iken, codePointBefore
için 1 ile katardaki char
sayısı aralığında olmalıdır. İşini karakterlerin temsilindeki özel istisnayı göz önüne alarak gören bir diğer ileti, katar içinde kaç tane char
olduğunu döndüren length
yerine kullanılması gerekebilecek codePointCount
iletisidir. Bu ileti, argümanlarında belirtilen indisler arasındaki karakterlerin, char
değerlerin değil, sayısını döndürür. Dört sekizli ile temsil edilen karakterlerin dilimin başı ve/veya sonunda yarıda kesilmesi durumunda ise, karakter(ler)in varlığı döndürülen int
değere yansıtılır. Herhangi bir indisin işaret ettiği konumdaki karakterin yarıda kesilip kesilmediği ise, offsetByCodePoints
iletisi yardımıyla öğrenilebilir. Bu ileti, ilk argümanında verilen indis değerinden başlayarak ikinci argümanındaki sayı kadar sonraki karakterin hangi indiste başladığını döndürür.indexOf
ve lastIndexOf
iletilerinin ilk argüman olarak String
alanları da her üç sınıfa ortaktır. Bu iletilerden indexOf
, ilk argümanında geçirilen String
nesnenin hedef nesne içinde ilk hangi indiste geçtiğini döndürürken, lastIndexOf
son geçişin indisini döndürür. Aranan katarın hedef nesnede bulunmaması durumu ise dönüş değerindeki -1 ile haber verilecektir. İstenecek olursa, her iki ileti de aramalarını aldıkları ikinci bir argümanın sonrasına/öncesine sınırlayabilir. Bu noktada, tüm iletilerin String
sınıfına özel olmak üzere, String
yerine char
türünde ilk argüman alarak çalışan uyarlamaları da olduğu söylenmelidir.Karakter katarı sınıflarına ait nesnelerin yaratılması için kullanabileceğimiz yapıcılara bakarak devam edelim. Her üç sınıf da varsayılan yapıcıya ek olarak argümanındaki
String
nesnenin kopyasını kullanarak ilkleme yapan yapıcılara sahiptir. Ancak, StringBuilder
ve StringBuffer
sınıflarının ortak bir özelliği, yapıcı kullanımı noktasında unutulmamalıdır: int
argüman geçirilerek çağrılan yapıcı dışında, her iki sınıfta da nesnenin ilklenmesinde kullanılan karakterlerin ötesinde 16 char
'lık bir bölge nesneye eklemeler yapılması durumunda kullanılmak üzere fazladan ayrılır. Bu gerçeği, aşağıdaki kod parçasının son satırındaki uzunluk ve sığayı döndüren length
ve StringBuilder
ile StringBuffer
'a özel capacity
iletilerinin ürettiği çıktıdan da gözlemleyebiliriz.String ad = "Tevfik", soyad = new String("Aktuğlu"); String adSoyad1 = ad.concat(" " + soyad); StringBuilder adSoyad2 = new StringBuilder(adSoyad1); System.out.println(adsoyad2.length()); // ⇒ 14 System.out.println(adsoyad2.capacity()); // ⇒ 30
Yukarıdaki kod parçası incelendiğinde bazı noktalar dikkatinizi çekecektir. Öncelikle,
String
nesneleri new
işleci ve sınıf adı olmaksızın, ilklemede kullanılacak katarın tırnak içinde yazılması ile de yaratılabilir. Bir diğer deyişle, String
türlü tutacaklar derleyici tarafından String
türlü olarak görülen karakter katarı sabitleri ile ilklenebilirler. Ancak, bunun özel bir durum olduğu ve sizi şaşırtacak sonuçlara neden olabileceği unutulmamalıdır. Ne demek istediğimizi aşağıdaki kod parçasını inceleyerek görelim: bir önceki kod parçası ile aynı biçimde yaratılan iki nesnenin eşitlik denetimleri beklediğimiz sonuçları verirken, aynılık denetimleri birbirleriyle farklı sonuçlar vermekte.String ad2 = "Tevfik", soyad2 = new String("Aktuğlu"); System.out.println(soyad.equals(soyad2)); // ⇒ true System.out.println(ad.equals(ad2)); // ⇒ true System.out.println(soyad == soyad2); // ⇒ false System.out.println(ad == ad2); // ⇒ trueAynılık denetiminin beklenilenin aksine
true
döndürmesinin nedeni, karakter katarı sabitleri için yerin String
sınıfının yönetimindeki bir içrek≝ bellekten ayrılmasıdır. Yer ayrımı öncesinde söz konusu sabite eşit bir değer için bu özel bölgeden daha önce yer ayrılıp ayrılmadığına bakılır ve yanıtın olumlu olması durumunda yeni yaratılacak bir String
nesnenin tutacağındansa, önceden yaratılmış olan nesnenin tutacağı döndürülür. Örneğimize dönecek olursak; ad
tutacağının ilklenmesi noktasında içrek belleğin "Tevfik" değerine sahip bir katar içermemesi nedeniyle [içrek bellekte] yeni bir nesne yaratılarak döndürülmüş, ad2
'nin ilklenmesi noktasında ise içrek bellekte aynı değerli bir katarın varlığı saptandığı için daha önceden yaratılan bu nesne kullanılmıştır. Yerden tasarruf sağlamasının yanısıra eşitlik denetimini aynılık denetimine indirgeyen bu özellik, iki karakter katarı sabitinin eşitlik denetiminde ==
işlecinin kullanılmasına olanak tanıyarak hızdan da kazandıracaktır.Karakter katarı sabitleri için geçerli olan içrekleştirme—yani, yerin içrek bellekten ayrılması—
intern
iletisi vasıtasıyla diğer String
nesnelerine de yaygınlaştırılabilir. Bu ileti, hedef nesnenin içeriğine sahip olan fakat yeri içrek bellekten ayrılmış bulunan bir nesnenin tutacağını döndürür. Ancak; intern
iletisinin tüm String
nesneler için kullanılmasının yığın belleğin bir bölgesi olan içrek belleği kolayca doldurup taşırabileceği unutulmamalıdır.String soyad3 = soyad.intern(); String soyad4 = soyad2.intern(); System.out.println(soyad3 == soyad4); // ⇒ trueYapıcılara dair ilk örneğimizde▵ açıklama bekleyen ikinci nokta, bitiştirme işleci (
+
) ile benzer bir işlev gören concat
iletisidir. Argümanlarından birisinin String
olması halinde diğerini de String.valueOf
metotlarından uyumlu türe sahip olanını çağırarak String
'e dönüştürüp diğeri ile bitiştiren +
işlecinin aksine, concat
iletisinin yegâne argümanının String
olmaması derleyici hatasına neden olur.1 Ayrıca, concat
argümanındeki karakter katarının boş olması durumunda yeni bir nesnenin tutacağı yerine ileti alıcıyı döndürür.String
sınıfının yapıcıları arasında, StringBuffer
ve StringBuilder
nesnelerinden String
nesnelerine dönüşümü sağlayan çeviren yapıcılara ek olarak, byte
, char
ve int
dizilerini String
nesnesine dönüştürenler de vardır. Bu yapıcılar, aynı veya farklı makinelerdeki süreçler arasında akan verilerin String
olarak işlenmesini sağlamak için gereklidir. Örnek olarak, yazdığınız Java programının iletişimde bulunduğu bir C programından kendisine gönderilen karakter verileri işlemek istediğini düşünün. Karakter desteğinin ASCII tablosu üzerine inşa edilmesi nedeniyle tek sekizli büyüklüğündeki bir char
türüne sahip C tarafından gönderilen karakterler, Java tarafında byte
dizisi olarak görülecektir. Bu dizi içindeki karakterlerin ASCII kodlamasına uygun bir şekilde Java programında kullanılabilir bir karakter katarına çevrilmesi gereklidir. Bunun için yapılması gereken aşağıdaki gibi bir yapıcının kullanılması olacaktır.2 Yapıcının ilk argümanı dönüşüme kaynaklık eden diziyi sağlarken, ikinci argüman dönüşümde kullanılacak karakter kodlamasını belirtir. İstenecek olursa, dizinin tamamı yerine bir dilimi kaynaklık edebileceği gibi, kodlama bir java.nio.charset.Charset
nesnesi vasıtasıyla da bildirilebilir veya sağlanmadığı durumlarda Java platformunun o anki kodlaması olarak varsayılabilir. Mesela, aşağıdaki kod parçasının son satırı, tampon
'da biriktirilmiş byte
'ların ilk indisten başlayarak 100 tanesini, o anki kodlamayı kullanarak String
nesnesine çevirmektedir.byte tampon[] = new byte[1024]; ... // Karşı taraftan akan veriyle tampon'u doldur. String katar = new String(tampon, "US-ASCII"); ... // katar'ı kullan. ...// tampon'u yeniden doldur. katar = new String(tampon, 0, 100);Bu noktada, karşı taraftaki program C değil de Java programı olsaydı ve
byte
yerine char
gönderiliyor olsaydı diye sorabilirsiniz. Hatta, ya gönderilen veri UTF-16'da iki char
ile temsil edilebilen istisnai karakterleri de içeriyor olsaydı diye üsteliyebilirsiniz. Rahat olun, sorularınız yanıtlanacak ama ilk önce Java'dan C tarafına veri göndermek istediğimizde ne yapmamız gerektiğine bir göz atalım. Bu durumda, String
nesnesi içindeki karakterlerin karşı taraftaki programın farkında olduğu bir kodlama ile byte
dizisine dönüştüren getBytes
işimizi görecektir. Hedef nesnedeki tüm karakterleri argümanındaki String
veya Charset
nesnesiyle belirtilen kodlamaya göre byte
dizisine çeviren getBytes
, argümansız kullanıldığı takdirde dönüşümü platformun o an varsayılan kodlamasını kullanarak yapar.StringBuffer katar = new StringBuffer(...); ... // katar'ı güncelle. String katar2 = new String(katar); byte tampon[] = new byte[1024]; katar2.getBytes("US_ASCII") ... // katar2'nin içeriğini karşı tarafa akıt.
String
nesnesinden char
dizisine benzer bir dönüşüm ise, diğer karakter katarı sınıflarınca da desteklenen getChars
ile yapılabilir. Hedef nesnenin ilk iki argümanında belirtilen dilimini son argümandaki indisten başlayarak üçüncü argümanda sağlanan diziye kopyalayan bu ileti, char
değerlerin ve String
içindeki karakterlerin aynı kodlamayı kullanması nedeniyle kodlama bilgisine gereksinim duymaz. Aynı işi çok daha basit bir imza ile halletmek isterseniz, hedef nesnenin bütün karakterlerini hedef nesnenin uzunluk özelliğine göre yeri ayrılmış bir char
dizisine kopyalayan ve sonucu olarak bu diziyi döndüren toCharArray
iletisini de kullanabilirsiniz.Gelelim iki Java programı arasında gidip gelen
char
'ların String
nesnesine dönüştürülmesi işine. Bunun için char
dizisi veya dizi dilimi alan yapıcılardan biri kullanılabileceği gibi, aynı parametre listelerine sahip uyarlamaları bulunan String.copyValueOf
metotları da kullanılabilir. Kimi zaman aynı iş için int
dizisi veya dizi dilimi alan yapıcıların kullanılması gerekebilir. Bu gereklilik iki nedenden ötürü ortaya çıkar: i) veri kaynağında UTF-16'nın tek char
'da temsil edemediği karakterlerin var olması, ii) okuduğu char
değere ek olarak aykırı durumlar için de kullanılan ekstra dönüş değerlerinden dolayı int
döndüren java.io.BufferedReader
sınıfındaki read
ve benzeri iletilerin doldurduğu dizilerin işlenmesi.Alışageldiğimiz eşitlik denetimi (
equals
), hoş yazım (toString
) ve kıyım fonksiyonunun (hashCode
) ezilmesine ek olarak, Comparable
arayüzünü gerçekleştiren String
sınıfı, doğal olarak, compareTo
iletisine karşılık bir metot sağlar. Ayrıca, büyük-küçük harf ayrımı gütmeksizin işini gören eşitlik denetimi (equalsIgnoreCase
) ve karşılaştırma (compareToIgnoreCase
) iletileri de sunulur. Bu iki iletiyi, toLowerCase
iletisinden de yararlanarak şu şekilde tanımlamamız mümkündür.3alıcı.equalsIgnoreCase(k)
≡
alıcı.toLowerCase().equals(k.toLowerCase());
alıcı.compareToIgnoreCase(k)
≡
alıcı.toLowerCase().compareTo(k.toLowerCase());
compareTo
ve compareToIgnoreCase
iletilerini Türkçe içerikli katarları karşılaştırmak için kullandığınızda beklemediğiniz bir sürprizle karşılaşabilirsiniz. Çünkü, bu iletilerin katarlardaki aynı indisli char
'ların Unicode tablosundaki sıralarının farkını döndürerek işini gören String
sınıfındaki gerçekleştirimleri, Türkçe'de olup İngilizce'de olmayan harflerin Unicode tablosunda diğerlerinden sonra gelmesi nedeniyle, aşağıdaki gibi alfabemizdekiyle çelişkili sonuçlar döndürebilir.boolean önceMi = "a".compareTo("b") > 0; // önceMi ← true önceMi = "ş".compareTo("t") > 0; // önceMi ← falseBu can sıkıcı durumun giderilmesi, bir yörenin (
Locale
) özelliklerine bağlı kalarak karşılaştırma yapan karşılaştırıcı nesne (Collator
) kullanımı ile olanaklıdır.import java.text.Collator; import java.util.Locale; ... Collator tr = Collator.getInstance(new Locale("tr")); ... önceMi = tr.compare("ş", "t") < 0; // şÖncet ← trueEşitlik denetimi amacıyla kullanılabilecek bir diğer ileti,
StringBuffer
ve CharSequence
türlü iki uyarlaması bulunan tek argümanlı contentEquals
iletisidir. equals
iletisinin varlığında gereksiz gibi gözükebilecek bu ileti, derleyiciye sağladığı ekstra hata denetimi imkanı ve performans avantajından dolayı tercih edilmelidir. equals
iletisinin Object
türlü bir argümana sahip olmasının bir sonucu olarak, bu iletinin String
sınıfı içindeki gerçekleştirimi olan metotta, ilk yapılan şey argümanın String
türlü olup olmadığının kontroludur. contentEquals
iletisinin gerçekleştirimlerinde ise, daha sıkı bir parametre türü bulunduğu için böylesine bir kontrola gerek yoktur.Tabii, bazılarınız
StringBuffer
'a özel bir uyarlamanın bulunup da StringBuilder
'a özel bir uyarlamanın neden bulunmadığını sorabilir. Bu haklı sorunun yanıtı, StringBuilder
'ın değişken içerikli ve tek izlekli olmasında yatar. Bu sınıf, nesnelerinin aynı anda tek bir izlek içinden kullanılacağı varsayılarak gerçekleştirilmiş, çok izlekli kullanım durumlarında ise sorumluluğu programcıya bırakmıştır. Dolayısıyla, StringBuilder
sınıfı çok izlekli bir programda eşitlik denetiminin başladığı noktadan bittiği noktaya kadar karşılaştırılmaya söz konusu olan nesnenin bir başka izlek içinden değiştirilmeyeceğini garanti edemez. Bu ise, sonucun döndürüldüğü noktada bile yanlış olabileceği anlamına gelir. Aşağıdaki maddeleri izleyerek böyle bir durumun nasıl ortaya çıkabileceğini görelim. - (İzlek 1) Eşitlik denetimi başlar. İki uzunluklu katarlardaki (
String
veStringBuilder
) ilkchar
'ların eşit olduğu görülür. - (İzlek 2) Argüman olarak geçirilen
StringBuilder
nesnesinin ilk indisindekichar
değiştirilir. - (İzlek 1) İkinci ve son
char
'ların eşit olduğu görülerektrue
döndürülür. Halbuki, diğer izlek tarafından yapılmış değişiklik nedeniyle, bu sonuç o anki durumu yansıtmamaktadır.
StringBuilder
nesnelerinin String
nesneleri ile eşitlik denetiminin equals
'a mahkum edilerek bu sınıfa haksızlık edildiğini düşünüyorsanız, size bir kere daha her üç karakter katarı sınıfının da CharSequence
arayüzünü gerçekleştirdiğini hatırlatayım. İstenecek olursa, contentEquals
iletisinin CharSequence
tutacağı bekleyen uyarlaması kullanılarak equals
'a göre hızlı bir karşılaştırma yapılabilir. Ancak, bu bağlamda şu uyarının yapılması yerinde olacaktır: StringBuilder
ve StringBuffer
sınıfları kısa süre içinde yoğun bir şekilde değişmesi beklenen karakter katarlarını modellemek için düşünülmüştür; daha sonra yararlanılacak gerekli bilgiyi biriktirmek için tampon olarak kullanılan ve tipik olarak kısa ömürlü olan bu sınıflara ait nesnelerin eşitlik denetimi ve karşılaştırma gibi işlemlere konu olması beklenmez. Bundan dolayıdır ki, her iki sınıf da Comparable
arayüzünü gerçekleştirmez, Object
sınıfından kalıtladıkları equals
ve hashCode
metotlarını ezmez.
equals
iletisinin bir diğer akrabası olan regionMatches
, hedef nesnenin bir diliminin kendisine geçirilen bir diğer String
nesnenin eşit uzunluklu dilimi ile eşit olup olmadığı sorusuna yanıt bulur. İstendiği takdirde, geçirilecek bir bayrak değeri ile eşitlik denetiminin büyük-küçük harf ayrımı yapıp yapmaması da kontrol edilebilir.Eşitlik denetimini andıran
matches
yüklemi, hedef nesnenin argümanında sağlanan düzenli ifadenin (İng., regular expression) betimlediği karakter katarlarından biri olup olmadığına bakar. Örneğin, aşağıdaki kod parçasının son satırında "H" veya "Hayır" girilmesi durumunda programdan çıkılması isteniyor. import java.util.Scanner; ... System.out.println("Devam etmek istiyor musunuz? "); String devamıMı = new Scanner(System.in).nextLine(); ... if (devamMı.matches("H(ayır)?")) System.exit(0);Düzenli ifade kullanarak işini gören bir diğer ileti, hedef nesnenin argümandaki düzenli ifadeyle betimlenen karakter katarlarının bulunduğu konumlardan bölündüğü alt katarları
String
dizisi içinde döndüren split
'tir. Örneğin, aşağıdaki kod parçasının son satırı, satır
içinde birbirlerinden ";" veya ":" ile ayrılmış olan katarları döndürecektir. İstenecek olursa, split
'e ikinci argümanında geçirilen bir int
değerle parçalardan ilk kaçının kullanıcıyı ilgilendirdiği de belirtilebilir. String satır; ... // satır'ın içine bir şeyler doldur. String[] parçalar = satır.split(";|:");Göz atacağımız son düzenli ifade kullanan
String
sınıfı iletileri, ilk argümanlarında betimlenen karakter katarlarının yerine ikinci argümanlarındaki String
nesnenin içeriğini yerleştiren replaceAll
ve replaceFirst
iletileridir. Bunlardan ilki, istenen değişikliği tüm noktalarda uygularken, ikincisi sadece uyan ilk noktadaki parçayı değiştirir. Buna göre, aşağıdaki kod parçasının son satırı sonrasında, yeniSatır
satır
'ın başı ve sonu boşluklardan arındırılmış ve içindeki boşlukların "?" ile değiştirilmiş halini tutacaktır. Anlatım dilinin olası aldatıcılığını düşünerek String
nesnelerinin değişmezlik özelliğini bir kez daha hatırlatmakta yarar var: hedef nesne—örneğimizde satır
'ın gösterdiği nesne—kesinlikle değişmemektedir; dönüşümler döndürülen String
nesneye—örneğimizde yeniSatır
'ın gösterdiği nesne—yansıtılmaktadır. String satır; ... // satır'ın içinde bir şeyler oku. String yeniSatır = satır.trim().replaceAll(" ", "?");İlk argümanda belirtilen değerin hedef nesne içinde geçtiği tüm konumlarda ikinci argümandaki ile değiştirildiği hedef nesne mutantını döndüren
replace
iletisi, char
ve CharSequence
argümanlı olmak üzere iki uyarlamaya sahiptir.Biraz nefes alarak, yolumuza
String
nesnelerinin içerik denetiminde yararlanılabilecek yüklemlere göz atalım. Hedef nesnenin boş olup olmadığına yanıt veren isEmpty
'ye ek olarak, endsWith
ve startsWith
, sırasıyla, hedef nesnenin argümandaki String
nesne ile sonlanıp sonlanmadığı ve başlayıp başlamadığı sorusunu yanıtlarken, contains
argümanındaki CharSequence
kategorisindeki nesnenin hedef nesnede geçip geçmediğine yanıt verir. startsWith
ile sorulan sorunun ikinci bir argüman geçirilmek suretiyle hangi indisten başlayarak geçerli olduğu da söylenebilir.Programlamanın
printf
ve scanf
'den ibaret olduğunu düşünen C gezegeni vatandaşları, eğer bu noktaya kadar sabredebildilerse, bayram edebilirler: String
sınıfından göz atacağımız son şey, sprintf
fonksiyonunun dengi olan format
metodu. Bu metot, ilk argümanında sağlanan format katarının dönüştürülmesiyle elde edilen String
nesnesinin tutacağını döndürür. java.util.Formatter
sınıfında anlatılan kurallara göre oluşturulan format katarındaki dönüşüm, format
'a geçirilen diğer argümanların format katarı içinde eşleştirildiği bildirime göre String
'e dönüştürülüp format katarı içine yerleştirilmesi ile yapılır. Örneğin; aşağıdaki tanımların ardından, a
'nın %d
ile 10'lu tabana, b
'nin %o
ile 8'li tabana, a + b
'nin %x
ile 16'lı tabana ve %n
'nin yeni satır karakterine dönüştürülerek format katarı içine yerleştirilmesiyle ktr
değişkeni "a(37) + b(111) = 6e\n" ile ilklenecektir. int a = 37, b = 73; String ktr = String.format("a(%d) + b(%o) = %x%n", a, b, a + b);Gelelim
StringBuilder
ve StringBuffer
sınıflarına. Bu sınıflar, değişken içerik ve ekleme sonrasında ortaya çıkacak uzamanın maliyetini azaltmaya dönük olarak tutulan boş bölge nedeniyle, String
sınıfından farklı olarak ekleme, silme ve değiştirme iletileriyle sığa işlemleme işlemlerini de destekler. Daha önce de söylendiği gibi, bu sınıfların nesnelerinin yaratılması sırasında ilklemede kullanılan katarın ihtiyacından on altı char
daha geniş bir yer ayrılır. Nesnenin kullanımı esnasında, taşma olmadığı müddetçe eklemeler bu fazlalık alandan yararlanılarak yapılacak ve taşmanın söz konusu olduğu ilk noktada nesneyi tutan bellek bölgesi genişletilecektir.İçeriği güncelleyen iletilerin değişiklikleri hedef nesnede yaptığı ve ileti alıcıyı döndürdüğünün bilinmesi zincirleme ileti gönderimini ve dolayısıyla daha akıcı okunabilen kod yazımını olanaklı kılacaktır. Örnek olarak aşağıdaki kod parçasının 7. satırını ele alalım. Bu satır, zincirleme ileti gönderme özelliği sayesinde
önder.insert();
ve önder.trimToSize();
gönderilerinin arka arkaya gönderilmesiyle elde edilecek etkiyi yaratacaktır. Benzer bir durum 4. satır için de söz konusudur. Ayrıca, append
ve insert
iletilerinin tüm ilkel ve bileşke türlü değerleri alabilecek şekilde aşırı yüklenerek gerçekleştirildiğinin de bilinmesi gereksiz yere kullanılacak dönüşümleri ortadan kaldırmak suretiyle daha kısa kod yazılmasını olanaklı kılacaktır. StringBuffer ad = new StringBuffer("Mustafa"); StringBuilder önder = new StringBuilder(ad); System.out.println(önder.capacity()); // ⇒ 23 önder.append('_').append("Atatürk"); önder.setCharAt(7, ' '); System.out.println(önder.capacity()); // ⇒ 23 önder.insert(8, "Kemal ").trimTosize(); System.out.println(önder.capacity()); // ⇒ 21 int i = önder.indexOf("Atatürk"); önder.replace(i, önder.length(), "ATATÜRK"); System.out.println(önder); // ⇒ Mustafa Kemal ATATÜRKYukarıdaki kod parçasında iki noktaya açıklık getirilmesinde yarar var. Öncelikle, yaratılmakta olan nesneyi argümanında geçirilen
StringBuffer
nesnenin içeriği ile ilkleyen ikinci satırdaki yapıcı çağrısının StringBuilder
sınıfında desteklenmediğini düşünen arkadaşları yatıştıralım. StringBuilder
sınıfında StringBuffer
argüman alan bir yapıcının bulunmaması ile telaşlanan bu arkadaşlar her üç karakter katarı sınıfının da CharSequence
arayüzünü desteklediğini ve arayüz tutacaklarının çokbiçimli kullanımını anımsarlarsa rahatlayacaklardır. Çünkü, StringBuilder
—ve onun çok izlekli uyarlaması olan ikizi StringBuffer
—CharSequence
arayüzü türlü tutacak bekleyen bir yapıcıya sahiptir. Dolayısıyla, StringBuffer
nesneler CharSequence
kategorisinde oldukları için, 2. satırda CharSequence
türlü tutacak bekleyen yapıcı çağrılacaktır. Açıklık getirilmesi gereken ikinci nokta, ekleme ve silme işlemlerinin etkiledikleri indis değerlerine bağlı olarak maliyetlerinin artabileceğidir. 6. satırdaki insert
iletisini ele alalım. Bu gönderi, hali hazırda on beş karakter içeren bir katara 8. indisten başlayarak altı karakterli bir katar eklemektedir. Bu isteğin yerine getirilebilmesi için, sondaki yedi karakterin kaydırılarak eklenecek karakterler için yer açılması gerekir. Gönderilerin düzenlenerek önder.append(' ').append("Kemal ").append("Atatürk")
append
'in maliyeti daha düşüktür. Hatta, gönderilerin birleştirilerek tek bir postada işin halledilmesi daha da iyi olacaktır. Son olarak; benzer maliyet kaygılarının argümanında sağlanan indisteki karakteri silen deleteCharAt
ve argümanlarında belirlenen katar dilimini silen delete
iletileri için de geçerli olduğu unutulmamalıdır. append
ve insert
iletilerinin tamsayı türlü değerler geçirilerek kullanılan uyarlamalarında şu nokta unutulmamalıdır: hedef nesneye eklenen, sayının hoş yazımla elde edilen gösterimidir, karakter tablosunun sayı ile belirtilen indisindeki karakter değil. İstenenin ikinci seçenek olması durumunda int
argüman alan appendCodePoint
iletisinin kullanılması gerekir. Aşağıdaki kod parçasını takip ederek farkı anlamaya çalışalım. İkinci satırdaki append
boş katara "65" eklerken, bir sonraki satırda gönderilen appendCodePoint
iletisi katar sonuna Unicode tablosunun 65. konumundaki 'A' karakterini ekleyecektir. StringBuilder katar = new StringBuilder(""); katar.append(65); katar.appendCodePoint(65); System.out.println(katar); // ⇒ 65A
capacity
, ensureCapacity
ve trimToSize
Vector
🔎 sınıfındaki adaşları ile benzer şekilde çalışırken, length
iletisi katardaki char
sayısını döndürür. setLength
ise, geçirilen argümanın ve o anki uzunluk değerine göre katarı uzatıp kısaltabilir; argümanın o anki uzunluktan büyük olması durumunda hedef nesne uzatılarak yeni konumlar '\u0000' değeri ile doldurulurken, yeni uzunluk değerinin eskisinden küçük olması durumunda hedef nesne yeni uzunluk ile eskisi arasındaki değerlerin kırpılması ile küçültülecektir.StringBuilder
ve StringBuffer
sınıflarındaki iletilerden değineceğimiz sonuncusu, hedef nesnenin içeriği yerinde tersine çeviren reverse
iletisidir. Gelin bu iletiyi kullanan ufak bir örnekle yazımızı bitirelim. import java.io.Console; import java.util.Locale; public class Palindromik { public static void main(String[] ksa) { System.out.print("Palindromikliği sınanacak katar: "); String sözcük = System.console().readLine().trim().toUpperCase(new Locale("TR")); String tersi = new StringBuilder(sözcük).reverse().toString(); if (sözcük.equals(tersi)) System.out.println("palindromik"); else System.out.println("palindromik değil"); } // void main(String[]) sonu } // Palindromik sınıfının sonuYukarıdaki kodu düzenleyerek bazı alternatif çözümler geliştirebilirsiniz. Mesela, küçük harften büyük harfe çeviren
toUpperCase
yerine ters yönde çeviri yapan toLowerCase
iletisini kullanmanız aynı işi görecektir. Ya da, equals
yerine compareTo
iletisi kullanılarak dönen değerin 0 ile eşitlik denetiminin yapılması da aynı kapıya çıkacaktır. Ancak; String
sınıfını işe bulaştırmadan kullanılacak bir equals
Object
sınıfındaki aynılık denetimi yapan aynı adlı metodu çağıracağı için beklediğinizden farklı sonuçlar verecektir. Çünkü, StringBuilder
ve StringBuffer
sınıfları, değişken içerikli ve genelde kısa ömürlü karakter katarı tamponlarını soyutlar; bu özellikteki nesnelerin eşitlik denetimi ve karşılaştırılmaları içeriklerinin değişkenliği nedeniyle pek anlamlı değildir. Yapılması gereken, ihtiyaç duyulan bilginin StringBuilder
veya StringBuffer
nesnesi içinde doldurulması sonrasında String
nesnesine çevrilmesi ve yola öyle devam edilmesidir.- Bitiştirme esnasındaki dönüşümün nesneye
toString
iletisi gönderilerek yapıldığını düşünenler yanılmıyorlar; dönüştürülecek değerin bileşke türlü olması durumunda işi kotaran gerçekten de hoş yazım iletisi olarak da bilinentoString
'dir. Ancak, hoş yazımı istenen değerin ilkel türden olması halinde, ileti alan tutacakları olmaması nedeniyle, işString.valueOf
metotlarından uygun olanı çağrılarak yerine getirilir. Aslına bakacak olursanız, bir nesnenin hoş yazımı için gönderilentoString
iletisi,Object
türlü argüman bekleyenString.valueOf
metodu içinden gönderilir. ↑ - Doğruyu söylemek gerekirse, böyle bir durumda
byte
'tanchar
'a dönüşümü otomatikman yapanjava.io.InputStreamReader
gibi bir akak sınıfının kullanılması daha yerinde olacaktır. ↑ - Benzer bir denklik,
toUpperCase
ile de kurulabilir. ↑