İçeriğe atla
← Java ve JVM

· 2 dk okuma

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.

#java #immutability #clean-code

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, double vs.) 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