Pendalaman String

Memahami imutabilitas, string pool, metode penting String, dan alasan == gagal membandingkan isi.

Daftar isi

String sudah dipakai sejak contoh pertama, tetapi mekanisme di baliknya baru terlihat ketika teks dibandingkan, disambung, dan diolah dalam jumlah besar. Topik ini mendalami tiga hal itu, yaitu sifat imutabel (immutable) yang membuat objek String tak pernah berubah, string pool yang berbagi objek literal dengan isi sama, serta alasan operator == gagal membandingkan isi teks. Karena itu, pembandingan isi teks wajib lewat metode equals. Rangkaian metode penting ditutup dengan contoh yang bisa dijalankan.

String itu imutabel#

Objek String bersifat imutabel, artinya isi teksnya tidak dapat diubah setelah objek itu dibuat. Setiap metode yang terkesan memodifikasi teks, seperti toUpperCase, replace, atau concat, sebenarnya membangun objek String baru dan mengembalikannya, sementara objek lama tetap utuh di tempatnya.

java BuktiImutabel.java
public class BuktiImutabel {
    public static void main(String[] args) {
        String kata = "java";
        String hasil = kata.toUpperCase();

        System.out.println("hasil panggilan : " + hasil);
        System.out.println("kata setelahnya : " + kata);

        kata = kata + " script";
        System.out.println("kata yang baru  : " + kata);
    }
}
text Keluaran
hasil panggilan : JAVA
kata setelahnya : java
kata yang baru  : java script

Pemanggilan kata.toUpperCase() menghasilkan objek baru berisi JAVA, sedangkan kata masih menunjuk objek lama berisi java. Baris kata = kata + " script" pun tidak mengubah objek lama, melainkan membuat objek ketiga lalu memindahkan tanda panah variabel kata ke objek baru itu. Desain imutabel ini bukan kebetulan, karena sifatnya membuat teks aman dibagikan ke banyak bagian program sekaligus, layak dijadikan kunci pada struktur data seperti HashMap, dan bebas dari perubahan mendadak oleh thread lain.

String pool dan objek yang berbagi#

String pool adalah area khusus di memori yang dikelola JVM untuk menyimpan objek String literal secara unik. Ketika dua literal ditulis dengan isi yang sama, keduanya merujuk satu objek yang sama di pool, sehingga memori tidak terbuang untuk teks yang berulang. Pembuatan objek dengan new String("...") memaksa objek baru di heap, meskipun isinya persis sama dengan literal yang sudah ada di pool.

Literal sama berbagi satu objek di string pool, new String membuat objek lainvariabelabc = new Stringstring poolobjek String"dwi"heapobjek String"dwi"
Dua literal berbagi satu objek di string pool, sedangkan new String membuat objek terpisah di heap

Perilaku ini menjelaskan hasil program pada bagian berikutnya, yaitu a dan b yang ditulis sebagai literal sama berakhir menunjuk objek yang sama, sedangkan c yang dibangun dengan new berdiri sendiri di heap. JVM juga menyediakan metode intern() yang menarik isi string apa pun kembali ke pool, meskipun pemakaiannya jarang diperlukan pada program biasa.

== gagal, equals benar#

Operator == membandingkan isi dua variabel primitif secara langsung, tetapi pada tipe referensi ia membandingkan alamat objeknya, bukan isinya. Dua objek String dengan teks identik bisa saja berada di alamat berbeda, dan di situ == melaporkan false padahal teksnya sama. Metode equals membandingkan isi karakter demi karakter, sehingga inilah alat yang benar untuk pembandingan teks.

java UjiStringPool.java
public class UjiStringPool {
    public static void main(String[] args) {
        String a = "dwi";
        String b = "dwi";
        String c = new String("dwi");

        System.out.println("a == b      : " + (a == b));
        System.out.println("a == c      : " + (a == c));
        System.out.println("a.equals(b) : " + a.equals(b));
        System.out.println("a.equals(c) : " + a.equals(c));
    }
}
text Keluaran
a == b      : true
a == c      : false
a.equals(b) : true
a.equals(c) : true

Baris pertama sering menipu pemula. a == b bernilai true bukan karena Java membandingkan isi, melainkan karena string pool membuat kedua literal itu berbagi satu objek. Begitu ada satu saja string yang dibangun saat program berjalan, jebakan ini terbongkar, sebagaimana dibuktikan program berikut.

