Takip et

Java’da String Programlama: Kapsamlı Bir Rehber

Java’da String Programlama: Kapsamlı Bir Rehber Java programlama dilinde String’ler, metin tabanlı verileri temsil etmek için kullanılan temel ve

Java’da String Programlama: Kapsamlı Bir Rehber

Java programlama dilinde String’ler, metin tabanlı verileri temsil etmek için kullanılan temel ve vazgeçilmez bir veri yapısıdır. Gündelik uygulamalardan karmaşık kurumsal sistemlere kadar her yerde karşımıza çıkan String’ler, kullanıcı arayüzü mesajlarından dosya içeriklerine, ağ protokollerinden veritabanı sorgularına kadar geniş bir kullanım alanına sahiptir. Bu makalede, Java’daki String kavramını derinlemesine inceleyecek, temel özelliklerinden gelişmiş manipülasyon tekniklerine, performans optimizasyonlarından sık karşılaşılan programlama senaryolarına kadar geniş bir yelpazeyi ele alacağız.

Java’da String Nedir? Temel Kavramlar ve Değişmezlik

Java’da String, java.lang.String sınıfının bir örneğidir ve karakter dizilerini temsil eder. En önemli özelliklerinden biri, değişmez (immutable) olmasıdır. Bu, bir String nesnesi oluşturulduktan sonra içeriğinin değiştirilemeyeceği anlamına gelir. Herhangi bir String üzerinde yapılan bir “değişiklik” işlemi (örneğin, birleştirme, alt dize alma), aslında orijinal String’i değiştirmek yerine, yeni bir String nesnesi oluşturur.

Bu değişmezlik özelliği, Java’da String’leri güvenli ve verimli kılar:
* Güvenlik: String’ler genellikle kullanıcı girişleri, parola veya dosya yolları gibi hassas verileri depolamak için kullanılır. Değişmezlik, bu verilerin bir kez atandıktan sonra beklenmedik bir şekilde değiştirilmesini engeller.
* Thread Güvenliği: Birden fazla thread aynı String nesnesine erişebilir ve onu okuyabilir; çünkü içeriği asla değişmeyecektir, bu da senkronizasyon gereksinimini ortadan kaldırır.
* Hash Kodları: Değişmezlik sayesinde String’lerin hash kodları bir kez hesaplanabilir ve önbelleğe alınabilir. Bu, HashMap ve HashSet gibi koleksiyonlarda String’lerin anahtar olarak verimli bir şekilde kullanılmasını sağlar.
* String Pool: Java, aynı String değişmezlerini (literallerini) bellekte tek bir kopyada tutarak bellek kullanımını optimize eder. Bu yapıya “String Pool” denir.

String Oluşturma ve Başlatma

Java’da String nesneleri oluşturmanın birkaç yolu vardır:

1. String Literalleri Kullanarak: Bu en yaygın ve önerilen yöntemdir.

String ad = "Ahmet";
    String soyad = "Yılmaz";

Bu durumda, Java Virtual Machine (JVM), String Pool’da “Ahmet” ve “Yılmaz” değerlerinin olup olmadığını kontrol eder. Eğer yoksa, yeni bir String nesnesi oluşturur ve onu havuza ekler. Eğer varsa, mevcut nesnenin referansını döndürür. Bu, bellek optimizasyonu sağlar.

2. new Anahtar Kelimesi Kullanarak:

String sehir1 = new String("Ankara");
    String sehir2 = new String("Ankara");

new anahtar kelimesi kullanıldığında, String Pool’da “Ankara” değeri olsa bile, heap bellekte her zaman yeni bir String nesnesi oluşturulur. sehir1 ve sehir2 farklı bellek adreslerine sahip iki ayrı nesneyi işaret ederler, ancak içerikleri aynıdır.

== operatörü String referanslarını karşılaştırırken, equals() metodu içerikleri karşılaştırır.

String s1 = "Merhaba";
String s2 = "Merhaba";
String s3 = new String("Merhaba");

