IMMUTABLE

Furkan Şahin Kulaksız
6 min readApr 16, 2023

Immutable kelime anlamı olarak değişmez demektir. Java’daki karşılığını ise şu şekilde açıklayabiliriz. Bir object oluşturulduktan sonra, contentinin(içeriğinin) / stateinin(durumunun) değiştirilememesinin garanti edilmesi anlamına gelir. Yani bir object tüm yaşam süresi boyunca aynı şekilde davranır.

Mesela String Class’ı immutable’dır. Yani değişmezdir. Peki bu ne demek.?

String name = "Furkan";
name.concat("Kulaksiz");
System.out.println(" Name -> " + name);

Yukarıdaki kodu inceleyelim. “name” adında bir String değişken var ve bu değişkene 2. satırda “concat()” methoduyla başka bir String ifadeyi birleştirme işlemi uygulanmış. Ama name adındaki değer okudunduğunda ekranda “Furkan” yazısını görürüz. Çünkü String’ler immutable’dır.

Ayrıca name.concat(“Kulaksiz”) ile oluşturulan yeni String instance’ı herhangi bir şekilde kullanılamayacağından Garbace Collector tarafından bir müddet sonra silinecektir.

Immutable’ı açıklamadan önce “final” keywordü ile alakalı da birkaç cümle kurmak gerekir.

Java’da bir değişken oluşturulduğu zaman default olarak mutable’dır. Yani contenti değiştirilebilir. Fakat bir değişkende final keywordü kullanılırsa Java Compiler, ilgili değişkenin contentini değiştirmemize izin vermez. Bunu yaparsak Java-Compiler bize compile-time error ile karşılık verir.

Çok önemli not: Bir değişkeni final yaptığımız zaman referansını değişmez yapmış oluruz. İlgili referanstaki değerin contentini değiştirebiliriz.

Yukarıdaki list adındaki listeyi final keywordü ile tanımladık ama list’in içerisine eleman ekleyebildik çünkü list’in sadece referansını değişmez kıldık. Contenti ile alakalı istediğimiz gibi eleman ekleme, çıkarma işlemlerini yapabiliriz.

List<String> list2 = new ArrayList<>();
list = list2;

Bir önceki paragrafta yazdığımız cümleyi doğrulamak için yukarıdaki kod blockuna bakmamız yeterli olacaktır. Herhangi başka bir list tanımlayıp en basitinden bir referans ataması işlemi yapmaya çalışırsak final keywordünden dolayı hata alırız.

IMMUTABILITY Biraz Daha Detaylandıralım

Wrapper Class’lar ya da String’ler java’da immutable classlara örnek olarak verilebilir. Bunun dışında List.of(), Set.of(), Map.of() ile oluşturulan veri yapıları da immutable’dır.

Peki kendi immutable classimizi oluşturmak istersek ne yapmamız lazım.?

Elimizde çok tipik 2 tane java classı olsun.

Yukarıda iki farklı class’ımız var. Fakat dikkat edilmesi gereken husus burada şu olmalı. Employee class’ının içerisinde Address tipinde bir değişken kullanılmıştır.

Amacımız Employee classını immutable yapmak.

1.Bir değişkenin contentini değiştiremiyor olmamız lazım. Bunun için de, aslında Employee classındaki setter’lardan kurtulsak çok iyi olur.

Setter methodları kaldırmak aslında yeterli gibi görünse de yapmamız gereken birkaç işlem daha var. Setter methodları kaldırmak neden yetmez.? Hemen bakalım.

Yukarıda Employee sınıfını extend edip ufak bir kurnazlık yapmaya çalıştık. Biliyoruz ki name field’ı aslında Employee class’ı içinde vardı ve biz onu alıp FatihTerim constructor’unda yeniden set’ledik.

Yukarıdaki kodun çıktısında ise aslında name field’ının Fatih Terim’den sonra Metin Oktay olduğunu görürüz ve dolayısıyla immutable için gerekli olan contentin değişmemesini garanti edemedik. Bu yüzden durmak yok yola devam.!

