Sınıflarınızı Bileşke Türler-Sınıflar adlı yazıda🔎 önerilen veya diğer kaynaklarda okuduğunuz reçetelere uyarak gerçekleştirdikten sonra bile, programlarınızın sıklıkla beklentileriniz dışında davranış sergilediğini gözlemleyebilirsiniz. Bu hayal kırıklığı kimi zaman yaptığınız bir dikkatsizlikten, kimi zamansa öngöremediğiniz bir işlevselliğin yokluğundan veya eksik gerçekleştirilmiş olmasından kaynaklanır. İşte bu yazıda, tüm sınıflarda özel olarak ele alınıp gerçekleştirilmeleri konusunda karar verilmesi gereken bazı özel metotlara değineceğiz ve Kuantum Tanrısı'nın JSM'nin içine kötü niyetli cinler yerleştirdiği kuruntusundan kurtulmaya çalışacağız.
Hatadan arındırma amacıyla başvurduğunuz çıktı komutlarınızın neden anlaşılmaz şeyler yazdığını açıklamakla başlayalım. Aşağıda verilen [oldukça yetersiz]
≡ ≡
Hangi sınıfa ait olursa olsun tüm nesnelere gönderilmesi söz konusu olabilecek bir diğer ileti, hedef nesneye kendisini aynı türden bir diğer nesne ile eşit olup olmadığını denetlemesini söyleyen
Programlamaya yeni başlayıp eşitlik denetiminde tatsız bir sürpriz yaşayanların çoğu, nesne kopyalama işleminde de benzer bir sıkıntı yaşarlar. Bunun sebebi, kopyalama amacıyla ilkleme veya atamadan yararlanmaları ve bu işlemlerin nesne yerine tutacağı kopyalamasıdır. Dolayısıyla, nesneler kopyalanmayacak, paylaşılacaktır. İhtiyacını duyduğumuz şey kopyalayan yapıcı gerçekleştirimidir. Kopyalanmak istenen nesnenin türünden bir parametresi bulunan bu metot, nesne kopyalamaktan ne anlaşıldığını gerçekleştiren bir gövdeye sahip olmalıdır.
Sizi gafil avlayabilecek bir diğer metot, nesnelerin ilklenmesi esnasında derleyicinin sentezlediği kod tarafından arka planda usulca çağrılabilecek olan argümansız yapıcıdır. Programcının talebi ile çağrılmayıp derleyicinin araya girerek yapıcı metot çağrılması gerekliliğine hükmettiği durumlarda çağrıldığı için varsayılan yapıcı olarak adlandırılan bu yapıcı da sınıf gerçekleştirimi sırasında göz önünde bulundurulmalıdır. Mesela, geliştirmekte olduğunuz sınıftan kalıtlayan bir sınıfın nesnesinin yaratılması sırasında sizden habersiz bir şekilde sınıfınızın varsayılan yapıcısı çağrılacaktır. Dolayısıyla, böylesine bir senaryoyu öngörmeniz durumunda varsayılan yapıcıyı sağlamanız yerinde bir hamle olacaktır.
Sınıf gerçekleştirimi sırasında gerekliliğine karar vermemiz icap eden bir diğer işlem, sınıfımızın bir nesnesini aynı türden bir diğeri ile karşılaştırma işlemidir. Biraz kolaycı olanlarınız,
Derdimizin çaresi, Java alemindeki sınıfları karşılaştırılabilir olanlar ve olmayanlar şeklinde ikiye ayırmaktır. Aradığımız bu çözüm bize arayüz üstkavramınca sağlanan tasnifin ta kendisidir. Birbirleriyle ne kadar alakasız olurlarsa olsunlar, aynı arayüzü gerçekleştiren iki (veya daha fazla sayıda) sınıf, arayüz tutacağı vasıtasıyla görüldükleri takdirde aynı kullanılabilirliğe sahip olacaklardır; söz konusu sınıfların nesneleri kendi türlerinden bir diğer nesne ile karşılaştırılabilecek ve bu işlemin sonucunda sıraya dizilebileceklerdir. Dolayısıyla, yapmamız gereken Java platformundaki nesneler için karşılaştırılabilirlik kategorisini tanımlayan
Hoş Yazım
Hatadan arındırma amacıyla başvurduğunuz çıktı komutlarınızın neden anlaşılmaz şeyler yazdığını açıklamakla başlayalım. Aşağıda verilen [oldukça yetersiz]
Öğrenci
sınıfını kullanarak ne yapmamız gerektiğine bir bakalım.Öğrenci.java
import static java.lang.System.*; public class Öğrenci { public static void main(String[] ksa) { Öğrenci öğr1 = new Öğrenci(123, "Ayşe", "Öztürk", 3.5F); out.println(öğr1); // metodun devamı... } // void main(String[]) sonu public Öğrenci(long no, String ad, String soyad, float ort) { _ad = ad; _no = no; _ortalama = ort; _soyad = soyad; } // yapıcı(long, String, String, float) sonu public String ad() { return _ad; } public long no() { return _no; } public float ortalama() { return _ortalama; } public String soyad() { return _soyad; } private String _ad, _soyad; private long _no; private float _ortalama; } // Öğrenci sınıfının sonu
$ javac -encoding UTF-8 Öğrenci.java $ java Öğrenci Öğrenci@6e1408 ...İşaretli satır tarafından basılanın bir
Öğrenci
nesnesi olmakla birlikte, haklı olarak, çıktıda nesne içeriğine dair bilgilendirici bir şey olmadığını düşünebilirsiniz. println
(ve print
) metotlarının işleyişine bakarak bunun neden tam olarak doğruyu yansıtmadığını anlamaya çalışalım. Söz konusu metotlar herhangi bir türden—ister ilkel tür olsun isterse bileşke tür—argüman alacak şekilde aşırı yüklenmiştir. Bu metotlardan char
, char[]
ve String
argüman bekleyenlerin dışındakiler, aldıkları argümanı önce bir String
nesnesi haline dönüştürür, ardından bu katar içindeki karakterleri basarak işini görür. Bileşke türlü değerlerden karakter katarına dönüşüm, aşağıdaki denkliklerden de görülebileceği gibi, önce String
sınıfındaki valueOf
metodunu çağırarak daha sonra ise bu metottan dönüşümü yapılmak istenen nesneye toString
iletisi gönderilerek yapılır.out.println(2 + öğr1);
out.println(String.valueOf(2) + String.valueOf(öğr1));
out.println(String.valueOf(2) + öğr1.toString()));Dolayısıyla, sorumlunun
öğr1
'e toString
iletisinin gönderilmesi sonucunda çağrılan aynı adlı metot olduğunu söyleyebiliriz. Ancak, öğr1
'in üyesi olduğu Öğrenci
sınıfına bakıldığında bu ada sahip bir metodun bulunmadığı görülür. O zaman, olmayan bir metodun çağrılmasıyla hata verilmesi gerekirken neden yukarıdaki çıktı üretilmektedir? Yanıt, söz konusu metodun bir diğer sınıftan kalıtlanmış olmasında yatar. Hoş yazım gibi tüm sınıflarda bulunması beklenen bir işlevsellik çok genel bir şekilde tüm sınıfların ortak atası olan Object
sınıfındaki toString
metodunun gerçekleştirimi ile sağlanmış ve Öğrenci
sınıfının kendi özelliklerini yansıtan bir gerçekleştirim sağlamaması sonrasında derleyici tarafından bu metot kullanılmıştır. Tüm sınıfların ortak paydasından yararlanarak içi doldurulan bu metot ise nesnenin ait olduğu sınıfın adı, '@' ve nesnenin kıyım değerini birleştirerek işini görmektedir.1 Nesnelerimizin çıktı ortamına basıldığında sonucun anlaşılır olmasını istiyorsak, toString
adlı bu metodu nesnelerimizin özelliklerini düşünerek gerçekleştirmemiz gerekir. Öğrenci
sınıfı için uygun bir gerçekleştirim aşağıda verilmiştir.... public class Öğrenci { ... public String toString() { return _no + " " + _ad " " + _soyad + " " + _ortalama; } // String toString() sonu ... } // Öğrenci sınıfının sonu
Eşitlik Denetimi
Hangi sınıfa ait olursa olsun tüm nesnelere gönderilmesi söz konusu olabilecek bir diğer ileti, hedef nesneye kendisini aynı türden bir diğer nesne ile eşit olup olmadığını denetlemesini söyleyen
equals
iletisidir. Başta işin, ilkel türlü değerlerde olduğu gibi, ==
ile halledilebileceğini, aslında equals
iletisine ihtiyaç bile duyulmaması gerektiğini düşünenleriniz olabilir. Bu arkadaşlara, diziler konusunu işlediğimiz yazının🔎 aşağıya kopyaladığımız örneğiyle bunun olanaksız olduğunu hatırlatalım. Çünkü ==
tutacakların eşitliğini denetler. Bu da, eşitlik denetiminin true
sonuç üretmesinin ancak aynı nesneyi gösteren iki tutacakla mümkün olacağı anlamına gelir. Bir diğer deyişle, ==
nesnelerin aynılık denetimini yapar, eşitlik denetimini değil.String ad1 = new String("Tevfik"); String ad2 = new String("Tevfik"); System.out.println(ad1 == ad2); // => falseO zaman,
Object
sınıfında sağlanan equals
metodu da eşitlik denetimini yapsaymış keşke diyebilirsiniz. Biraz düşündüğünüzde, farklı yapıya sahip belirsiz sayıda sınıfta eşitlik kavramının birbirlerinden çok farklı olacağını anlarsınız ve bunun da toString
'de olduğu gibi sınıf gerçekleştirimcisinin müdahelesini gerektirdiğini görürsünüz. Zira, equals
iletisinin tüm sınıflar için ortak payda olarak sunulan Object
sınıfındaki metot gerçekleştirimi, ==
gibi davranır ve aynılık denetimi yapar. Yüksek performanslı bu çözümün isteklerinizi karşılamaması durumunda, aşağıdaki gibi amaçlarınıza uygun bir metodu sağlamanız yerinde olacaktır.... public class Öğrenci { ... public boolean equals(Object diğerNesne) { Öğrenci diğerÖğr = (Öğrenci) diğerNesne; return _no == diğerÖğr._no; } // boolean equals(Object) sonu ... } // Öğrenci sınıfının sonuDikkat edecek olursanız, metot gövdesinin ilk satırında parametrede geçirilen
Object
türlü tutacağı biçimlendirmek suretiyle arkadaki nesneyi bir Öğrenci
nesnesi gibi kullanmak isteğimizi bildiriyoruz.2 Böylece, biçimlendirmenin sonucu olarak döndürülen Öğrenci
türlü tutacak değerini atadığımız diğerÖğr
adlı yerel değişkeni, kaplamının (İng., scope) sonuna kadar Öğrenci
sınıfının özelliklerini yansıtacak şekilde kullanma hakkını kazanıyoruz. Bunun sebebi, Object
sınıfının tüm Java sınıflarının ortak özelliklerini tutuyor olması ve _no
altalanının bu ortak özellikler arasında bulunmaması.equals
metodunun ezilmesi yönünde bir karar verilmesi durumunda, hashCode
iletisinin de gözden geçirilmesi gerekecektir. Hedef nesnenin Hashtable
, HashMap
ve HashSet
gibi kapların altyapısında yararlanılan doğrudan erişimli veri yapılarında tutulabilmesi için kıyımdan geçirilerek tamsayı karşılığını döndüren bu iletinin Object
sınıfındaki sözleşme tanımına bakılacak olursa, eşit olan nesnelerin aynı kıyım değerine sahip olması gerektiği görülür. Dolayısıyla, aynı öğrenciyi temsil eden farklı nesnelerin eşit ilan edildiği Öğrenci
sınıfında, hashCode
metodunun da bu eşitliğin altını çizen bir biçimde ezilmesi gerekecektir. Buna göre, aynı öğrenci numarasına sahip tüm Öğrenci
nesnelerini eşit ilan eden yukarıdaki equals
metoduna koşut hashCode
gerçekleştirimi aşağıda verilmiştir.... public class Öğrenci { ... public int hashCode() { return (int) _no; } ... } // Öğrenci sınıfının sonuDikkat edecek olursanız,
long
olan numaranın int
olarak biçimlendirilmesi sonucu veri kaybı yaşanmış ve farklı iki öğrenciyi temsil eden Öğrenci
nesnelerinin aynı kıyım değerine sahip olma olasılığı belirmiştir. long
türünün int
türünden daha büyük bir değer uzayına sahip olmasından kaynaklanan bu durum, hashCode
sözleşmesini ihlal etmemektedir; istenen aynı öğrenciyi temsil eden nesnelerin eşit kıyım değerlerine sahip olmasını garanti etmektir, ayrı öğrencilerin aynı kıyım değerine sahip olmasını engellemek değil.hashCode
iletisinin kullanımı, nesnelerin kıyım tablosu temelli kaplarda tutulmasına sınırlı değildir. Eşitlik denetiminin uzun zaman alan bir işlem olması durumunda, hashCode
olumsuz yanıtın hızlı verilmesini olanaklı kılar. Örnek olarak, binlerce elemana sahip ve ön tarafları birbirleriyle aynı olan eşit uzunluklu iki liste düşünün. Listeleri dolaşıp elemanları karşılıklı olarak eşitlik denetimine tabi tutmaktansa, listelerin öncelikle kıyım değerleri karşılaştırılır. Karşılaştırmanın olumsuz sonuç vermesi, listelerin eşit olmaması anlamına geldiği için hızlı bir biçimde yanıtı bulmamızı sağlayacaktır.3 Dolayısıyla, hashCode
metodu da equals
ile birlikte ele alınmalıdır.Kopyalama
Programlamaya yeni başlayıp eşitlik denetiminde tatsız bir sürpriz yaşayanların çoğu, nesne kopyalama işleminde de benzer bir sıkıntı yaşarlar. Bunun sebebi, kopyalama amacıyla ilkleme veya atamadan yararlanmaları ve bu işlemlerin nesne yerine tutacağı kopyalamasıdır. Dolayısıyla, nesneler kopyalanmayacak, paylaşılacaktır. İhtiyacını duyduğumuz şey kopyalayan yapıcı gerçekleştirimidir. Kopyalanmak istenen nesnenin türünden bir parametresi bulunan bu metot, nesne kopyalamaktan ne anlaşıldığını gerçekleştiren bir gövdeye sahip olmalıdır.
Öğrenci
nesnelerinin kopyalanması için kullanılabilecek bir kopyalayan yapıcı gerçekleştirimi aşağıda verilmiştir.... public class Öğrenci { ... public Öğrenci(Öğrenci diğerÖğr) { _no = diğerÖğr._no; _ad = new String(diğerÖğr._ad); _soyad = new String(diğerÖğr._soyad); _ortalama = diğerÖğr._ortalama; } // kopyalayan yapıcı sonu ... } // Öğrenci sınıfının sonuDikkat edersiniz,
_ad
ve _soyad
altalanları için String
sınıfının kopyalayan yapıcısı kullanılıyor. Bu durumda, String
nesnelerinin değişmez içerikli karakter katarları tuttuğunu bilenler, haklı olarak, kopyalayan yapıcı yerine ilklemenin daha akılcı olacağını söyleyeceklerdir. Ne de olsa, değişmeyen bir şeyin kopyalanması ile paylaşılması arasında bir fark yoktur; paylaşılan nesnenin değişmesi söz konusu olmayacağı için bir tutacak yoluyla yapılan değişikliğin bir diğer tutacak yoluyla görülmesi gibi bir durum asla ortaya çıkmayacaktır. Dolayısıyla, Öğrenci
sınıfı için aşağıda verilen daha verimli gerçekleştirim de işimizi görecektir.... public class Öğrenci { ... public Öğrenci(Öğrenci diğerÖğr) { _no = diğerÖğr._no; _ad = diğerÖğr._ad; _soyad = diğerÖğr._soyad; _ortalama = diğerÖğr._ortalama; } // kopyalayan yapıcı sonu ... } // Öğrenci sınıfının sonu
Varsayılan Yapıcı
Sizi gafil avlayabilecek bir diğer metot, nesnelerin ilklenmesi esnasında derleyicinin sentezlediği kod tarafından arka planda usulca çağrılabilecek olan argümansız yapıcıdır. Programcının talebi ile çağrılmayıp derleyicinin araya girerek yapıcı metot çağrılması gerekliliğine hükmettiği durumlarda çağrıldığı için varsayılan yapıcı olarak adlandırılan bu yapıcı da sınıf gerçekleştirimi sırasında göz önünde bulundurulmalıdır. Mesela, geliştirmekte olduğunuz sınıftan kalıtlayan bir sınıfın nesnesinin yaratılması sırasında sizden habersiz bir şekilde sınıfınızın varsayılan yapıcısı çağrılacaktır. Dolayısıyla, böylesine bir senaryoyu öngörmeniz durumunda varsayılan yapıcıyı sağlamanız yerinde bir hamle olacaktır.
... public class Öğrenci { ... public Öğrenci() { } ... } // Öğrenci sınıfının sonuBu noktada, derleyicinin varsayılan yapıcı sentezleme noktasındaki yardımını bir kez daha hatırlatarak, bu desteğin programcı tarafından herhangi bir yapıcının sağlanması durumunda geçerli olmadığını yineleyelim. Bundan dolayı, yukarıdaki işlevsiz gibi gözüken yapıcı gerçekleştirimi zorunludur.
Karşılaştırma
Sınıf gerçekleştirimi sırasında gerekliliğine karar vermemiz icap eden bir diğer işlem, sınıfımızın bir nesnesini aynı türden bir diğeri ile karşılaştırma işlemidir. Biraz kolaycı olanlarınız,
toString
ve equals
'da olduğu gibi, aynı türe ait nesnelerin öncelik-sonralık sıralamasını saptayan bu işleme karşılık da Object
sınıfında bir metot bulunduğunu ve yapılması gerekenin sınıfımızın ihtiyaçlarına göre bu metodun ezilmesi olduğunu düşünebilirler. Ancak, karşıt bir örnek kolay varılan bu yargının tüm sınıflar için doğru olmadığını gösterir. Örneğin, matemetikteki küme kavramını gerçekleştirmekte olduğumuzu düşünün; kümelerin sıraya dizilmesinin, iki kümeden hangisinin daha önce/sonra geldiği sorusunun pek de mantıklı olmadığı görülecektir. Dolayısıyla, karşılaştırma işlemi hoş yazım ve eşitlik denetimi işlemlerinden farklı ele alınmalıdır.Derdimizin çaresi, Java alemindeki sınıfları karşılaştırılabilir olanlar ve olmayanlar şeklinde ikiye ayırmaktır. Aradığımız bu çözüm bize arayüz üstkavramınca sağlanan tasnifin ta kendisidir. Birbirleriyle ne kadar alakasız olurlarsa olsunlar, aynı arayüzü gerçekleştiren iki (veya daha fazla sayıda) sınıf, arayüz tutacağı vasıtasıyla görüldükleri takdirde aynı kullanılabilirliğe sahip olacaklardır; söz konusu sınıfların nesneleri kendi türlerinden bir diğer nesne ile karşılaştırılabilecek ve bu işlemin sonucunda sıraya dizilebileceklerdir. Dolayısıyla, yapmamız gereken Java platformundaki nesneler için karşılaştırılabilirlik kategorisini tanımlayan
Comparable
arayüzünü sınıfımızda gerçekleştirmektir. Bunun örneği aşağıda verilmiştir.... public class Öğrenci implements Comparable<Öğrenci> { ... public int compareTo(Öğrenci diğerÖğr) { if (_ortalama > diğerÖğr._ortalama) return 1; else if (_ortalama < diğerÖğr._ortalama) return -1; else if (_no > diğerÖğr._no) return 1; else return -1; } // int compareTo(Öğrenci) sonu ... } // Öğrenci sınıfının sonu
Comparable
arayüzü, içerdiği yegâne ileti olan compareTo
'nun Java dokümantasyonunda ifade edilen anlamına uygun bir biçimde gerçekleştirimini zorunlu koşar. Bu, dönüş değeri olarak hedef nesnenin diğer nesneden "önce" gelmesi durumda artı, "sonra" gelmesi durumunda eksi, aksi takdirde 0 döndürüleceği anlamına gelir.- Bir nesnenin kıyım değeri—yani, nesnenin altalanlarının özeti—nesneye
hashCode
iletisinin gönderilmesi ile elde edilir. ↑ - Burada herhangi bir şekilde nesnenin kopyalanması gibi bir durum olmayacaktır. Aynı nesne değişik türlü iki tutacak vasıtasıyla,
diğerNesne
vediğerÖğr
, iki farklı arayüze sahipmiş gibi kullanılacaktır. ↑ - Anlatılanın geçerli olması için, kap içeriğini değiştirmeye dönük herhangi bir işlemin başarıyla sonuçlanması sonrasında kaba dair kıyım değerinin güncellenmesi gerekir. Bu ise, içerik güncelleme işlemlerinin maliyetini olumsuz bir biçimde etkileyecektir. Dolayısıyla, önerilen yöntem daha ziyade değişmez içerikli kaplara sınırlıdır. ↑
Hiç yorum yok:
Yorum Gönder
Not: Yalnızca bu blogun üyesi yorum gönderebilir.