Java: Pass by Value ve Defensive Copy
Java gerçekten pass-by-value mı? Referansların kopyalanması ve defensive copy ile nesne bütünlüğünü koruma.
Pass By Reference / Value
Metota bir parametre geçerken bizzat kendisi mi (by reference) yoksa değeri mi (by value) geçiyor?
Bu sorunun cevabı Java için şöyledir:
- Primitive türler (
int,boolean,doublevs.) by value’dur. Değerin kopyası geçer. - Referans türler (nesneler) aslında bunlar da by value’dur. Ancak value’dan kasıt referansın gösterdiği adrestir (nesnenin bellekteki adresi). Dolayısıyla bir nesneyi bir metota parametre olarak verirsek, metot içerisinde de nesne ile ilgili bir güncelleme yapılırsa, bu güncelleme metot dışındaki nesneye yansıyacaktır.
Defensive Copy
Bir nesnenin mutable (değiştirilebilir) iç durumunu dış müdahalelere karşı korumak için, dışarıdan gelen ya da dışarıya verilen mutable nesnelerin kopyasını alıp orijinali paylaşmama tekniğidir.
Kodu çağıran tarafın (client) kötü niyetli ya da dikkatsiz davranarak nesnenin iç durumunu bozmasına karşı savunma yaptığı için defensive copy denilmiştir.
2 kritik noktaya değinilebilir:
- Dışarıdan alınan mutable parametrenin kopyalanma ihtiyacı doğabilir. Çünkü client elimizdeki referansı değiştirse bile bizim iç durumumuz etkilenmemesi gerekiyordur.
- İçerideki mutable alanı dışarıya doğrudan değil, kopyasını veririz. Çünkü client döndürülen referans üzerinden iç durumumuzu değiştirsin istemeyiz.
Örnek 1: Bir sınıfın invariant’ını (değişmezini) koruma — Getter’dan sızıntı
Bir banka hesabında işlem geçmişini tuttuğumuzu varsayalım. Bu geçmiş asla dışarıdan değiştirilememeli.
❌ Kötü Senaryo:
public final class Account {
private final List<Transaction> history = new ArrayList<>();
public void deposit(Transaction t) { history.add(t); }
// KÖTÜ: iç listeyi doğrudan döndürüyor
public List<Transaction> getHistory() { return history; }
}
Client şöyle bir şey yaparsa benim iç durumuma müdahale etmiş oluyor. İstenmeyen durum!
account.getHistory().clear(); // Tüm işlem geçmişi silindi!
✅ Doğru senaryo:
public final class Account {
private final List<Transaction> history = new ArrayList<>();
public void deposit(Transaction t) { history.add(t); }
public List<Transaction> getHistory() {
return new ArrayList<>(history); // ya da List.copyOf(history)
}
}
Mutable iç durumun getter üzerinden dışarı sızması, kapsüllemeyi (encapsulation) fiilen yok eder. Nesnen üzerindeki kontrolü kaybederiz.
Örnek 2: Constructor’a giren nesnenin sonradan değişmesi — Giriş sızıntısı
Bir rezervasyonun başlangıç/bitiş zamanını tutan bir sınıf düşünelim. Set edildikten sonra sabit kalmalıdır.
❌ Kötü Senaryo:
public final class Reservation {
private final Date start;
public Reservation(Date start) { this.start = start; } // KÖTÜ
public Date getStart() { return start; } // KÖTÜ
}
Client, elinde hâlâ tuttuğu referansla nesneyi set ettikten sonra bozabilir:
Date d = new Date();
Reservation r = new Reservation(d);
d.setYear(0); // Reservation'ın içindeki start da değişti!
✅ Doğru senaryo:
public final class Reservation {
private final Date start;
public Reservation(Date start) {
this.start = new Date(start.getTime()); // dışarıdaki d ile bağ koptu
}
public Date getStart() { return new Date(start.getTime()); }
}
Aynı kategoriden
Mutable ve Immutable Nesneler
Immutable nesnelerin özellikleri, ne zaman tercih edilmeli, avantaj ve dezavantajları örneklerle.
JVM Bellek Alanları
Heap, metaspace, stack, PC register: hangi veri nerede durur, thread-safety ile ilişkisi nedir?
JDK, JRE ve JVM Arasındaki Fark
JDK, JRE ve JVM ne işe yarar, birbirini nasıl kapsar? Kısa karşılaştırma tablosuyla.