System.out.println(s1 == s2);        // true (String Pool'dan aynı nesneyi işaret ederler)
System.out.println(s1 == s3);        // false (s1 String Pool'da, s3 heap'te yeni bir nesne)
System.out.println(s1.equals(s3));   // true (içerikleri aynı)

Temel String Metotları ve Kullanımları

java.lang.String sınıfı, String’ler üzerinde çeşitli işlemler yapmak için zengin bir metot seti sunar. İşte en sık kullanılanlar ve örnekleri:

String Uzunluğu ve Karakter Erişimi

* length(): String’deki karakter sayısını döndürür.

String metin = "Java Programlama";
    int uzunluk = metin.length(); // 16
    System.out.println("Metnin uzunluğu: " + uzunluk);

* charAt(int index): Belirtilen dizindeki karakteri döndürür. Dizeler 0’dan başlar.

char ilkKarakter = metin.charAt(0); // 'J'
    char sonKarakter = metin.charAt(metin.length() - 1); // 'a'
    System.out.println("İlk karakter: " + ilkKarakter);
    System.out.println("Son karakter: " + sonKarakter);

Alt Dize (Substring) İşlemleri

* substring(int beginIndex): Belirtilen dizinden başlayarak String’in sonuna kadar olan alt dizeyi döndürür.
* substring(int beginIndex, int endIndex): beginIndex‘ten başlayıp endIndex - 1‘e kadar olan alt dizeyi döndürür. endIndex dahil değildir.

String cumle = "Bu bir örnek cümledir.";
    String altDize1 = cumle.substring(7); // "örnek cümledir."
    String altDize2 = cumle.substring(7, 12); // "örnek"
    System.out.println("Alt dize 1: " + altDize1);
    System.out.println("Alt dize 2: " + altDize2);

String Karşılaştırma

* equals(Object anObject): İki String’in içeriklerinin aynı olup olmadığını kontrol eder. Büyük/küçük harf duyarlıdır.
* equalsIgnoreCase(String anotherString): İki String’in içeriklerinin büyük/küçük harf duyarlılığı olmadan aynı olup olmadığını kontrol eder.

String s1 = "Hello";
    String s2 = "hello";
    System.out.println(s1.equals(s2));          // false
    System.out.println(s1.equalsIgnoreCase(s2)); // true

* compareTo(String anotherString): İki String’i sözlük sırasına göre karşılaştırır.
* 0 döndürürse String’ler eşittir.
* Pozitif bir değer döndürürse, çağrılan String parametreden sonra gelir.
* Negatif bir değer döndürürse, çağrılan String parametreden önce gelir.
* compareToIgnoreCase(String anotherString): Büyük/küçük harf duyarlılığı olmadan sözlük sırasına göre karşılaştırır.

String a = "Apple";
    String b = "Banana";
    String c = "apple";
    System.out.println(a.compareTo(b));        // Negatif bir değer (A, B'den önce gelir)
    System.out.println(b.compareTo(a));        // Pozitif bir değer (B, A'dan sonra gelir)
    System.out.println(a.compareTo(c));        // Pozitif bir değer (ASCII 'A' < 'a')
    System.out.println(a.compareToIgnoreCase(c)); // 0 (büyük/küçük harf göz ardı edildiğinde eşit)

String Birleştirme

* concat(String str): Belirtilen String'i mevcut String'in sonuna ekler. Yeni bir String döndürür.
* + Operatörü: String birleştirme için daha yaygın ve okunabilir bir yöntemdir. Derleyici tarafından genellikle StringBuilder kullanılarak optimize edilir.

String ad = "Ayşe";
    String soyad = "Can";
    String tamAd = ad.concat(" ").concat(soyad); // "Ayşe Can"
    String tamAd2 = ad + " " + soyad;             // "Ayşe Can"
    System.out.println(tamAd);
    System.out.println(tamAd2);

String İçerik Kontrolü

* contains(CharSequence s): String'in belirtilen karakter dizisini içerip içermediğini kontrol eder.

String metin = "Java programlama çok eğlencelidir.";
    System.out.println(metin.contains("Java"));      // true
    System.out.println(metin.contains("Python"));    // false

