Değişmez Sınıf Tasarlamak — Bir Kez Kur, Hep Güven
Önce şunu oku: Object Sınıfı — Her Nesnenin Ortak Mirası , final ve static — Kilitli Olan ve Ortak Olan
30 saniyede özet
Değişmez bir nesne kurulduktan sonra hiç değişmez; değişiklik yeni bir nesne üretir. Tarifi kısadır: final sınıf, private final alanlar, setter yok, değişebilen girdilerin kopyası. Karşılığında güvenle paylaşılır.
Bir müzede vitrindeki tabloya kimse dokunamaz. Onu yüzlerce kişi aynı anda izler ve hiçbiri ötekinin gördüğünü bozamaz.
Bazı nesneler de böyle olmalı: kurulduktan sonra hiç değişmeyen, herkesin gönül rahatlığıyla paylaştığı nesneler.
Adım adım oku
- Tutar nesnesi bir vitrinde duruyor: 100 TRY.
- Onu değiştirmek isteyen bir setter yok; deneme geri döner.
- arti(50) çağrısı yeni bir Tutar üretir: 150 TRY.
- Eski tutar hâlâ 100; onu paylaşan herkes güvende.
-
Bayt: Bir tutar nesnesini iki hesaba verdim. Biri değiştirdi, öteki de değişmiş!
-
Sen: Aynı nesneyi paylaşıyorlardı yani.
-
Bayt: Evet. Nesne değişebildiği için paylaşmak tehlikeliydi.
-
Bayt: Değişmez yaparsak paylaşmak tamamen güvenli olur. Tarifi beş adım.
Değişmez nesne ne demek?
Bir immutableOluşturulduktan sonra içi değişmeyen nesne. Anahtar olarak güvenlidir, çünkü hashCode'u hiç değişmez.Sözlükte gör → nesne kurulduğu andaki hâlini sonsuza kadar korur. String, BigDecimal, LocalDate ve Integer böyledir.
Bu nesnelerin “değiştiren” metotları aslında yeni bir nesne döndürür: tarih.plusDays(1) eski tarihi değiştirmez. Böylece bir nesneyi kime verirsen ver, geri geldiğinde aynı kalmıştır.
Kafam karıştı, daha basit anlat
Değişmez nesne bir fotoğraf gibidir: çekildiği an donmuştur. Farklı bir hâl istiyorsan yeni bir fotoğraf çekersin; eskisi albümde aynen durur.
Program ne yazdırır?
Beş adımlık tarif
Kendi değişmez sınıfını yazmak için beş kural yeter:
- Sınıfı
finalyap. Alt sınıf değişebilen bir sürüm yazamasın. - Alanları
private finalyap. Bir kez atanır, dışarıdan görünmez. - Setter yazma. Değişiklik isteyen metot yeni bir nesne döndürsün.
- Kurucuda kuralı kontrol et. Geçersiz nesne hiç doğmasın.
- Değişebilen girdileri ve çıktıları kopyala. Buna defensive copyDışarıdan alınan ya da dışarıya verilen değiştirilebilir bir nesnenin kopyasını kullanmak; böylece başkasının referansı iç durumu değiştiremez.Sözlükte gör → denir.
public final class Tutar { private final BigDecimal deger; private final String paraBirimi;
public Tutar(BigDecimal deger, String paraBirimi) { if (deger.signum() < 0) throw new IllegalArgumentException("Negatif olamaz"); this.deger = deger; this.paraBirimi = paraBirimi; }
public Tutar arti(Tutar diger) { // setter değil: yeni nesne return new Tutar(deger.add(diger.deger), paraBirimi); }}Kafam karıştı, daha basit anlat
Kapıyı kilitle (final sınıf), çekmeceleri kilitle (private final), anahtar dağıtma (setter yok). Değişiklik istenirse yeni bir kutu hazırla ve onu ver.
Değişmez bir sınıf yazarken sınıfı neden `final` yaparsın?
Değişmez bir Tutar sınıfında değeri artırmak gerekiyor. En uygun yol hangisi?
Satır satır: record da delinebilir
record alanları final yapar ve setter üretmez. Ama bir alan değişebilen bir liste ise ne olur?
Sepet adında, bir ürün listesi taşıyan bir record var. Değişebilen bir ArrayList verip sepeti kurduktan sonra aynı listeye `add("elma")` dersen sepetin içi değişir mi? Cevabı göster
Değişir. record listenin kendisini değil, listeye giden oku saklar. Dışarıda kalan aynı ok üzerinden liste değişince sepetin gördüğü liste de değişir.
Oku değil, kopyayı sakla
record Sepet(List<String> urunler) { Sepet { urunler = List.copyOf(urunler); }} List<String> liste = new ArrayList<>(List.of("ekmek"));Sepet sepet = new Sepet(liste);liste.add("elma");System.out.println(sepet.urunler());Debug
kompakt kurucu Gelen listenin değişmez bir kopyası alınır. Sepet artık dışarıdaki listeye bağlı değil.
- sepet.urunler
- = [ekmek] (kopya)
Sol/sağ ok tuşlarıyla da gezebilirsin.
Sepetin içine kim dokunabilir?
Tohum 608508record Sepet(List<String> urunler) { Sepet { Kopya yok (oku sakla) } }
sepet.urunler() = [ekmek]
Şu an ne oldu?
Kopya yok (oku sakla)
Sepet [ekmek] ile kuruldu. İki sızıntı denenecek: içeri ve dışarı.
Görevler0/3
Sepeti, ona hiç dokunmadan değiştiraçık
İpucu
Varsayılan ayarlar yeter.
Girişi koru ama sepet yine dışarıdan değişsinaçık
İpucu
Kopya değişebilir olsun.
Sepet baştan sona [ekmek] kalsınaçık
İpucu
Değişmez bir kopya.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat. Kopya yok: dışarıdaki listeye eklenen elma da, erişim metodundan eklenen kola da sepete girdi.
- “new ArrayList” seç. Elma artık giremedi, ama erişim metodu değişebilen kopyayı dışarı verdiği için kola girdi.
- “List.copyOf” seç. İki deneme de boşa çıktı; sepet [ekmek] kaldı.
Program ne yazdırır?
Neden değer?
Değişmez bir nesneyi birden çok thread aynı anda kilitsiz okuyabilir; kimse ötekinin altından değiştiremez. HashMap anahtarı olarak da güvenlidir, çünkü hash değeri hiç değişmez.
Bedeli, her değişiklikte yeni bir nesne üretilmesidir. Küçük değer nesnelerinde bu genelde önemsizdir; çok büyük ve çok sık değişen yapılarda ise ölçüp karar verirsin.
Kafam karıştı, daha basit anlat
Değişmeyen bir şeyi paylaşmak risksizdir: kimse onu senin elinden değiştiremez. Karşılığında her değişiklikte yeni bir kopya yaparsın, bu da çoğu zaman ucuzdur.
Değişmez nesnelerin birden çok thread arasında kilitsiz paylaşılabilmesinin sebebi nedir?
Bankada değer nesnelerinin çoğu böyle yazılır: bir para transferi talimatı bir kez oluşturulur ve onay, kayıt ve gönderim adımları boyunca hiç değişmez.
Derinleş · Transfer talimatı: bir kez kur, her adımda güven 2 dosya · ~28 satır · ilk okumada atlayabilirsin
Tuzaklar
final alanı değişmez sanmak. final List<String> yalnızca oku kilitler; listenin içi değişir. İçerik de sabit kalsın istiyorsan kopyasını al ve değişmez bir tip kullan.
İç diziyi dışarı vermek. Bir getter iç diziyi ya da değişebilen listeyi doğrudan döndürürse, alan kişi içini değiştirebilir. Kopya ya da değişmez bir görünüm döndür.
final olmayan sınıf. Biri sınıftan türeyip değişebilen alanlar ekleyebilir ve senin sınıfın gibi davranarak dolaşabilir. Sınıfı final yap ya da record kullan.
Bu sınıf değişmez olarak tasarlandı ama bir servis kurulmuş bir ekstrenin hareketlerini değiştirebiliyor. Deliğin kaynağı hangi satır?
Kendini sına
Değişmez bir nesnenin değiştiren metotları yeni bir nesne döndürür.
Her tipi değişmez ya da değişebilen olarak ayır.
Aklında kalacak üç şey
- 1 Değişmez bir nesne kurulduktan sonra hiç değişmez; bir değişiklik istendiğinde yeni bir nesne döndürülür.
- 2 Tarif: final sınıf, private final alanlar, setter yok, kurucuda kural kontrolü ve değişebilen her girdinin ya da çıktının kopyası.
- 3 record da içindeki listeyi korumaz; kompakt kurucuda List.copyOf ile kopyalamadıkça dışarıdan değiştirilebilir.
4 kart sonraki derste seni bekliyor