String Derinlemesine
String, Java'da en çok kullandığın veri tipi. Kullanıcı adı, e-posta, dosya yolu, hata mesajı — her yerde String var. Ama String'in bir sürü inceliği var ve bunları bilmezsen sinsi bug'larla tanışırsın.
Bu derste String'in iç yapısını, neden "özel" olduğunu ve doğru kullanım yollarını öğreneceğiz.
String Nedir?
String, karakter dizisi tutan bir sınıftır (class). Primitive tip değildir — bir nesnedir. Ama Java onu o kadar özel tutar ki, neredeyse primitive gibi kullanırsın.
String isim = "Ahmet"; // Literal ile oluşturma
String soyisim = new String("Yılmaz"); // new ile oluşturma (önerilmez)Çift tırnak arasına yazdığın her şey bir String literal'dir. Java bunu arka planda bir String nesnesi olarak oluşturur.
String Immutable'dır — Değiştirilemez
Bu, String hakkında bilmen gereken en önemli şey: bir String nesnesi oluşturulduktan sonra içeriği asla değişmez.
Bunu şöyle düşün: String bir taş tablete kazınmış yazı gibi. Yazıyı değiştiremezsin — ama yeni bir tablet yapabilirsin.
String mesaj = "Merhaba";
mesaj = mesaj + " Dünya";
System.out.println(mesaj); // "Merhaba Dünya"Burada "Merhaba" nesnesi değişmedi. Java yeni bir "Merhaba Dünya" nesnesi oluşturdu ve mesaj değişkeni artık bu yeni nesneye işaret ediyor. Eski "Merhaba" nesnesi bellekte öylece duruyor (garbage collector temizleyene kadar).
String s = "Java";
String s2 = s.toUpperCase(); // Yeni nesne oluşur
System.out.println(s); // "Java" — orijinal değişmedi!
System.out.println(s2); // "JAVA" — yeni nesne💡 String üzerinde çağırdığın her metot (toUpperCase, trim, replace vb.) orijinal String'i değiştirmez, yeni bir String döner. Bu çok önemli — unutma.
String Pool — Java'nın Akıllı Bellek Yönetimi
Java, String literal'lerini özel bir bellek alanında saklar: String Pool. Aynı içeriğe sahip iki literal aynı nesneyi paylaşır.
String a = "Merhaba";
String b = "Merhaba";
System.out.println(a == b); // true — aynı nesne!İkisi de pool'daki aynı "Merhaba" nesnesine işaret ediyor. Bu, bellek tasarrufu sağlar.
Ama new ile oluşturduğunda farklı:
String c = new String("Merhaba");
String d = new String("Merhaba");
System.out.println(c == d); // false — farklı nesneler!
System.out.println(c.equals(d)); // true — içerik aynınew her zaman heap'te yeni bir nesne oluşturur, pool'u kullanmaz.
equals() vs == — Kritik Fark
Bu, Java'da en çok yapılan hatalardan biri. İki String'in içeriğini karşılaştırmak istiyorsan `equals()` kullan, == değil.
==: İki referansın aynı nesneyi gösterip göstermediğini kontrol ederequals(): İki String'in içeriğinin aynı olup olmadığını kontrol eder
String s1 = "Java";
String s2 = "Java";
String s3 = new String("Java");
System.out.println(s1 == s2); // true (ikisi de pool'da)
System.out.println(s1 == s3); // false (s3 heap'te)
System.out.println(s1.equals(s3)); // true (içerik aynı)⚠️ Altın Kural: String karşılaştırmasında her zaman `equals()` kullan. == sadece pool'daki literal'ler arasında doğru çalışır — bu güvenilir değil.
// Doğru yol
if (isim.equals("Ahmet")) {
System.out.println("Hoş geldin Ahmet!");
}
// Daha güvenli yol — NullPointerException'dan korunur
if ("Ahmet".equals(isim)) {
System.out.println("Hoş geldin Ahmet!");
}İkinci yöntemde isim null olsa bile hata almassın, çünkü "Ahmet" literal'i null olamaz.
equalsIgnoreCase — Büyük/Küçük Harf Duyarsız
String girdi = "java";
System.out.println(girdi.equals("Java")); // false
System.out.println(girdi.equalsIgnoreCase("Java")); // trueKullanıcı girdisi alırken çok işe yarar. Kullanıcı "JAVA", "Java" veya "java" yazabilir — hepsini yakala.
Sık Kullanılan String Metotları
length() — Uzunluk
String s = "Merhaba";
System.out.println(s.length()); // 7Boş String ile null karıştırma:
String bos = "";
String nul = null;
System.out.println(bos.length()); // 0
// System.out.println(nul.length()); // NullPointerException!charAt(index) — Belirli Karakter
String s = "Java";
System.out.println(s.charAt(0)); // 'J'
System.out.println(s.charAt(3)); // 'a'
// s.charAt(4) → StringIndexOutOfBoundsException!İndeks 0'dan başlar, length() - 1'e kadar gider.
substring() — Alt Dize
String s = "Merhaba Dünya";
System.out.println(s.substring(8)); // "Dünya"
System.out.println(s.substring(0, 7)); // "Merhaba"substring(baslangic, bitis) — başlangıç dahil, bitiş hariç. Bu "dahil-hariç" kuralı Java'nın her yerinde geçerli.
indexOf() ve contains() — Arama
String s = "Java programlama dili";
System.out.println(s.indexOf("program")); // 5
System.out.println(s.indexOf("Python")); // -1 (bulunamadı)
System.out.println(s.contains("Java")); // true
System.out.println(s.contains("java")); // false (büyük/küçük harf duyarlı)split() — Bölme
String csv = "Ali,Veli,Ayşe,Fatma";
String[] isimler = csv.split(",");
for (String isim : isimler) {
System.out.println(isim);
}
// Ali
// Veli
// Ayşe
// FatmaString metin = "Merhaba Dünya";
String[] kelimeler = metin.split("\\s+"); // Bir veya daha fazla boşluk
System.out.println(kelimeler.length); // 2split() parametre olarak regex alır. Bu yüzden nokta ile bölmek istersen "\\." yazmalısın — çünkü regex'te . "herhangi bir karakter" demek.
trim() ve strip() — Boşluk Temizleme
String s = " Merhaba ";
System.out.println("[" + s.trim() + "]"); // [Merhaba]
System.out.println("[" + s.strip() + "]"); // [Merhaba] (Java 11+)
// Sadece baş veya son
System.out.println("[" + s.stripLeading() + "]"); // [Merhaba ]
System.out.println("[" + s.stripTrailing() + "]"); // [ Merhaba]trim() sadece ASCII boşluklarını temizler. strip() (Java 11+) Unicode boşluk karakterlerini de temizler. Modern Java'da strip() tercih et.
replace() — Değiştirme
String s = "Java çok zor";
String yeni = s.replace("zor", "kolay");
System.out.println(yeni); // "Java çok kolay"
System.out.println(s); // "Java çok zor" — orijinal değişmedi!// Tüm geçenleri değiştirir
String telefon = "0532-123-45-67";
String temiz = telefon.replace("-", "");
System.out.println(temiz); // "0532123456 7"toUpperCase() ve toLowerCase()
String s = "Java";
System.out.println(s.toUpperCase()); // "JAVA"
System.out.println(s.toLowerCase()); // "java"⚠️ Türkçe tuzağı: "title".toUpperCase() → "TITLE", ama Türkçe locale'de "ı".toUpperCase() → "I" değil, "I" olmalı. Locale belirtmen gerekebilir:
String s = "sığır";
System.out.println(s.toUpperCase(new java.util.Locale("tr", "TR")));
// "SIĞIR" — Türkçe İ/I kuralına uygunString.format() — Biçimlendirme
String ad = "Ahmet";
int yas = 25;
double not_ = 3.75;
String mesaj = String.format("Ad: %s, Yaş: %d, Not: %.2f", ad, yas, not_);
System.out.println(mesaj);
// "Ad: Ahmet, Yaş: 25, Not: 3.75"| Format | Açıklama | Örnek |
|---|---|---|
| %s | String | "Ahmet" |
| %d | Tam sayı | 42 |
| %f | Ondalıklı | 3.140000 |
| %.2f | 2 basamak ondalık | 3.14 |
| %n | Yeni satır | (satır sonu) |
| %b | Boolean | true |
Java 15+ ile text block ve formatted() metodu:
String html = """
<html>
<body>
<p>Merhaba %s!</p>
</body>
</html>
""".formatted(ad);String Birleştirme (Concatenation)
+ Operatörü
En basit yol:
String ad = "Ali";
String soyad = "Demir";
String tamAd = ad + " " + soyad;
System.out.println(tamAd); // "Ali Demir"Bir String ile başka bir tip birleştirildiğinde, diğer tip otomatik String'e çevrilir:
int yas = 30;
String mesaj = "Yaş: " + yas; // "Yaş: 30"
System.out.println("Sonuç: " + (10 + 20)); // "Sonuç: 30"
System.out.println("Sonuç: " + 10 + 20); // "Sonuç: 1020" — Dikkat!Son satırda soldan sağa işleniyor: önce "Sonuç: " + 10 → "Sonuç: 10", sonra "Sonuç: 10" + 20 → "Sonuç: 1020". Parantez kullanarak bu tuzaktan kaçın.
concat() Metodu
String s = "Merhaba".concat(" ").concat("Dünya");
System.out.println(s); // "Merhaba Dünya"Pratikte + operatörü daha okunabilir, concat() nadiren kullanılır.
StringBuilder — Performanslı String Birleştirme
String immutable olduğu için döngüde string birleştirmek çok verimsiz. Her + işlemi yeni bir nesne oluşturur.
// KÖTÜ — her adımda yeni String nesnesi
String sonuc = "";
for (int i = 0; i < 10000; i++) {
sonuc += i + ", "; // 10000 yeni String nesnesi!
}Bunu şöyle düşün: Her satırda yeni bir kağıt alıp tüm yazıyı baştan yazıyorsun. 10.000 kere. Çılgınlık.
StringBuilder bunu çözer:
// İYİ — tek nesne üzerinde çalışır
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(", ");
}
String sonuc = sb.toString();StringBuilder mutable (değiştirilebilir) bir karakter dizisi tutar. append() çağırdığında mevcut diziyi genişletir, yeni nesne oluşturmaz.
StringBuilder Metodları
StringBuilder sb = new StringBuilder("Merhaba");
sb.append(" Dünya"); // Sonuna ekle
sb.insert(7, " Güzel"); // Belirli indekse ekle
sb.replace(0, 7, "Selam"); // Aralığı değiştir
sb.delete(5, 11); // Aralığı sil
sb.reverse(); // Ters çevir
System.out.println(sb.toString());// Zincirleme (method chaining) kullanımı
String sonuc = new StringBuilder()
.append("Ad: ")
.append("Ali")
.append(", Yaş: ")
.append(25)
.toString();
System.out.println(sonuc); // "Ad: Ali, Yaş: 25"StringBuilder vs StringBuffer
İkisi neredeyse aynı. Tek fark:
StringBuilder: Thread-safe değil, daha hızlı. Bunu kullan.
StringBuffer: Thread-safe (synchronized), daha yavaş.
Çoğu durumda StringBuilder yeterli. Çoklu thread ortamında çalışıyorsan ve aynı buffer'a farklı thread'ler yazıyorsa StringBuffer kullan — ama bu çok nadir bir senaryo.
String Karşılaştırma — Özet Tablo
| Yöntem | Ne yapar | Ne zaman kullan |
|---|---|---|
== | Referans karşılaştırma | Kullanma (String için) |
equals() | İçerik karşılaştırma | Her zaman |
equalsIgnoreCase() | Büyük/küçük harf yok sayarak | Kullanıcı girdisi |
compareTo() | Alfabetik sıralama | Sıralama işlemleri |
String a = "elma";
String b = "armut";
int sonuc = a.compareTo(b);
// sonuc > 0 → a, b'den sonra gelir (alfabetik)
// sonuc < 0 → a, b'den önce gelir
// sonuc == 0 → eşitler
System.out.println("elma".compareTo("armut")); // pozitif (e > a)Null ve Empty Kontrolü
String s = null;
String bos = "";
String bosluk = " ";
// Null kontrolü
if (s != null && !s.isEmpty()) {
// güvenli kullanım
}
// Java 11+ ile isBlank()
System.out.println("".isEmpty()); // true
System.out.println("".isBlank()); // true
System.out.println(" ".isEmpty()); // false
System.out.println(" ".isBlank()); // true (sadece boşluk)isEmpty() uzunluğun 0 olup olmadığını kontrol eder. isBlank() (Java 11+) sadece boşluk karakterleri içerip içermediğini kontrol eder.
💡 Pratik öneri:
nullkontrolünü her zaman önce yap.s.isEmpty()çağrısısnull iseNullPointerExceptionfırlatır.
Pratik Örnek: Kullanıcı Girdisi Temizleme
public class GirdiTemizleyici {
public static String temizle(String girdi) {
if (girdi == null) {
return "";
}
return girdi
.strip() // Baş/son boşluk
.replaceAll("\\s+", " ") // Çoklu boşluk → tek boşluk
.trim();
}
public static void main(String[] args) {
String kirli = " Merhaba Dünya ";
String temiz = temizle(kirli);
System.out.println("[" + temiz + "]"); // [Merhaba Dünya]
}
}Java 13+: Text Blocks (Çok Satırlı String)
// Eski yol
String json = "{\n" +
" \"ad\": \"Ali\",\n" +
" \"yas\": 25\n" +
"}";
// Text block (Java 13+)
String jsonYeni = """
{
"ad": "Ali",
"yas": 25
}
""";
System.out.println(jsonYeni);Text block'lar üç çift tırnakla (""") başlar ve biter. Escape karakterlerine gerek kalmaz, çok satırlı metinleri çok daha okunabilir yapar.
String.join() — Birleştirme
split() ile böldüysen, join() ile birleştirirsin:
String[] kelimeler = {"Java", "çok", "güzel"};
String cumle = String.join(" ", kelimeler);
System.out.println(cumle); // "Java çok güzel"
// Virgülle birleştirme
String csv = String.join(",", "Ali", "Veli", "Ayşe");
System.out.println(csv); // "Ali,Veli,Ayşe"
// List ile de çalışır
List<String> isimler = List.of("Java", "Python", "Go");
String diller = String.join(", ", isimler);
System.out.println(diller); // "Java, Python, Go"String repeat() ve diğer Java 11+ Metotları
Java 11 ve sonrası String'e güzel metotlar ekledi:
// repeat() — Java 11+
String cizgi = "-".repeat(30);
System.out.println(cizgi); // "------------------------------"
String tab = " ".repeat(3);
System.out.println(tab + "Girintili metin");
// isBlank() — Java 11+
System.out.println("".isBlank()); // true
System.out.println(" ".isBlank()); // true
System.out.println(" a ".isBlank()); // false
// lines() — Java 11+ (satırlara böler)
String metin = "Satır 1\nSatır 2\nSatır 3";
metin.lines().forEach(System.out::println);
// indent() — Java 12+
String kod = "if (true) {\n ok();\n}";
System.out.println(kod.indent(4)); // Her satıra 4 boşluk eklerString Performans Karşılaştırması
Döngüde string birleştirmenin ne kadar yavaş olduğunu somut olarak görelim:
public class StringPerformans {
public static void main(String[] args) {
int tekrar = 100_000;
// Yöntem 1: + operatörü (KÖTÜ)
long baslangic1 = System.currentTimeMillis();
String s1 = "";
for (int i = 0; i < tekrar; i++) {
s1 += "a";
}
long sure1 = System.currentTimeMillis() - baslangic1;
// Yöntem 2: StringBuilder (İYİ)
long baslangic2 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tekrar; i++) {
sb.append("a");
}
String s2 = sb.toString();
long sure2 = System.currentTimeMillis() - baslangic2;
System.out.println("+ operatörü: " + sure1 + " ms");
System.out.println("StringBuilder: " + sure2 + " ms");
// Tipik sonuç: ~3000ms vs ~2ms — 1000x fark!
}
}💡 Kural: 3-4 string birleştirme?
+kullan, sorun yok. Döngüde veya çok sayıda birleştirme? StringBuilder kullan.
Modern Java derleyicisi tek satırdaki + zincirlerini otomatik olarak optimize eder. Ama döngüdeki birleştirmeleri optimize edemez — orada sen StringBuilder kullanmalısın.
intern() Metodu
intern() bir String'i String Pool'a ekler (veya pool'daki mevcut kopyasını döner):
String s1 = new String("test"); // Heap'te yeni nesne
String s2 = s1.intern(); // Pool'daki "test"i döner
String s3 = "test"; // Pool'daki aynı "test"
System.out.println(s1 == s3); // false (s1 heap'te)
System.out.println(s2 == s3); // true (ikisi de pool'da)intern() genellikle çok fazla tekrarlanan string'lerde bellek tasarrufu için kullanılır (örneğin XML parser'lar). Günlük kodda nadiren ihtiyaç duyarsın.
Pratik Örnek: Metin İstatistikleri
public class MetinIstatistik {
public static void main(String[] args) {
String metin = "Java programlama dili 1995 yılında Sun Microsystems "
+ "tarafından geliştirildi. Java platform bağımsız bir dildir.";
// Kelime sayısı
String[] kelimeler = metin.split("\\s+");
System.out.println("Kelime sayısı: " + kelimeler.length);
// Karakter sayısı (boşluk hariç)
long karakterSayisi = metin.chars()
.filter(c -> c != ' ')
.count();
System.out.println("Karakter (boşluksuz): " + karakterSayisi);
// Cümle sayısı
String[] cumleler = metin.split("[.!?]+");
System.out.println("Cümle sayısı: " + cumleler.length);
// En uzun kelime
String enUzun = "";
for (String kelime : kelimeler) {
if (kelime.length() > enUzun.length()) {
enUzun = kelime;
}
}
System.out.println("En uzun kelime: " + enUzun);
// Belirli kelime kaç kez geçiyor
String aranan = "Java";
int index = 0;
int sayac = 0;
while ((index = metin.indexOf(aranan, index)) != -1) {
sayac++;
index += aranan.length();
}
System.out.println("'" + aranan + "' kaç kez: " + sayac);
}
}Pratik Örnek: E-posta Doğrulama (Basit)
public class EmailDogrulama {
public static boolean gecerliMi(String email) {
if (email == null || email.isBlank()) {
return false;
}
email = email.strip();
// @ işareti var mı?
int atIndex = email.indexOf('@');
if (atIndex <= 0) return false; // @ yok veya başta
// @ sonrasında . var mı?
int dotIndex = email.lastIndexOf('.');
if (dotIndex <= atIndex + 1) return false;
// Sonda . olmamalı
if (dotIndex == email.length() - 1) return false;
// Birden fazla @ olmamalı
if (email.indexOf('@') != email.lastIndexOf('@')) return false;
return true;
}
public static void main(String[] args) {
System.out.println(gecerliMi("ali@example.com")); // true
System.out.println(gecerliMi("ali@example")); // false
System.out.println(gecerliMi("@example.com")); // false
System.out.println(gecerliMi("ali@@example.com")); // false
System.out.println(gecerliMi(null)); // false
}
}Sık Yapılan Hatalar
1. == ile karşılaştırma:
String s1 = new String("test");
String s2 = new String("test");
if (s1 == s2) { } // false — BUG!
if (s1.equals(s2)) { } // true — doğru2. Null üzerinde metot çağırma:
String s = null;
s.length(); // NullPointerException!
// Güvenli yol
if (s != null && s.length() > 0) { ... }
// Veya
"sabit".equals(s); // s null olsa bile hata vermez3. Döngüde + ile birleştirme:
// Yavaş — StringBuilder kullan
String s = "";
for (int i = 0; i < 1000; i++) {
s += i; // Her adımda yeni String nesnesi!
}4. split() ile regex tuzağı:
"dosya.txt".split("."); // Boş dizi! (. regex'te her karakter)
"dosya.txt".split("\\."); // ["dosya", "txt"] — doğru
"a|b|c".split("|"); // Her karakter ayrı! (| regex'te OR)
"a|b|c".split("\\|"); // ["a", "b", "c"] — doğru5. String'in immutable olduğunu unutmak:
String s = " merhaba ";
s.trim(); // Orijinal s DEĞİŞMEZ!
System.out.println("[" + s + "]"); // [ merhaba ]
s = s.trim(); // Sonucu geri ata!
System.out.println("[" + s + "]"); // [merhaba]Mülakat Soruları
S: String neden immutable? C: Güvenlik (String Pool paylaşımı), thread safety, hashCode caching. Değiştirilebilseydi pool'daki bir string'i değiştirmek onu kullanan herkesi etkilerdi.
S: String Pool nedir? C: JVM'in string literal'leri sakladığı özel bellek alanı. Aynı içerikli literal'ler aynı nesneyi paylaşır, bellek tasarrufu sağlar.
S: StringBuilder vs StringBuffer farkı? C: StringBuilder thread-safe değil (daha hızlı), StringBuffer thread-safe (synchronized, daha yavaş). Çoğu durumda StringBuilder yeterli.
S: String s = "abc" ile String s = new String("abc") farkı? C: İlki pool'dan alır (varsa mevcut nesneyi kullanır). İkincisi her zaman heap'te yeni nesne oluşturur.
Özet
String immutable'dır — üzerindeki her işlem yeni bir nesne döner, orijinali değiştirmez
String karşılaştırmasında `equals()` kullan,
==kullanmaString Pool sayesinde aynı literal'ler bellekte tek nesne olarak tutulur
Döngüde string birleştirme gerekiyorsa `StringBuilder` kullan
trim()/strip(),split(),substring(),format()en sık kullanılan metotlardırNull kontrolünü her zaman
equals()'tan önce yap veya literal'i sola koy:"sabit".equals(degisken)
AI Asistan
Sorularını yanıtlamaya hazır