* startsWith(String prefix): String'in belirtilen önekle başlayıp başlamadığını kontrol eder.
* endsWith(String suffix): String'in belirtilen sonekle bitip bitmediğini kontrol eder.

String dosyaAdi = "belge.pdf";
    System.out.println(dosyaAdi.startsWith("belge")); // true
    System.out.println(dosyaAdi.endsWith(".pdf"));    // true

* isEmpty(): String'in boş olup olmadığını kontrol eder (uzunluk 0 ise true).
* isBlank() (Java 11+): String'in boş olup olmadığını veya sadece boşluk karakterlerinden oluşup oluşmadığını kontrol eder.

String bosStr = "";
    String boslukStr = "   ";
    System.out.println(bosStr.isEmpty());     // true
    System.out.println(boslukStr.isEmpty());  // false
    System.out.println(bosStr.isBlank());     // true
    System.out.println(boslukStr.isBlank());  // true

Karakter Dizisi Konumu

* indexOf(int ch) / indexOf(String str): Belirtilen karakterin veya alt dizenin ilk geçtiği dizini döndürür. Bulamazsa -1 döndürür.
* indexOf(int ch, int fromIndex) / indexOf(String str, int fromIndex): Belirtilen dizinden başlayarak arama yapar.
* lastIndexOf(int ch) / lastIndexOf(String str): Belirtilen karakterin veya alt dizenin son geçtiği dizini döndürür.
* lastIndexOf(int ch, int fromIndex) / lastIndexOf(String str, int fromIndex): Belirtilen dizinden geriye doğru arama yapar.

String ornek = "Merhaba dünya, merhaba Java.";
    System.out.println(ornek.indexOf('a'));        // 3
    System.out.println(ornek.lastIndexOf('a'));     // 23
    System.out.println(ornek.indexOf("merhaba"));   // 0
    System.out.println(ornek.indexOf("merhaba", 1)); // 13 (ikinci merhaba)

String Değiştirme ve Düzenleme

* replace(char oldChar, char newChar): Belirtilen tüm eski karakterleri yeni karakterle değiştirir.
* replace(CharSequence target, CharSequence replacement): Belirtilen tüm hedef karakter dizilerini yenisiyle değiştirir.
* replaceAll(String regex, String replacement): Düzenli ifadeye uyan tüm alt dizeleri değiştirir.
* replaceFirst(String regex, String replacement): Düzenli ifadeye uyan ilk alt dizeyi değiştirir.

String metin = "Merhaba dünya.";
    String yeniMetin1 = metin.replace('a', 'e');       // "Merhebe dünye."
    String yeniMetin2 = metin.replace("dünya", "evren"); // "Merhaba evren."
    String sayilar = "123abc456def";
    String sadeceHarfler = sayilar.replaceAll("[0-9]", ""); // "abcdef"
    System.out.println(yeniMetin1);
    System.out.println(yeniMetin2);
    System.out.println(sadeceHarfler);

* trim(): String'in başındaki ve sonundaki boşluk karakterlerini kaldırır.
* strip() (Java 11+): Unicode boşluk karakterlerini de kaldırır. trim()'den daha kapsamlıdır.
* stripLeading() (Java 11+): Sadece baştaki boşlukları kaldırır.
* stripTrailing() (Java 11+): Sadece sondaki boşlukları kaldırır.

String bosluklu = "   Merhaba Dünya   ";
    System.out.println("'" + bosluklu.trim() + "'");       // 'Merhaba Dünya'
    System.out.println("'" + bosluklu.strip() + "'");      // 'Merhaba Dünya'
    System.out.println("'" + bosluklu.stripLeading() + "'"); // 'Merhaba Dünya   '
    System.out.println("'" + bosluklu.stripTrailing() + "'"); // '   Merhaba Dünya'

* toLowerCase(): String'deki tüm karakterleri küçük harfe dönüştürür.
* toUpperCase(): String'deki tüm karakterleri büyük harfe dönüştürür.

String mesaj = "Java Öğreniyorum";
    System.out.println(mesaj.toLowerCase()); // "java öğreniyorum"
    System.out.println(mesaj.toUpperCase()); // "JAVA ÖĞRENİYORUM"

