Strings #
String adalah tipe data yang paling sering digunakan di hampir semua program Java, namun juga yang paling sering disalahpahami. Tidak seperti tipe primitif, String di Java adalah objek — dan bukan sembarang objek. String bersifat immutable: sekali dibuat, nilainya tidak bisa diubah. Setiap operasi yang terlihat “mengubah” String sebenarnya membuat objek String baru dan membuang yang lama. Memahami sifat ini — beserta konsep String pool, perbedaan antara == dan equals(), dan kapan harus menggunakan StringBuilder — adalah fondasi untuk menulis kode Java yang benar dan efisien. Di artikel ini kita akan menjelajahi seluruh API String dari standar library Java, mulai dari operasi dasar hingga formatting, parsing, regular expression, dan fitur modern seperti Text Block yang diperkenalkan di Java 15.
Immutability dan String Pool #
Sebelum menyentuh API, pahami dua properti fundamental String yang menentukan semua perilakunya.
Immutability #
Ketika kamu menulis str = str + " dunia", Java tidak mengubah String yang ada. Ia membuat String baru dengan nilai gabungan, mengarahkan variabel str ke objek baru, dan meninggalkan String lama untuk di-garbage collect.
String a = "halo";
String b = a; // b dan a menunjuk ke objek yang SAMA
a = a + " dunia"; // a sekarang menunjuk ke objek BARU "halo dunia"
// b masih menunjuk ke "halo" — tidak ikut berubah
System.out.println(a); // "halo dunia"
System.out.println(b); // "halo" — tidak terpengaruh
// Imutabilitas menjadikan String thread-safe secara otomatis
// Tidak ada race condition karena tidak ada shared mutable state
String Pool #
Java menyimpan literal String di area memori khusus bernama String pool (bagian dari metaspace sejak Java 8). Dua literal yang sama merujuk ke satu objek yang sama di pool.
String s1 = "halo"; // masuk ke String pool
String s2 = "halo"; // menggunakan objek yang SAMA dari pool
String s3 = new String("halo"); // membuat objek BARU di heap, di luar pool
// ✗ ANTI-PATTERN: membandingkan referensi dengan ==
System.out.println(s1 == s2); // true — kebetulan sama karena pool
System.out.println(s1 == s3); // false — objek berbeda di heap!
// ✓ BENAR: selalu gunakan equals() untuk membandingkan NILAI String
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true — nilai sama
// intern() — paksakan String masuk ke pool
String s4 = s3.intern();
System.out.println(s1 == s4); // true — s4 kini menunjuk ke objek pool
// ✗ ANTI-PATTERN: new String("literal") — selalu buang waktu dan memori
String buang = new String("halo"); // hindari ini
flowchart TD
subgraph POOL["String Pool - Metaspace"]
SP["halo"]
end
subgraph HEAP["Heap"]
HO[""halo" - objek baru"]
end
S1["s1"] --> SP
S2["s2"] --> SP
S3["s3"] --> HO
S4["s4 = s3.intern()"] --> SPMembuat String #
// Dari literal — cara paling umum, masuk ke String pool
String s1 = "Halo Dunia";
// Dari char array
char[] chars = {'H', 'a', 'l', 'o'};
String s2 = new String(chars);
String s3 = String.valueOf(chars); // ekuivalen, lebih idiomatis
// Dari byte array (penting: selalu tentukan charset!)
byte[] bytes = "Halo".getBytes(java.nio.charset.StandardCharsets.UTF_8);
String s4 = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
// ✗ ANTI-PATTERN: tidak tentukan charset — bergantung pada platform default
String buruk = new String(bytes); // charset bergantung OS!
// Menggabungkan beberapa String
String gabung = String.join(", ", "apel", "jeruk", "mangga");
// "apel, jeruk, mangga"
String gabungList = String.join(" | ", java.util.List.of("A", "B", "C"));
// "A | B | C"
// Mengulang String (Java 11+)
String ulang = "ha".repeat(3); // "hahaha"
// String kosong dan blank
String kosong = "";
String spasi = " ";
System.out.println(kosong.isEmpty()); // true — panjang 0
System.out.println(spasi.isEmpty()); // false — ada karakter spasi
System.out.println(spasi.isBlank()); // true (Java 11+) — hanya whitespace
Operasi Dasar #
Panjang dan Akses Karakter #
String teks = "Pemrograman Java";
// Panjang string
int panjang = teks.length(); // 16
// Akses karakter berdasarkan index (0-based)
char pertama = teks.charAt(0); // 'P'
char terakhir = teks.charAt(teks.length() - 1); // 'a'
// Konversi ke char array
char[] chars = teks.toCharArray();
// Konversi ke byte array
byte[] bytes = teks.getBytes(java.nio.charset.StandardCharsets.UTF_8);
// Codepoint — untuk karakter Unicode di luar Basic Multilingual Plane
// Emoji dan beberapa karakter Asia butuh dua char (surrogate pair)
String emoji = "Java ☕";
System.out.println(emoji.length()); // 7 (char count)
System.out.println(emoji.codePointCount(0, emoji.length())); // 6 (codepoint count)
Pencarian dan Pengecekan #
String kalimat = "Belajar Java sangat menyenangkan";
// indexOf — posisi pertama substring (-1 jika tidak ditemukan)
int posisi = kalimat.indexOf("Java"); // 8
int posisiChar = kalimat.indexOf('a'); // 2
int dariPosisi = kalimat.indexOf("a", 10); // cari mulai dari index 10
// lastIndexOf — posisi terakhir
int terakhir = kalimat.lastIndexOf("a"); // 30
// contains — apakah mengandung substring
boolean ada = kalimat.contains("Java"); // true
// startsWith / endsWith
boolean awalan = kalimat.startsWith("Belajar"); // true
boolean akhiran = kalimat.endsWith("kan"); // true
// matches — cek dengan regex
boolean angka = "12345".matches("\\d+"); // true
boolean email = "[email protected]".matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}"); // true
// regionMatches — bandingkan bagian dari dua String
String s1 = "Halo Dunia";
String s2 = "halo semua";
boolean cocok = s1.regionMatches(
true, // ignoreCase
0, // offset di s1
s2, // string lain
0, // offset di s2
4 // panjang yang dibandingkan
); // true — "Halo" == "halo" (case insensitive)
Transformasi #
String teks = " Halo Dunia ";
// Trim dan strip
String trimmed = teks.trim(); // "Halo Dunia" — hapus whitespace ASCII
String stripped = teks.strip(); // "Halo Dunia" (Java 11+) — hapus Unicode whitespace
String stripKiri = teks.stripLeading(); // "Halo Dunia "
String stripKanan = teks.stripTrailing(); // " Halo Dunia"
// Ubah case
String upper = "halo".toUpperCase(); // "HALO"
String lower = "HALO".toLowerCase(); // "halo"
// Gunakan Locale untuk bahasa tertentu
String upperLocale = "istanbul".toUpperCase(java.util.Locale.forLanguageTag("tr"));
// Substring
String sub1 = "Halo Dunia".substring(5); // "Dunia" — dari index 5 ke akhir
String sub2 = "Halo Dunia".substring(0, 4); // "Halo" — dari 0 sampai sebelum 4
// Replace — ganti karakter atau substring
String r1 = "aababc".replace('a', 'x'); // "xxbxbc" — ganti char
String r2 = "halo halo".replace("halo", "hai"); // "hai hai" — ganti semua substring
String r3 = "a1b2c3".replaceAll("[0-9]", "#"); // "a#b#c#" — ganti dengan regex
String r4 = "a1b2c3".replaceFirst("[0-9]", "#");// "a#b2c3" — ganti pertama saja
// Split — pecah String menjadi array
String[] kata = "apel,jeruk,mangga".split(",");
// ["apel", "jeruk", "mangga"]
String[] batas = "a b c".split("\\s+");
// ["a", "b", "c"] — split oleh satu atau lebih whitespace
// Split dengan batas maksimum bagian
String[] terbatas = "a:b:c:d".split(":", 2);
// ["a", "b:c:d"] — maksimum 2 bagian
// Concat — hindari untuk banyak operasi, gunakan StringBuilder
String gabung = "Halo".concat(" Dunia"); // "Halo Dunia"
// Komparasi
int cmp = "apel".compareTo("jeruk"); // negatif — "apel" < "jeruk" secara leksikografis
int cmpIgnore = "APEL".compareToIgnoreCase("apel"); // 0 — sama
boolean eq = "halo".equalsIgnoreCase("HALO"); // true
StringBuilder dan StringBuffer #
Karena String immutable, melakukan banyak penggabungan String dalam loop sangat tidak efisien — setiap + membuat objek String baru. Gunakan StringBuilder untuk operasi string yang intensif.
// ✗ ANTI-PATTERN: concatenation dalam loop — O(n²) operasi
String hasil = "";
for (int i = 0; i < 10000; i++) {
hasil += i + ","; // membuat 10.000 String baru yang langsung dibuang!
}
// ✓ BENAR: StringBuilder — O(n) operasi, jauh lebih efisien
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(",");
}
String hasilEfisien = sb.toString();
// Operasi StringBuilder
StringBuilder builder = new StringBuilder("Halo");
builder.append(" Dunia"); // "Halo Dunia"
builder.append('!'); // "Halo Dunia!"
builder.insert(4, ","); // "Halo, Dunia!" — sisipkan di index 4
builder.delete(4, 5); // "Halo Dunia!" — hapus dari 4 sampai sebelum 5
builder.deleteCharAt(10); // "Halo Dunia" — hapus karakter di index 10
builder.replace(5, 10, "Java"); // "Halo Java" — ganti range dengan string baru
builder.reverse(); // "avaJ olaH"
builder.setCharAt(0, 'J'); // ubah karakter di index 0
// Ukuran dan kapasitas
int panjang = builder.length(); // jumlah karakter yang ada
int kapasitas = builder.capacity(); // ukuran internal buffer (bisa lebih besar dari length)
// StringBuilder vs StringBuffer
// StringBuilder — tidak thread-safe, tapi lebih cepat (gunakan ini hampir selalu)
// StringBuffer — thread-safe (synchronized), gunakan hanya jika memang butuh concurrency
// Tip performa: beri kapasitas awal jika tahu perkiraan panjang
StringBuilder preAlloc = new StringBuilder(10000); // hindari resize internal buffer
for (int i = 0; i < 10000; i++) {
preAlloc.append(i).append(",");
}
String Formatting #
String.format() dan printf #
// String.format — buat string terformat
String nama = "Budi";
int umur = 30;
double gaji = 8500000.50;
String info = String.format("Nama: %s, Umur: %d tahun", nama, umur);
// "Nama: Budi, Umur: 30 tahun"
// Format angka
String desimal = String.format("%.2f", gaji); // "8500000,50"
String ilmiah = String.format("%e", gaji); // "8.500001e+06"
String hex = String.format("%x", 255); // "ff"
String octal = String.format("%o", 8); // "10"
// Lebar dan perataan
String lebar = String.format("|%10s|", "kanan"); // "| kanan|" — rata kanan
String kiri = String.format("|%-10s|", "kiri"); // "|kiri |" — rata kiri
String nol = String.format("|%05d|", 42); // "|00042|" — padding nol
// Karakter format yang umum:
// %s → String
// %d → integer (desimal)
// %f → float/double
// %.Nf → float dengan N desimal
// %e → notasi ilmiah
// %b → boolean
// %c → karakter
// %n → newline (portabel, berbeda di Windows/Unix)
// %x → hex lowercase, %X uppercase
// %t → tanggal/waktu (banyak sub-format)
// Format tanggal
java.time.LocalDateTime now = java.time.LocalDateTime.now();
String tanggal = String.format("%tF", now); // "2024-05-10"
String waktu = String.format("%tT", now); // "14:30:00"
String lengkap = String.format("%1$tF %1$tT", now); // gunakan argumen yang sama dua kali
// printf ke System.out
System.out.printf("Halo, %s! Kamu berumur %d tahun.%n", nama, umur);
Formatted (Java 15+) dan Text Block (Java 15+) #
// String.formatted() — versi instance method dari String.format
String pesan = "Selamat datang, %s! Saldo: Rp %.2f".formatted("Sari", 1500000.0);
// "Selamat datang, Sari! Saldo: Rp 1500000,00"
// Text Block — string multi-baris tanpa escape hell (Java 15+)
// ✗ ANTI-PATTERN: cara lama — penuh escape dan susah dibaca
String jsonLama = "{\n" +
" \"nama\": \"Laptop\",\n" +
" \"harga\": 15000000\n" +
"}";
// ✓ BENAR: Text Block — bersih dan mudah dibaca
String jsonBaru = """
{
"nama": "Laptop",
"harga": 15000000
}
""";
// Text Block otomatis hapus indentasi yang konsisten
// (indentasi relatif terhadap posisi tanda kutip penutup)
// Text Block dengan formatting
String query = """
SELECT p.nama, p.harga
FROM produk p
WHERE p.kategori = '%s'
ORDER BY p.harga %s
LIMIT %d
""".formatted("Elektronik", "ASC", 10);
// Text Block untuk HTML
String html = """
<!DOCTYPE html>
<html>
<head><title>%s</title></head>
<body>
<h1>%s</h1>
</body>
</html>
""".formatted("Judul Halaman", "Selamat Datang");
// Escape di dalam Text Block
String denganTanda = """
Tanda kutip tunggal ' tidak perlu escape.
Tanda kutip ganda " tidak perlu escape.
Tiga tanda kutip \""" perlu escape satu.
Backslash \\ perlu escape.
""";
Parsing dan Konversi #
String ke Tipe Primitif #
// Parsing String ke tipe numerik
int angkaInt = Integer.parseInt("42");
long angkaLong = Long.parseLong("9876543210");
double angkaDouble = Double.parseDouble("3.14");
float angkaFloat = Float.parseFloat("2.71");
boolean boolTrue = Boolean.parseBoolean("true"); // case insensitive
boolean boolFalse = Boolean.parseBoolean("TRUE"); // true
// Parsing dengan radix (basis bilangan)
int hex = Integer.parseInt("FF", 16); // 255
int biner = Integer.parseInt("1010", 2); // 10
int oktal = Integer.parseInt("17", 8); // 15
// Penanganan error — selalu tangkap NumberFormatException
try {
int hasil = Integer.parseInt("bukan angka");
} catch (NumberFormatException e) {
System.out.println("Bukan angka valid: " + e.getMessage());
}
// Cara aman tanpa exception — konversi ke Optional
java.util.OptionalInt aman = java.util.OptionalInt.empty();
try {
aman = java.util.OptionalInt.of(Integer.parseInt("42"));
} catch (NumberFormatException ignored) {}
// Tipe primitif ke String
String dariInt = String.valueOf(42); // "42"
String dariDouble = String.valueOf(3.14); // "3.14"
String dariBoolean = String.valueOf(true); // "true"
String dariChar = String.valueOf('A'); // "A"
// Atau menggunakan method wrapper class
String hex2 = Integer.toHexString(255); // "ff"
String biner2 = Integer.toBinaryString(10); // "1010"
String oktal2 = Integer.toOctalString(8); // "10"
// toString() — alternatif, tapi perhatikan autoboxing overhead
String s = Integer.toString(42); // "42"
String s2 = "" + 42; // "42" — tapi membuat objek lebih banyak
Konversi Karakter dan Encoding #
import java.nio.charset.StandardCharsets;
// String ke bytes dengan encoding spesifik
byte[] utf8 = "Pemrograman Java".getBytes(StandardCharsets.UTF_8);
byte[] latin1 = "Hello".getBytes(StandardCharsets.ISO_8859_1);
// Bytes ke String dengan encoding spesifik
String kembali = new String(utf8, StandardCharsets.UTF_8);
// Base64 encoding/decoding (Java 8+)
String asli = "Rahasia: password123";
String encoded = java.util.Base64.getEncoder()
.encodeToString(asli.getBytes(StandardCharsets.UTF_8));
// "UmFoYXNpYTogcGFzc3dvcmQxMjM="
byte[] decoded = java.util.Base64.getDecoder().decode(encoded);
String kembaliLagi = new String(decoded, StandardCharsets.UTF_8);
// "Rahasia: password123"
// URL-safe Base64 (untuk URL dan filename)
String urlSafe = java.util.Base64.getUrlEncoder()
.encodeToString(asli.getBytes(StandardCharsets.UTF_8));
Regular Expression dengan String #
Java punya API regex lengkap di java.util.regex, tapi banyak operasi regex bisa dilakukan langsung dari method String.
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// Validasi dengan matches() — cek apakah SELURUH string cocok dengan pola
boolean emailValid = "[email protected]".matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}");
boolean nomorHP = "08123456789".matches("0[89][0-9]{8,10}");
boolean hanyaAngka = "12345".matches("\\d+");
// ✗ ANTI-PATTERN: kompilasi Pattern dalam loop — sangat lambat
for (String item : daftarEmail) {
if (item.matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}")) { ... }
// Pattern.compile() dipanggil ulang setiap iterasi!
}
// ✓ BENAR: kompilasi Pattern sekali, gunakan berkali-kali
Pattern emailPattern = Pattern.compile("[\\w.]+@[\\w.]+\\.[a-z]{2,}");
for (String item : daftarEmail) {
if (emailPattern.matcher(item).matches()) { ... }
}
// Ekstrak grup dari string
Pattern tanggalPattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher matcher = tanggalPattern.matcher("Tanggal: 2024-05-10 adalah hari ini");
if (matcher.find()) {
String tahun = matcher.group(1); // "2024"
String bulan = matcher.group(2); // "05"
String hari = matcher.group(3); // "10"
System.out.println("Tahun: " + tahun + ", Bulan: " + bulan + ", Hari: " + hari);
}
// Temukan SEMUA kecocokan
Pattern angkaPattern = Pattern.compile("\\d+");
Matcher m = angkaPattern.matcher("Ada 3 kucing dan 12 anjing di 2 rumah");
while (m.find()) {
System.out.println("Angka: " + m.group() + " di posisi " + m.start());
}
// Angka: 3 di posisi 4
// Angka: 12 di posisi 16
// Angka: 2 di posisi 26
// Named groups (Java 7+) — lebih ekspresif dari numbered groups
Pattern named = Pattern.compile("(?<tahun>\\d{4})-(?<bulan>\\d{2})-(?<hari>\\d{2})");
Matcher nm = named.matcher("2024-05-10");
if (nm.matches()) {
System.out.println("Tahun: " + nm.group("tahun"));
System.out.println("Bulan: " + nm.group("bulan"));
}
// replaceAll dengan regex — sudah dibahas di atas
String bersih = "Halo Dunia !!!".replaceAll("\\s+", " ").trim();
// "Halo Dunia !!!"
// split dengan regex
String[] token = "satu, dua ,tiga , empat".split("\\s*,\\s*");
// ["satu", "dua", "tiga", "empat"]
String di Java Modern #
String API Java 9–21 #
// chars() — IntStream dari codepoint setiap karakter (Java 9+)
"Halo".chars()
.forEach(c -> System.out.print((char) c + " "));
// H a l o
// lines() — Stream<String> baris-per-baris (Java 11+)
String multiline = "baris satu\nbaris dua\nbaris tiga";
long jumlahBaris = multiline.lines().count(); // 3
multiline.lines()
.filter(baris -> baris.contains("dua"))
.forEach(System.out::println);
// "baris dua"
// strip(), stripLeading(), stripTrailing() (Java 11+)
// Sadar Unicode whitespace, lebih baik dari trim()
" \u2000halo\u2000 ".strip(); // "halo" — hapus Unicode whitespace
// repeat() (Java 11+)
String garis = "-".repeat(40); // "----------------------------------------"
// isBlank() (Java 11+) — true jika kosong atau hanya whitespace
"".isBlank(); // true
" ".isBlank(); // true
" a ".isBlank(); // false
// indent() (Java 12+) — tambah atau kurangi indentasi setiap baris
String kode = "int x = 1;\nint y = 2;";
String indented = kode.indent(4);
// " int x = 1;\n int y = 2;\n"
// transform() (Java 12+) — terapkan fungsi ke String
String hasil = " halo dunia "
.transform(String::strip)
.transform(s -> s.substring(0, 1).toUpperCase() + s.substring(1));
// "Halo dunia"
// formatted() (Java 15+) — sudah dibahas di bagian formatting
// String.valueOf() untuk null-safe conversion
Object obj = null;
String safe = String.valueOf(obj); // "null" — tidak throw NullPointerException
// (String) obj → NullPointerException!
Pola Umum dan Anti-Pattern #
// ✗ ANTI-PATTERN: membandingkan String dengan ==
String a = new String("halo");
String b = new String("halo");
if (a == b) { } // SELALU false! Bandingkan referensi, bukan nilai
// ✓ BENAR: gunakan equals()
if (a.equals(b)) { } // true
// ✓ BENAR: jika salah satu bisa null, letakkan literal di kiri
if ("halo".equals(variabelYangMungkinNull)) { } // tidak throw NPE
// ✗: if (variabelYangMungkinNull.equals("halo")) → NPE jika null!
// ✗ ANTI-PATTERN: concatenation dalam loop (sudah dibahas)
String s = "";
for (String item : daftar) {
s += item + ","; // buat objek baru setiap iterasi
}
// ✓ BENAR: StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : daftar) {
sb.append(item).append(",");
}
// ✓ LEBIH BAIK LAGI: String.join() untuk kasus sederhana
String joined = String.join(",", daftar);
// ✓ ATAU: Collectors.joining() untuk Stream
String fromStream = daftar.stream()
.collect(java.util.stream.Collectors.joining(", ", "[", "]"));
// "[item1, item2, item3]"
// ✗ ANTI-PATTERN: tidak tentukan charset saat konversi bytes
byte[] bytes = teks.getBytes(); // charset default platform!
String fromBytes = new String(bytes); // charset default platform!
// ✓ BENAR: selalu eksplisit tentukan charset
byte[] bytesUTF8 = teks.getBytes(StandardCharsets.UTF_8);
String fromBytesUTF8 = new String(bytesUTF8, StandardCharsets.UTF_8);
// ✗ ANTI-PATTERN: compile Pattern di dalam method yang sering dipanggil
public boolean isEmail(String s) {
return s.matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}"); // kompilasi Pattern setiap kali!
}
// ✓ BENAR: Pattern sebagai field static final
private static final Pattern EMAIL_PATTERN =
Pattern.compile("[\\w.]+@[\\w.]+\\.[a-z]{2,}");
public boolean isEmail(String s) {
return EMAIL_PATTERN.matcher(s).matches();
}
Perbandingan dan Pengurutan String #
Pengurutan alfabet yang “benar” untuk bahasa tertentu tidak bisa hanya mengandalkan Unicode ordering. compareTo() mengurutkan berdasarkan nilai Unicode — huruf kapital selalu lebih kecil dari huruf kecil, dan karakter beraksent tidak diurutkan sesuai konvensi bahasa.
import java.text.Collator;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
// compareTo() — urutan leksikografis berdasarkan nilai Unicode
List<String> buah = new java.util.ArrayList<>(
List.of("mangga", "Apel", "jeruk", "Banana")
);
buah.sort(String::compareTo);
System.out.println(buah);
// [Apel, Banana, jeruk, mangga] — huruf kapital SEBELUM huruf kecil di Unicode!
// compareToIgnoreCase() — lebih natural, abaikan case
buah.sort(String::compareToIgnoreCase);
System.out.println(buah);
// [Apel, Banana, jeruk, mangga]
// Komparator berantai untuk sorting kompleks
List<String> data = List.of("Budi", "ani", "Citra", "budi", "ANI");
List<String> terurut = data.stream()
.sorted(Comparator.comparingInt(String::length) // urut panjang dulu
.thenComparing(String.CASE_INSENSITIVE_ORDER)) // lalu abjad
.toList();
// ✓ BENAR: Collator untuk pengurutan linguistik yang tepat per bahasa
Collator collator = Collator.getInstance(new Locale("id", "ID")); // Bahasa Indonesia
collator.setStrength(Collator.SECONDARY); // abaikan perbedaan case minor
buah.sort(collator::compare); // urutan yang benar secara linguistik
String dan Keamanan #
Password — Jangan Gunakan String #
// ✗ ANTI-PATTERN: simpan password sebagai String
String password = "rahasia123";
// Masalah:
// 1. String di-intern di String pool — tetap di memori sampai GC jalankan
// 2. String tidak bisa di-zero-out — bisa muncul di heap dump
// ✓ BENAR: gunakan char[] untuk data sensitif
char[] passwordChar = "rahasia123".toCharArray();
try {
autentikasi(passwordChar);
} finally {
// Zero-out setelah selesai — hapus dari memori secara eksplisit
java.util.Arrays.fill(passwordChar, '\0');
}
// API Java standar mengikuti praktik ini:
// javax.crypto.SecretKey, java.security.KeyStore, dan SSL context
// semuanya menggunakan char[] atau byte[] untuk credential sensitif
Log Injection #
// ✗ ANTI-PATTERN: log input pengguna langsung
String userInput = request.getParameter("nama");
logger.info("User login: " + userInput);
// Jika input = "admin\nINFO: Fake admin login successful"
// Log akan terkontaminasi dengan baris palsu!
// ✓ BENAR: sanitasi newline dari input sebelum di-log
String sanitized = userInput.replaceAll("[\r\n]", "_");
logger.info("User login: {}", sanitized); // parameterized logging — lebih aman dan lebih cepat
Ringkasan #
- String bersifat immutable — setiap operasi “modifikasi” menghasilkan objek baru. Ini thread-safe secara otomatis, tapi boros memori jika dilakukan berulang kali dalam loop.
- Selalu gunakan
equals()untuk membandingkan nilai String, bukan==. Letakkan literal di sisi kiri ("halo".equals(variabel)) untuk menghindariNullPointerException.- Gunakan
StringBuilderuntuk penggabungan String dalam loop atau operasi berulang. Untuk kasus sederhana,String.join()atauCollectors.joining()lebih ekspresif dari loop manual.- Text Block (Java 15+) menghilangkan kebutuhan escape dan konkatenasi untuk string multi-baris seperti JSON, SQL, dan HTML. Gunakan ini untuk keterbacaan yang jauh lebih baik.
- Kompilasi
Patternsekali sebagaistatic finalfield, bukan di dalam method yang sering dipanggil. Kompilasi regex itu mahal — jangan ulangi setiap kali.- Selalu tentukan charset eksplisit (
StandardCharsets.UTF_8) saat konversi antaraStringdanbyte[]. Mengandalkan charset default platform menyebabkan bug yang sangat sulit direproduksi.strip()(Java 11+) lebih baik daritrim()karena sadar Unicode whitespace.isBlank()lebih ekspresif dariisEmpty()untuk memeriksa string yang hanya berisi whitespace.String.valueOf(obj)adalah cara null-safe untuk mengkonversi objek ke String — mengembalikan"null"jika objek null, bukanNullPointerException.