İçeriğe atla
← Java ve JVM

· 4 dk okuma

Mutable ve Immutable Nesneler

Immutable nesnelerin özellikleri, ne zaman tercih edilmeli, avantaj ve dezavantajları örneklerle.

#java #immutability #tasarim

Tanım

  • Oluşturulduktan sonra iç durumları (state) değiştirilebilen nesnelere mutable denir. Dolayısıyla iç durumu değiştirilemeyen nesnelere de immutable denilir.
  • Java’da en klasik örnek String (immutable) ile StringBuilder (mutable) dır.

Immutable Objelerin Özellikleri

  • 🧵 Thread-safe’tir; birden fazla thread kolaylıkla paylaşabilir.
  • 🤝 Durumu değişmediği için güvenli bir şekilde paylaşılabilir, paylaşım sırasında defensive copy gerekmez.
  • 🔑 Hash değeri değişmediği için HashMap/Set anahtarı olarak güvenle kullanılır.
  • 📥 Immutable objeler yalnızca get metotları sunar.
  • 🆕 Immutable bir nesnenin durumunu değiştirmeye yönelik bir girişim yeni bir nesne üretir. Dolayısıyla sık güncellenmesi gereken bir nesne immutable olmamalıdır. Mesela String bir değişken bir döngü içerisinde sürekli güncellenecek ise StringBuilder kullanmak daha doğru olacaktır.

⚠️ Nüans: “Defensive copy gerekmez” ifadesi nesneyi paylaşırken geçerlidir (çünkü nesne zaten değişemez). Ancak immutable bir sınıfın iç alanı mutable bir nesne ise (ör. List), bu alanı constructor’da alırken ve getter’da dönerken defensive copy yapılmalıdır; aksi halde dışarıdan içerik değiştirilebilir.

Kullanım Senaryosu

User Story

Bir kullanıcı hesabından fatura ödemesi yapmak istiyor; böylece bakiyesinden ödeme tutarı ve işlem ücreti düşülecek ve doğru kalan bakiyeyi görebilecek.

Kabul kriteri: Bakiye yetersizse işlem reddedilmeli ve orijinal bakiye hiç değişmemeli.

Senaryo

Ahmet’in hesabında 100.00 TL var. 84.50 TL’lik fatura + 1.00 TL işlem ücreti ödeyecek. Toplam 85.50 TL. İşlem sonrası bakiye 14.50 TL olmalı.

BigDecimal immutable olduğu için, hesaplama sırasında orijinal bakiye asla bozulmaz. Bu, failure atomicity güvencesini sağlar: başarısız bir işlem sonrasında bile nesne, işlem çağrılmadan önceki tutarlı halinde kalır.

public class PaymentService {
    // Bakiye ve tutarlar BigDecimal (immutable) — para hesabında double ASLA kullanılmaz
    public BigDecimal payBill(BigDecimal balance, BigDecimal billAmount, BigDecimal fee) {
        BigDecimal totalCost = billAmount.add(fee); // yeni nesne: 85.50
        // Kontrol: bakiye yeterli mi? (compareTo, çünkü equals ölçeğe duyarlıdır)
        if (balance.compareTo(totalCost) < 0) {
            throw new IllegalStateException("Yetersiz bakiye");
            // balance HİÇ değişmedi -> hesap tutarlı kaldı (failure atomicity)
        }
        BigDecimal newBalance = balance.subtract(totalCost); // yeni nesne: 14.50
        return newBalance;
    }
}

Çağrım:

var service = new PaymentService();
BigDecimal balance = new BigDecimal("100.00");
BigDecimal bill    = new BigDecimal("84.50");
BigDecimal fee     = new BigDecimal("1.00");

BigDecimal result = service.payBill(balance, bill, fee);
System.out.println(balance); // 100.00  -> ORİJİNAL DEĞİŞMEDİ
System.out.println(result);  // 14.50   -> yeni bakiye

Buradaki Kilit Noktalar

  1. Orijinal bozulmaz. add/subtract yeni nesne döndürdüğü için balance her satırda hâlâ 100.00. İşlemin ortasında hata çıksa bile hesap “yarı düşülmüş” hatalı bir durumda kalmaz.
  2. Yanlışlıkla değiştirme (aliasing) riski yok. Aynı balance nesnesi başka bir servise/thread’e geçse bile, kimse onun içeriğini değiştiremez. Mutable olsaydı bir yerde balance.setValue(...) yapıldığında her yer etkilenirdi — buna aliasing bug denir.

Mutable Olsaydı Ne Olurdu? (karşılaştırma)

💡 Not: BigDecimal gerçekte immutable’dır. Aşağıdaki Money sınıfı, mutable davranışı göstermek için kullanılan farazi bir örnektir.

// Mutable (TEHLİKELİ senaryo — farazi)
Money balance = new Money("100.00");
balance.subtract(totalCost); // nesnenin İÇİ değişir -> balance artık 14.50
// Sorun: aynı balance nesnesini paylaşan başka bir yer varsa,
// onun da değeri habersizce değişti. Ayrıca subtract sırasında hata olursa
// nesne yarı-değişmiş, tutarsız durumda kalabilir.

Özet Tablo: Mutable vs Immutable

Kaynak: GeeksforGeeks — “Mutable and Immutable in Java”.

Mutable Immutable
Nesnenin değeri initialization sonrası değiştirilebilir. Nesnenin değeri initialization sonrası değiştirilemez.
State (durum) değiştirilebilir. State, oluşturulduktan sonra sabit kalır.
Yeni nesne oluşmaz. Nesnenin değerini değiştirmek yeni bir nesne oluşturur.
Nesneyi değiştirmek için metotlar sunar. Nesne değerini değiştirmek için metot sunmaz.
get() ve set() metotlarını destekler. Nesne değerini almak için yalnızca get() metodunu destekler.
Thread-safe olabilir ya da olmayabilir. Immutable nesneler doğası gereği thread-safe’tir.
Mutable bir sınıf oluşturmanın temelleri: alanları değiştiren metotlar, getter’lar ve setter’lar. Immutable bir sınıf oluşturmanın temelleri: final class, private alanlar ve final mutable nesneler.

Mutable & Immutable Nesnelerin Avantajları ve Kullanımları

Kaynak: GeeksforGeeks — “Mutable and Immutable in Java”.

Mutable Nesnelerin Avantajları Immutable Nesnelerin Avantajları
Esneklik (Flexibility): Mutable sınıf nesneleri durumlarının değiştirilmesine izin verir; bu, dinamik değişiklikler için kullanışlıdır. Thread Safety: Immutable nesneler, durumları değişemediği için senkronizasyon olmadan thread’ler arasında paylaşılabilir.
Performans: Yeni nesne oluşturmadan doğrudan değiştirilebildikleri için genellikle iyi performans gösterirler. Güvenlik (Security): Immutable nesneler daha güvenlidir; durumları oluşturulduktan sonra kötü niyetle değiştirilemez.
Builder Pattern: Mutable nesneler, akıcı (fluent) ve okunabilir nesne oluşturmaya olanak tanıyan builder pattern ile yaygın olarak kullanılır. Daha Basit Durum Yönetimi: Immutable nesneler, durumları beklenmedik şekilde değişmediği için hata ayıklamayı ve bakımı basitleştirir.
Caching: Durumları değişmediği için önbellekleme (caching) mekanizmaları için idealdir; kolayca cache’lenebilirler.
Bellek Yönetimi: Defensive copy gerektirmedikleri için bellek yönetimi daha kolaydır.

Aynı kategoriden