String Bölme (Splitting)

* split(String regex): String'i belirtilen düzenli ifadeye göre böler ve bir String dizisi döndürür.
* split(String regex, int limit): Bölme sayısını sınırlayabilir.

String veri = "elma,armut,kiraz,üzüm";
    String[] meyveler = veri.split(",");
    for (String meyve : meyveler) {
        System.out.println(meyve);
    }
    // Çıktı: elma, armut, kiraz, üzüm

Diğer Yararlı Metotlar

* valueOf(primitive data type): Çeşitli ilkel veri tiplerini (int, double, boolean vb.) String'e dönüştürür.

int sayi = 123;
    String strSayi = String.valueOf(sayi); // "123"
    System.out.println(strSayi + 1); // "1231" (String birleştirme)

* toCharArray(): String'i bir karakter dizisine (char[]) dönüştürür.

String kelime = "kod";
    char[] harfler = kelime.toCharArray(); // ['k', 'o', 'd']
    for (char c : harfler) {
        System.out.print(c + " ");
    } // k o d
    System.out.println();

* format(String format, Object... args): Belirtilen format dizesini kullanarak formatlanmış bir String döndürür. C'deki printf'e benzer.

String isim = "Ayşe";
    int yas = 30;
    String formattedString = String.format("Merhaba, benim adım %s ve %d yaşındayım.", isim, yas);
    System.out.println(formattedString); // Merhaba, benim adım Ayşe ve 30 yaşındayım.

String, StringBuilder ve StringBuffer: Ne Zaman Hangisi?

Java'da String manipülasyonu için sadece String sınıfı değil, aynı zamanda StringBuilder ve StringBuffer sınıfları da mevcuttur. Bu üçü arasındaki temel farklar ve kullanım senaryoları şunlardır:

* String:
* Değişmez (Immutable): İçeriği değiştirilemez. Her manipülasyon yeni bir String nesnesi oluşturur.
* Thread-Safe: Evet, değişmez olduğu için.
* Performans: Çok sayıda birleştirme veya manipülasyon işlemi gerektiğinde performans düşüklüğüne neden olabilir, çünkü her adımda yeni bir nesne oluşturulur ve eski nesneler çöp toplayıcıya kalır.
* Kullanım Alanı: Metin içeriğinin nadiren veya hiç değişmediği durumlar, String pool avantajından faydalanmak istendiğinde.

* StringBuilder:
* Değişebilir (Mutable): İçeriği değiştirilebilir. Aynı nesne üzerinde manipülasyonlar yapılabilir.
* Thread-Safe: Hayır, senkronize değildir. Birden fazla thread aynı anda erişirse veri tutarsızlığına yol açabilir.
* Performans: Tek thread'li ortamlarda veya senkronizasyonun manuel olarak yönetildiği durumlarda String birleştirme ve manipülasyonu için en hızlı ve en verimli yöntemdir.
* Kullanım Alanı: Tek thread'li ortamlarda çok sayıda String birleştirme veya yoğun metin manipülasyonu gerektiğinde (örneğin, döngü içinde String oluşturma).

* StringBuffer:
* Değişebilir (Mutable): İçeriği değiştirilebilir.
* Thread-Safe: Evet, tüm metotları synchronized anahtar kelimesi ile işaretlenmiştir. Bu, birden fazla thread'in aynı anda eriştiğinde veri tutarlılığını garanti eder.
* Performans: StringBuilder'dan daha yavaştır çünkü senkronizasyon ek yükü vardır.
* Kullanım Alanı: Çok thread'li ortamlarda güvenli String manipülasyonu gerektiğinde.

Özetle:
* String: Eğer String içeriği hiç değişmeyecekse veya çok az değişecekse.
* StringBuilder: Tek thread'li bir ortamda çok sayıda String manipülasyonu yapılacaksa (performans öncelikliyse).
* StringBuffer: Çok thread'li bir ortamda String manipülasyonu yapılacaksa (thread güvenliği öncelikliyse).

Örnek:

// String ile performans sorunu
String result = "";
for (int i = 0; i < 1000; i++) {
    result += i + " "; // Her döngüde yeni bir String nesnesi oluşturulur
}
System.out.println(result.length());