java UjiPembandingTeks.java
public class UjiPembandingTeks {
    public static void main(String[] args) {
        String bagian1 = "ja";
        String bagian2 = "va";
        String tersusun = bagian1 + bagian2;

        System.out.println("isi tersusun             : " + tersusun);
        System.out.println("tersusun == \"java\"       : " + (tersusun == "java"));
        System.out.println("tersusun.equals(\"java\")  : " + tersusun.equals("java"));
    }
}
text Keluaran
isi tersusun             : java
tersusun == "java"       : false
tersusun.equals("java")  : true

Variabel tersusun berisi java, tetapi tersusun == "java" bernilai false karena penyambungan saat program berjalan membangun objek baru di heap, di luar pool. Isinya sama, alamatnya beda. Karena tidak ada jaminan tentang kapan teks berasal dari pool dan kapan tidak, kesimpulannya tegas, yaitu pembandingan isi teks selalu memakai equals dan == disisihkan untuk kasus primitif.

Metode penting String#

Kelas String menyediakan puluhan metode, dan sebagian kecilnya menutup hampir semua kebutuhan harian. Program berikut mempraktikkan beberapa di antaranya dalam satu rangkaian.

java DemoMetodeString.java
public class DemoMetodeString {
    public static void main(String[] args) {
        String kalimat = "  Latihan Java dimulai  ";
        String rapi = kalimat.strip();

        System.out.println("panjang awal : " + kalimat.length());
        System.out.println("panjang rapi : " + rapi.length());
        System.out.println("huruf besar  : " + rapi.toUpperCase());
        System.out.println("ganti kata   : " + rapi.replace("dimulai", "selesai"));
        System.out.println("kata kedua   : " + rapi.split(" ")[1]);
        System.out.println("berisi Java  : " + rapi.contains("Java"));
        System.out.println("awalan Latih : " + rapi.startsWith("Latih"));
        System.out.println("huruf ke-4   : " + rapi.charAt(3));
    }
}
text Keluaran
panjang awal : 24
panjang rapi : 20
huruf besar  : LATIHAN JAVA DIMULAI
ganti kata   : Latihan Java selesai
kata kedua   : Java
berisi Java  : true
awalan Latih : true
huruf ke-4   : i

Metode lain yang sering dipakai dirangkum pada tabel berikut, semuanya mengikuti pola yang sama, yaitu menerima argumen lalu mengembalikan hasil tanpa menyentuh string asalnya.

MetodeTugas
substring(awal, akhir)memotong teks dari indeks awal sampai sebelum akhir, batas akhirnya eksklusif
indexOf(teks)mencari posisi pertama teks, mengembalikan -1 bila tidak ditemukan
equalsIgnoreCase(teks)membandingkan isi tanpa memandang besar kecil hurufnya
isEmpty(), isBlank()menguji string kosong, atau kosong dan hanya berisi spasi
strip(), trim()membuang spasi di awal dan akhir
repeat(n)mengulang teks sebanyak n kali
toCharArray()memecah teks menjadi array char

Kesalahan umum#

  • Membandingkan isi teks dengan ==. Hasilnya betul pada literal dan salah pada string hasil perhitungan. Gunakan equals untuk isi dan simpan == untuk primitif.
  • Memanggil metode tanpa menampung hasilnya. Baris kata.toUpperCase(); berdiri sendiri tidak mengubah apa pun karena hasilnya objek baru yang langsung dibuang. Tulis kata = kata.toUpperCase(); bila variabelnya memang perlu diperbarui.
  • Salah memahami batas substring. Pemanggilan substring(0, 4) mengambil indeks 0 sampai 3, karena batas akhirnya tidak ikut. Satu karakter terpotong atau kelebihan satu karakter adalah kesalahan yang paling sering muncul di sini.
  • Memanggil metode pada variabel String yang bernilai null, yang melempar NullPointerException. Bila nilai null mungkin terjadi, bandingkan dari arah literal, misalnya "java".equals(nilai), sehingga pemanggilan aman meski nilai kosong.
  • Melupakan bahwa pola pada split berupa regular expression. Pemanggilan split(".") memecah menjadi array kosong karena titik berarti karakter apa pun, sehingga titik literal harus ditulis split("\\.").

Ringkasan#

  • Objek String imutabel, sehingga setiap metode olah teks menghasilkan objek baru dan objek lama tetap utuh.
  • String pool menyimpan literal secara unik, dua literal berisi sama berbagi satu objek, sedangkan new String membuat objek terpisah di heap.
  • == membandingkan alamat objek sehingga bisa false padahal isi sama, maka pembandingan isi teks selalu memakai equals.
  • Metode String seperti strip, replace, split, dan substring bekerja mengembalikan hasil baru, dan substring memiliki batas akhir yang eksklusif.

Referensi#