2.Employee class’ını final keywordü ile imleyelim. Bu sayede class’ının override olma olasılığını ortadan kaldırmış oluruz.

3. Bu adım bir class’ı immutable yaparken karşımıza çıkan önemli adımlardan birisidir. Burada bilmemiz çok önemli konular var. (object copy — object cloning)Bu konuları hallettikten sonra odak noktamıza geri dönebiliriz.

Object Cloning — Object Copy

→ Object kopyalamak aslında bir objeyi birebir kopyalamak demektir. Geçerli objecte ait class’ın yeni bir instance’ını oluşturur ve tüm field’larını tam olarak bu object’in karşılık gelen field’larının içeriğiyle başlatır. Java’da bir objectin kopyasını oluşturacak bir operatör yoktur. Java’da atama operatörü aslında nesnenin değil, nesnenin referansının bir kopyasını oluşturur. Konuyu daha da detaylı bir şekilde kavramak için reference copy ve object copy arasındaki farkı anlamamız gerekir.

Reference Copy, bir objecte işaret eden bir referans değişkeninin bir kopyasını oluşturur. Örneğin elimizde bir tane Byce objecti olsun. Bunun haricinde ikinci bir MyByce objecti tanımlayıp bunu atama operatörü ile eşitlersek elimizde iki tane MyByce değişkeni olacak ama yine de totalde bir instance’ımız olacak.

Aşağıdaki kod blockunu incelersek eğer;

-- OUTPUT --
x1 : 10 y1 : 20
x1 : 100 y1 : 20
x2 : 100 y2 : 20

Yukarıdaki olayı görsellerle anlatmak gerekirse;

Object Copy ise objectin kendisinin bir kopyasını oluşturur. Yani yukarıda yapmış olduğumuz işlemden farklı olarak objectin kopyasını oluştururuz ve kopyalanan objeye reference olan ikinci bir reference değişkeni oluşturmuş oluruz.

Shallow Copy — Deep Copy — Lazy Copy, object kopyalamakla alakalı konulardır.

  • Shallow Copy

Shallow’un Türkçe karşılığı sığ, yüzeysel gibi anlamlara gelir. Bundan dolayı shallow copy’yi (her ne kadar bu yöntemi sevmesem de) “yüzeysel kopyalama” olarak çevirebiliriz.

Bir objection shallow copy’si, instance değişkenleri ile aynı olan yeni bir object olmasıdır. Örneğin, Bir SET’in sahllow copy’si eski SET ile aynı üyelere sahiptir ve objectleri eski SET ile pointerlar aracılığıyla paylaşırlar.

Bir diğer deyişle Shallow Copy’de sadece referanslar kopyalandığını söyleyebiliriz. Öyleyse asıl object ile kopyalanan object aynı referansa işaret ederler.

Eğer shallow copy mekanizmasıyla bir object kopyalarsak, orjinal objectin bütün fieldları kopyalanır. Bir önemli not daha, eğer objectin içerisinde class tipinde değerler varsa bu durumda sadece objectlere yapılan referencelar kopyalanır.

Clonable interface’i bulunan clone() methodu aslında Shallow copy’ye bir örnektir.

** Clonelanmış objectler ve gerçek objectler tamamen birbirlerinden bağımsız değildirler.

** Clonelanmış object’te yapılan değişiklik, gerçek object’te de taklit edilir. Ya da bunun tam tersi de geçerlidir.

** clone() methodunun default uygulanışı Shallow Copy’ye bir örnektir.

** Eğer class primitive type’lardan oluşuyorsa o zaman Shallow Copy tercih edilebilir.

** Hızlıdır ve Deep Copy’ye göre daha az maliyetlidir.

** Memory, Deep Copy’ye göre daha verimli bir şekilde kullanılır.

** == operatörü ile aynı işlevi gördüğünü söyleyebiliriz. Shallow Copy object reference copy’dir.