// StringBuilder ile daha iyi performans
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i).append(" "); // Aynı StringBuilder nesnesi üzerinde işlem yapılır
}
System.out.println(sb.toString().length());

// StringBuffer ile thread-safe
StringBuffer sbf = new StringBuffer();
// Çok thread'li bir senaryoda kullanılabilir

Sık Karşılaşılan String Programlama Senaryoları

İşte String manipülasyonu ile çözülebilecek bazı yaygın programlama problemleri:

1. String'i Ters Çevirme

public String reverseString(String str) {
    // Yöntem 1: StringBuilder kullanma (en verimli)
    return new StringBuilder(str).reverse().toString();

    // Yöntem 2: Manuel döngü
    // char[] charArray = str.toCharArray();
    // int start = 0;
    // int end = charArray.length - 1;
    // while (start < end) {
    //     char temp = charArray[start];
    //     charArray[start] = charArray[end];
    //     charArray[end] = temp;
    //     start++;
    //     end--;
    // }
    // return new String(charArray);
}

2. Palindrom Kontrolü

Bir String'in tersten okunuşunun da aynı olup olmadığını kontrol eder (örneğin, "madam", "kayak").

public boolean isPalindrome(String str) {
    String cleanedStr = str.toLowerCase().replaceAll("[^a-z0-9]", ""); // Harf ve rakam dışındakileri temizle
    String reversedStr = new StringBuilder(cleanedStr).reverse().toString();
    return cleanedStr.equals(reversedStr);

    // Yöntem 2: İki işaretçi ile
    // int left = 0;
    // int right = cleanedStr.length() - 1;
    // while (left < right) {
    //     if (cleanedStr.charAt(left) != cleanedStr.charAt(right)) {
    //         return false;
    //     }
    //     left++;
    //     right--;
    // }
    // return true;
}

3. Bir Karakterin Kaç Kez Geçtiğini Bulma

public int countOccurrences(String str, char ch) {
    int count = 0;
    for (char c : str.toCharArray()) {
        if (c == ch) {
            count++;
        }
    }
    return count;
    // Yöntem 2: Java 8 Stream API
    // return (int) str.chars().filter(c -> c == ch).count();
}

4. Yinelenen Karakterleri Kaldırma

public String removeDuplicates(String str) {
    StringBuilder sb = new StringBuilder();
    boolean[] seen = new boolean[256]; // ASCII karakterler için
    for (char c : str.toCharArray()) {
        if (!seen[c]) {
            sb.append(c);
            seen[c] = true;
        }
    }
    return sb.toString();
}

5. Anagram Kontrolü

İki String'in aynı karakterlerden, farklı sıralamalarla oluşup oluşmadığını kontrol eder (örneğin, "listen" ve "silent").

public boolean areAnagrams(String str1, String str2) {
    if (str1.length() != str2.length()) {
        return false;
    }
    char[] charArray1 = str1.toCharArray();
    char[] charArray2 = str2.toCharArray();

    java.util.Arrays.sort(charArray1);
    java.util.Arrays.sort(charArray2);

    return java.util.Arrays.equals(charArray1, charArray2);
}

Düzenli İfadeler (Regular Expressions) ile String İşlemleri

Java, java.util.regex paketi aracılığıyla güçlü düzenli ifade desteği sunar. String sınıfının matches(), replaceAll(), replaceFirst() ve split() metotları da düzenli ifadeleri kullanır. Daha karmaşık senaryolar için Pattern ve Matcher sınıfları kullanılır.

* Pattern: Bir düzenli ifadeyi derlenmiş bir biçimde temsil eder.
* Matcher: Bir Pattern nesnesini bir giriş String'i üzerinde eşleştirmek için kullanılır.