** Java’da unmodifibleList adında bir list tipi mevcuttur. Bu aslında Collections sınıfındaki unmodifibleList methodu yardımıyla yaptığımız bir özellik.(tabii bu unmodifibleList olabileceği gibi, unmodifibleSet ya da unmodifibleMap ‘ de olabilirdi.) İçerisine aldığı list parametreyi, modify yapılamayacak hale getiren bir yöntemdir. Bir kopyasını oluşturur. Orjinal liste ne değişikliğe uğrarsa, unmodifibleList de aynısını oluşturur. Yani shallow copy’ye birörnek olabilir.

  • Deep Copy

Deep Copy yaklaşımında oluşturulan clone, daha önce değişebilecek herhangi bir nesneye bağımlı değildir. Orjinal nesnenin tüm field’ları otomatik olarak kopyalanır. Ama burada Shallow Copy’den şöyle bir fark vardır. Eğer kopyalanacak object field olarak reference type bir veri içeriyorsa bunların da kopyası oluşturulur. Bu da şu anlama gelir. Reference içeren bir object’de deep copy yaparsanız, hem original nesne hem de kopyalanan nesne farklı reference noktalarını gösterir. Ve verilerde herhangi bir değişiklik olursa, kopylanan object orijinal nesneyi etkilemez.

** Clonelanmış objectler ve original objectler birbirlerinden tamamen ayrı yapıdadırlar.

** Bellek kullanımı Shallow Copy’ye göre daha az verimlidir.

** Shallow Copy’ye göre daha yavaştır ve maliyeti Shallow Copy’ye göre daha fazladır.

** Bir object’in deep copy’sini almak için clone() methodunu ezmeliyiz.

  • LAZY COPY

Lazy copy, bir nesnenin kopyalanmasını, nesneye ilk erişildiğinde gerçekeştirir. Bu, nesnenin kopyalanmasının gerekip gerekmediğine bağlı olarak, performans arttırabilir. Ayrıca lazy copy için, Deep Copy ve Shallow Copy’nin bir kombinasyonudur diyebiliriz. GeeksForGeeks’in Lazy Copy tanımında aynen şu ifadeler yer almaktadır. Mekanizma basittir. Initial state’te, shallow copy yaklaşımı kullanılır. Verileri kaç nesnenin paylaştığını izlemek için bir sayaç da kullanılır. Program orijinal nesneyi değiştirmek istediğinde, nesnenin paylaşılıp paylaşılmadığını kontrol eder. Nesne paylaşılırsa, deep copy mekanizması başlatılır.

4. Copy işlemlerini konuştuk, burada bizim işimize yarayacak olan, aslında genelde deep copy. Bazı durumlarda da shallow copy kullanılabilir. Veri üyelerinin bir nesne referansıyla değiştirilememesi için, parametre alan consturctor’lardan bir deep copy gerçekleştirerek tüm alanların initial edilmesi gerekir. Ama bu durum tabiiki, class içerisinde mutable olan object’ler varsa yapılır. Mesela bir class’ın içi tamamen primitive type’lardan oluşuyorsa bu class’ı mutable yapmak için deep copy ya da shallow copy kullanmaya gerek kalmaz. Ya dabir class’ın içerisinde String bir değer varsa, böyle bir işleme gerek kalmaz. (Çünkü string zaten immutable’dır.) Ama bir class içerisinde başka bir class varsa o zaman deep copy kullanılmalıdır. Farklı bir not olarak da, eğer aşağıdaki örnekte göreceğiniz gibi bir class’ın içerisinde sadece array şeklinde bir field varsa da burada sadece shallow copy yardımıyla class’ınızı immutable yapabilirsiniz.

Yani aslında bir class’ı immutable yapmak için, yukarıdaki adımları uyguladığımızda karşımıza şöyle bir kod yapısı çıkacaktır.

İletişim, İşbirlikleri ve Teklifler için,

github: https://github.com/fsk

bitbucket: https://bitbucket.org/furkandev

twitter: https://twitter.com/0xfsk

mail: furkansahinkulaksiz@gmail.com

linkedIn: https://www.linkedin.com/in/frknshnklksz/

--

--