Örnek: E-posta adreslerini doğrulamak veya metinden belirli kalıpları çıkarmak.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample {
    public static void main(String[] args) {
        String metin = "Kişisel bilgiler: Adım Ali, yaşım 30. E-posta: ali.veli@example.com ve telefon: 555-123-4567.";

        // E-posta adresini bulma
        Pattern emailPattern = Pattern.compile("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b");
        Matcher emailMatcher = emailPattern.matcher(metin);
        if (emailMatcher.find()) {
            System.out.println("Bulunan e-posta: " + emailMatcher.group());
        }

        // Metindeki tüm sayıları bulma
        Pattern digitPattern = Pattern.compile("\\d+"); // Bir veya daha fazla rakam
        Matcher digitMatcher = digitPattern.matcher(metin);
        System.out.print("Bulunan sayılar: ");
        while (digitMatcher.find()) {
            System.out.print(digitMatcher.group() + " ");
        }
        System.out.println();

        // String.matches() ile tüm String'in kalıba uyup uymadığını kontrol etme
        String telefonNumarasi = "555-123-4567";
        boolean isValidPhone = telefonNumarasi.matches("\\d{3}-\\d{3}-\\d{4}");
        System.out.println("Telefon numarası geçerli mi? " + isValidPhone); // true
    }
}

Performans Optimizasyonları ve En İyi Uygulamalar

String manipülasyonları, özellikle büyük ölçekli uygulamalarda veya yoğun döngülerde performans darboğazlarına yol açabilir. İşte bazı en iyi uygulamalar:

1. Gereksiz String Nesnesi Oluşturmaktan Kaçının:
* Döngü içinde + operatörü ile String birleştirmek yerine StringBuilder veya StringBuffer kullanın.
* new String("literal") yerine String literalleri kullanın ("literal").
* String.valueOf() veya Integer.toString() gibi metotları kullanarak ilkel tipleri String'e dönüştürün.

2. StringBuilder veya StringBuffer için Başlangıç Kapasitesi Belirleyin:
Eğer son String'in yaklaşık boyutunu biliyorsanız, StringBuilder sb = new StringBuilder(tahminiBoyut); şeklinde bir başlangıç kapasitesi belirlemek, yeniden boyutlandırma işlemlerinden kaynaklanan performans maliyetini azaltır.

3. equals() ve equalsIgnoreCase() Kullanın:
String içeriklerini karşılaştırmak için her zaman == yerine equals() veya equalsIgnoreCase() kullanın. == sadece referans eşitliğini kontrol eder.

4. String.intern() Dikkatli Kullanın:
intern() metodu, bir String'i String Pool'a ekler veya havuzdaki mevcut bir String'in referansını döndürür. Nadiren ve özel durumlarda bellek kullanımını optimize etmek için kullanılabilir, ancak genellikle performansa olumsuz etkisi olabilir.

5. Düzenli İfadeleri Önceden Derleyin:
Eğer aynı düzenli ifadeyi birden fazla kez kullanacaksanız, Pattern.compile() ile bir Pattern nesnesini önceden derlemek ve sonra bu nesneyi Matcher oluşturmak için kullanmak daha verimlidir.

6. isEmpty() ve isBlank() Kullanımı:
String'in boş olup olmadığını kontrol etmek için str.length() == 0 yerine str.isEmpty() kullanın. Java 11 ve sonrası için sadece boşluklardan oluşan String'leri kontrol etmek için str.isBlank() tercih edin.

Sonuç

Java'da String'ler, metin tabanlı verilerle çalışmak için güçlü ve esnek bir temel sunar. String sınıfının değişmez doğası, güvenlik ve thread güvenliği sağlarken, StringBuilder ve StringBuffer sınıfları yoğun String manipülasyonu gerektiren senaryolarda performans ve thread güvenliği seçenekleri sunar. Sağlanan zengin metot kütüphanesi ve düzenli ifade desteği ile String'ler üzerinde hemen hemen her türlü işlemi gerçekleştirmek mümkündür. Bu makalede ele alınan kavramlar ve örnekler, Java geliştiricilerinin String programlamada karşılaşabilecekleri çeşitli zorlukların üstesinden gelmelerine ve daha verimli, okunabilir ve güvenli kod yazmalarına yardımcı olacaktır. Doğru aracı doğru senaryoda kullanmak, Java uygulamalarının genel performansı ve sürdürülebilirliği açısından kritik öneme sahiptir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.