Dizi yapısının belki de en büyük artısı, eleman erişim ve eleman güncellemenin sabit maliyetli olmasıdır; hangi indisteki eleman söz konusu olursa olsun, doğrudan erişim sayesinde her iki işlem de sabit (ve yüksek) hızlı bir şekilde yapılabilmektedir. Ancak, bunu mümkün kılan "elemanların dizinin yaratıldığı noktada ayrılan ardışık bellek konumlarına yerleştirilmesi" özelliği, yapının statik olması sonucunu da doğurur. Yani, dizinin yaratılması sonrasında uzunluğunun değiştirilmesi olanaklı değildir. Dolayısıyla, kullanım sırasında bir taşmanın olmaması için, yer ayrımının olası en yüksek eleman sayısı düşünülerek yapılması gerekir. Bunun sonucu da kimi zaman, ayrılan yerin tümünden yararlanılmadığından, belleğin verimsiz kullanımı olacaktır.
Bu olumsuzluğu gidermek için, dizi tutacağını yeri yeni ayrılmış ve baş kısmında eski dizi nesnesinin kopyasına sahip daha büyük bir dizi nesnesini gösterecek şekilde güncellemeyi düşünebilirsiniz. Java karşılığı aşağıda sunulan bu yaklaşımın iki zaafı vardır: i) dizinin büyümesi için gerekli olan kod programcı tarafından yazılmak zorundadır ve ii) programcı dizinin ne zaman büyümesi gerektiğini bilmelidir.
Değineceğimiz son arayüzler,
İletilere öznitelikleri işlemlemek için yararlanabileceklerimizle başlayalım. Argümansız
Sınıflarını yazarken titiz davrananlara tanıdık gelecek iletilerle devam edelim.
İçerik sorgulama ve güncelleme iletilerinin anlatımına geçmeden önce, eleman ekleme ve silme iletilerinin kullanımı sırasında akılda tutulması yararlı olacak şu noktayı hatırlatalım: ekleme sırasında, eklemenin yapıldığı indis sonrasındaki elemanlar sona doğru, silme sırasında, silinen elemanın sonrasındaki elemanlar öne doğru kaydırılacaktır. Dolayısıyla, bu işlemlerin etkilediği indis kap önlerine yaklaştıkça maliyet artacaktır.
İki uyarlaması bulunan
Sorgulama amacıyla yararlanabileceğimiz bir diğer ileti çifti, yegâne argümanlarında geçirilen nesnenin veya
Hedef nesneden eleman silme işlemi, argüman olarak indis veya silinmesi istenen değere eşit bir nesne bekleyen iki
Çoklu silme,
Eleman güncellemede kullanılan
Değineceğimiz bir sonraki ileti grubu, gezici nesnesi döndürenler.1
Bu olumsuzluğu gidermek için, dizi tutacağını yeri yeni ayrılmış ve baş kısmında eski dizi nesnesinin kopyasına sahip daha büyük bir dizi nesnesini gösterecek şekilde güncellemeyi düşünebilirsiniz. Java karşılığı aşağıda sunulan bu yaklaşımın iki zaafı vardır: i) dizinin büyümesi için gerekli olan kod programcı tarafından yazılmak zorundadır ve ii) programcı dizinin ne zaman büyümesi gerektiğini bilmelidir.
int[] dizi = {0, 1, 4}; ... dizi = Arrays.copyOf(dizi, dizi.length * 2); dizi[3] = 9;İşte bu yazımızda, Java kitaplığı tarafından değişken uzunluklu dizileri desteklemek amacıyla sağlanan
java.util.Vector
sınıfına bakacağız. Vector
nesnelerinin sahip olması gereken öznitelikler ile başlayalım: sığa, eleman sayısı ve sığa artımı. Sığa, bir Vector
nesnesinin büyümeye gerek olmadan kaç değer tutabileceğini gösterirken; eleman sayısı, sorgulandığı anda Vector
'de kaç değer tutulmakta olduğunu gösterir. Vector
nesnesinin dolması sonrasında—yani eleman sayısı ile sığanın eşit olması halinde—yeni bir elemanın eklenmesi istenecek olursa, taşmayı önlemek adına Vector
nesnesinin sığası, sığa artımı özelliğinde öngörüldüğü miktarda artırılarak yeni eleman için yer açılır ve ekleme yapılır.Yapıcılar
Vector
nesnelerine gönderilebilecek iletilere geçmeden önce, sağlanan yapıcılara bir göz atalım. Aşağıda verilen örneklerden de görülebileceği gibi, Vector
sınıfı soysaldır; tutacaklarının tanımlandığı ve nesnelerinin yaratıldığı noktalarda ilişkin kapta tutulacak elemanların türü bilgisini tür argümanı olarak ister. Tür argümanının es geçilmesi, Vector
sınıfının J2SE 5.0 öncesinde olduğu gibi tür güvenliği olmaksızın kullanılacağı anlamına gelir.Vector<Integer> intVec1 = new Vector<Integer>(50, 10); Vector<Integer> intVec2 = new Vector<Integer>(50); Vector<Integer> intVec3 = new Vector<Integer>(); intVec1.add(1); ... Vector<Integer> intVec4 = new Vector<Integer>(intVec1); boolean aynıMı = intVec4 == intVec1; // aynıMı ← false boolean eşitMi = intVec4.equals(intVec1); // eşitMi ← true intVec4.set(0, 11); eşitMi = intVec4.equals(intVec1); // eşitMi ← falseİlk yapıcı çağrısı, başlangıç sığası 50 olan ve taşma noktalarında 10 elemanlık artımla genişletilecek bir
Vector
nesnesi yaratır. Sığa artım argümanı olarak pozitif olmayan bir değer geçirilmesi veya ikinci satırdaki yapıcı çağrısında olduğu gibi artım değerinin sağlanmaması durumunda, Vector
nesnesinin sığası her taşma noktasında ikiye katlanarak artırılacaktır. Buna göre, intVec2
tutacağı ile gösterilen nesne 51. elemanın eklenmesi istendiği noktada 100, 101. elemanın eklenmek istendiği noktada ise 200 eleman tutabilecek şekilde genişletilecektir. Benzer bir sığa artırım politikası izlenecek üçüncü satırdaki varsayılan yapıcı çağrısı, on elemanlık başlangıç sığasına sahip bir Vector
nesnesi yaratacaktır. Son satırdaki çağrı ise, kopyalayan yapıcı görevini görecek ve kendisine geçirilen Collection<E>
kategorisindeki bir kabın elemanlarını gezicinin≝ döndürdüğü sırada yaratılmakta olan Vector
nesnesine kopyalayacaktır. Kopyalamanın sonrasında eşit fakat birbirlerinden farklı (ve bağımsız) iki Vector
nesnesinin var olacağı ve bunlardan birisine yapılacak değişikliğin diğerini etkilemeyeceği unutulmamalıdır. Ayrıca, yeni yaratılan Vector
nesnesinin ilk sığası diğerinin eleman sayısı ile sınırlı olacaktır.Desteklenen Arayüzler
Vector
sınıfının dokümantasyonuna baktığınızda, desteklenen iletilerin çokluğu gözünüzü korkutabilir. Ancak, bunların bir bölümünün bir diğer ileti ile eşdeğer olduğu, bir bölümünün ise bildiğiniz sınıflardan tanıdık geleceği düşünüldüğünde, işimizin o kadar da zor olmadığı görülecektir. Ne de olsa, aşağıda verilen soy ağacına sahip olan Vector
sınıfı, gerçekleştirilen ortak arayüzler ve kalıtlanılan ortak üstsınıflar nedeniyle Veri Kapları Çerçevesi'ndeki diğer sınıflarla büyük benzerlikler gösterir.Iterable
arayüzü ile başlayalım. Genelde programcı tarafından doğrudan kullanılmayacak olan iterator
iletisini içeren bu arayüz, derleyiciye söz konusu sınıfın gezici for
döngüsü ile [baştan sona] dolaşılabileceğini garanti eder. Mesela, aşağıdaki kod parçası, hiç gezici nesne yaratıp, bu nesneye ileti göndermek zahmetine katlanmadan intVec4
'in gösterdiği kaptaki tüm elemanların kareköklerini standart çıktıya basar.for (Integer eleman : intVec4) System.out.println(Math.sqrt(eleman));Gerçekleştirilen arayüzler listesindeki
Cloneable
, Vector
nesnelerinin kopyalarının çıkarılabileceği anlamına gelir. Object
sınıfındaki clone
metodunun gösterge arayüz≝ olan Cloneable
'ı gerçekleştiren sınıflarda özel bir biçimde ezilmesiyle yerine getirilen sözleşme sonucunda, clone
iletisi Object
türlü bir tutacakla dahi gönderilebilir ve yaratılan kopya Object
tutacağı aracılığıyla döndürülür. Dolayısıyla, yeni kopyaya Vector
sınıfına has iletiler gönderilmesi öncesinde sonucun biçimlendirilmesi gerekecektir. Ayrıca; işlem sırasında elemanları tutan kap kopyalandığı için hedef nesne ile çıkarılan kopya ayrı nesneler olacaktır. Bu, nesnelerin atıflarının (tutacak) geçirildiği Java'da, değer geçirme yönteminin benzetimi için kullanılabilir.... public void mtt(Vector<Integer> vec) { Vector<Integer> vecKopya = (Vector<Integer>) vec.clone(); // vecKopya'yı kullan. ... } // void mtt(Vector<Integer>) sonu
java.util.RandomAccess
de gösterge arayüz olup herhangi bir ileti içermez. İşaret görevini gören bu arayüzün varlığı, kimi zaman doğrudan erişimli kimi zamansa doğrudan erişimli olmayan kapları işleyerek işini gören soysal kod parçalarında, doğrudan erişimliliğin avantajlarından yararlanılabilecek noktalarda bu özelliğin kullanılmasıyla performansın artmasını olanaklı kılar. Bunun için, nesnenin RandomAccess
arayüzünü destekleyip desteklemediğinin aşağıdaki gibi denetlenmesi yeterli olacaktır.... if (kap instanceof RandomAccess) { // Doğrudan erişimin avantajlarından yararlan. ... } else { ... } ...Desteklenen gösterge arayüzlerden bir diğeri olan
java.io.Serializable
, Vector
nesnelerinin disk dosyası, ağ gibi çevre aygıtlara dışsallaştırılıp, aynı aygıtlardan içselleştirilebileceğini gösterir. Bu, Vector
nesnelerinin java.io.ObjectOutputStream
türünden akak≝lara writeObject
ile yazılıp, java.io.ObjectInputStream
türlü akaklardan readObject
ile okunabileceği anlamını taşır.Değineceğimiz son arayüzler,
Vector
nesnelerini ekleme, güncelleme, silme, sorgulama gibi işlemlerle manipüle eden java.util.Collection
ve java.util.List
arayüzleridir. Collection
, Veri Kapları Çerçevesi'ndeki kapların pek çoğu için geçerli olan işlemlerin genel sözleşmelerini sağlar. Bu yapılırken, ne çeşit bir kabın ele alındığı konusunda—aynı değerden iki veya daha fazlası tutulabilir mi, uygulanan işlemlerin sıra ve sonucuna göre elemanların yapı içindeki sırası bilinebilir mi?—bir varsayımda bulunulmaz. Buna karşılık, List
arayüzü, kaptaki değerlerin tekrarlanabileceğini ve uygulanan işlemlerin sonrasında yapı içindeki sıranın bilinebileceğini varsayar.Desteklenen İletiler
İletilere öznitelikleri işlemlemek için yararlanabileceklerimizle başlayalım. Argümansız
size
ve capacity
iletileri, sırasıyla, hedef nesnenin kaç eleman içerdiğini ve sığasını döndürürken, isEmpty
boşluk yüklemi olarak görev görür. Hedef nesnenin içeriğini eleman sayısına eşit sığalı bir kaba koyarak bellekten tasarruf sağlayan trimToSize
, eleman sayısının artmayacağından emin olduğumuz durumlarda kullanılabilir. Sığanın değiştirilmesine yarayan bir diğer ileti olan ensureCapacity
, hedef nesneyi argümanda geçirilen değere eşit veya daha büyük bir sığaya sahip olacak şekilde değiştirir. Bu işlemin icra edilmesi esnasında, hedef nesne yaratılırken sağlanan/varsayılan sığa artım değerinden yararlanılacaktır. Son olarak, setSize
, hedef nesnedeki eleman sayısı özniteliğini günceller. Bu iletinin kullanımında dikkat edilmesi gereken bir nokta, geçirilen argümana göre eleman sayısının artması gibi azalmasının da mümkün olduğudur: argümanın ileti gönderimi anındaki eleman sayısından büyük olması eleman sayısının null
değerine sahip elemanlar eklenerek artırılmasına neden olurken, küçük olması kabın sonuna doğru bazı değerlerin kırpılmasına, yani eleman sayısının azaltılmasına neden olacaktır.Sınıflarını yazarken titiz davrananlara tanıdık gelecek iletilerle devam edelim.
equals
ve toString
, beklendiği gibi, sırasıyla, eşitlik denetimi ve hoş yazım işlemlerini karşılarken hashCode
, hedef nesneyi kıyarak özet değeri görevini gören bir int
döndürür. Bilmeyenler için, bunun kulağa geldiği kadar vahşi bir şey olmadığını, nesneyi değiştirerek zarar vermediğini söyleyelim ve ne işe yaradığını biraz açalım. Potansiyel olarak pek çok eleman içerebilecek kapların eşitlik denetimi oldukça uzun bir zaman alabilir; ilk elemandan başlayarak eşit olmayan eleman çiftine kadar kontrol edilen iki kabın aynı indislerdeki elemanları birbirleriyle karşılaştırılır. Bu, eleman sayısı ile doğru orantılı olarak artan oldukça pahalı bir işlemin söz konusu olduğu anlamına gelir. Maliyet kap içeriğinin özeti olarak bir değerin tutulması ile azaltılabilir; kap değiştikçe—bu, eleman ekleme, güncelleme ve eleman silme ile mümkün olabilir—özet değer yeniden hesaplanır. Bir diğer kap ile eşitlik denetiminin yapılması istendiği durumlarda ise, önce eleman sayıları sonra ise hashCode
ile öğrenilebilecek özet değerler karşılaştırılır. Özet değerler eşit değilse, kaplar da eşit değildir; aksi takdirde, eşitsiz kapların özetlerinin eşit olması olanaklı olduğundan, yukarıda bahsettiğmiz pahalı yöntemle eşitlik denetimi yapılır.public class Vector<E> extends AbstractList<E> implements List<E>, Cloneable, RandomAccess, Serializable { ... public boolean equals(Object sağ) { Vector<E> sağTaraf = (Vector<E>) sağ; if (size() != sağTaraf.size()) return false; if (hashCode() != sağTaraf.hashCode()) return false; // Kapları dolaşarak karşılıklı elemanları denetle. ... } // boolean equals(Object) sonu ... } // Vector<E> sınıfının sonu
Vector
sınıfı, içerik güncellenmesi ve sorgulanması için pek çok ileti sunar. Ancak, kimi iletilerin bir diğer Java sürümünde eklenen eşdeğerleri bulunduğu için, ileti listesini anlamlandırmak işinin altından kalkmak görünenden kolay olacaktır. Bu gruba giren iletilerden aşağıdaki tablonun ilk sütununda verilmiş olanları kullanmanız daha akıllıca olacaktır. Zira, Collection
ve List
arayüzlerinde tanımlanmış olan bu iletilerin kullanımı, Vector
yerine Veri Kapları Çerçevesi'ndeki diğer sınıfların kodunuzda değişiklik yapılmadan kullanılmasını sağlayacak ve yeniden kullanımı olanaklı kılacaktır. add(e) | addElement(e) | e 'yi kabın sonuna ekler |
add(i, e) | insertElementAt(e, i) | e 'yi i indisli konuma ekler |
clear() | removeAllElements() | Hedef nesneyi boşaltır |
get(i) | elementAt(i) | i indisli elemanı döndürür |
remove(e) | removeElement(e) | e ile eşit ilk konumdaki değeri siler |
remove(i) | removeElementAt(i) | i indisli elemanı siler |
set(i, e) | setElementAt(e, i) | i indisli elemanı e ile değiştirir ve eski değeri döndürür. |
İçerik sorgulama ve güncelleme iletilerinin anlatımına geçmeden önce, eleman ekleme ve silme iletilerinin kullanımı sırasında akılda tutulması yararlı olacak şu noktayı hatırlatalım: ekleme sırasında, eklemenin yapıldığı indis sonrasındaki elemanlar sona doğru, silme sırasında, silinen elemanın sonrasındaki elemanlar öne doğru kaydırılacaktır. Dolayısıyla, bu işlemlerin etkilediği indis kap önlerine yaklaştıkça maliyet artacaktır.
İki uyarlaması bulunan
add
, hedef nesneye yeni eleman eklemeye yarar. Bu iletilerden, tek argümanlı olanı, argümanındaki değeri Vector
nesnesinin sonuna ekleyip true
döndürürken, iki argümanlı olanı, ikinci argümandaki değeri ilk argümanda belirtilen indisteki konuma ekler. Eklemenin toptan yapılması istendiğinde, bir döngü veya pek çok add
iletisi kullanmaktansa addAll
iletisi kullanılabilir. add
iletisine koşut iki uyarlaması bulunan bu ileti, kendisine geçirilen Collection
arayüzünü destekleyen kabın içindeki elemanları gezicisinin döndürdüğü sırada hedef nesneye ekler.get
iletisi, yegâne argümanında belirtilen indisteki elemanı döndürür. Bu iletinin özel kullanımları için, sırasıyla ilk ve son elemanları döndüren firstElement
ve lastElement
iletileri tercih edilebilir. Ancak, kodun yeniden kullanım kaygıları ağır basıyorsa, List
arayüzünde tanımlanmış get
'i yeğlemek daha yerinde olacaktır. Bir grup ardışık elemanın döndürülmesi ise, List
arayüzü türündeki tutacakla gösterilen bir Vector
nesnesi döndüren subList
iletisi ile olanaklıdır. Bu ileti, hedef nesnenin ilk argümandaki indisten başlayıp ikinci argümandaki indisin bir öncesinde sonlanan dilimini döndürür.indexOf
ve lastIndexOf
iletileri de sorgulama amacıyla kullanılablir. get
verilen bir indisteki elemanı döndürürken, indexOf
ve lastIndexOf
eleman türündeki bir değer alıp bu değerin hedef nesnede geçtiği indisi döndürür. İki uyarlaması var olan indexOf
, tek argümanlı kullanılacak olursa argümanda geçirilen nesnenin bulunduğu ilk indisi döndürürken, iki argümanlı kullanılırsa, ilk argümanda geçirilen nesnenin ikinci argümanda belirtilen indisten sonra bulunduğu ilk yerin indisini döndürür. Benzer şekilde; lastIndexOf
, tek argümanlı kullanılacak olursa argümanda geçirilen nesnenin bulunduğu son indisi döndürürken, iki argümanlı kullanılırsa, ilk argümanda geçirilen nesnenin ikinci argümanda belirtilen indisten önce bulunduğu ilk yerin indisini döndürür. Aranan nesnenin hedef nesnede bulunmaması kullanıcıya -1 döndürülerek bildirilecektir.Sorgulama amacıyla yararlanabileceğimiz bir diğer ileti çifti, yegâne argümanlarında geçirilen nesnenin veya
Collection
arayüzünü destekleyen kaptaki elemanların hedef nesnede geçip geçmediğinin yanıtını veren contains
ve containsAll
yüklemleridir.Hedef nesneden eleman silme işlemi, argüman olarak indis veya silinmesi istenen değere eşit bir nesne bekleyen iki
remove
iletisi ile karşılanır. İndis alan uyarlama istenen konumdaki elemanı silip sonucu olarak döndürürken, diğer ileti argümanındaki nesnenin hedef nesne içinde, varsa, geçtiği ilk noktadaki elemanı siler ve silmenin gerçekleşmesi durumunda true
, aksi halde false
döndürür.Çoklu silme,
removeAll
, retainAll
veya clear
iletilerinden biri kullanılarak yapılabilir. removeAll
, argümanındaki Collection
arayüzünü destekleyen kap içindeki elemanların hedef nesnedeki bütün kopyalarını silerken, retainAll
argümandaki kabın elemanlarının hedef nesnedeki tüm kopyalarının korunması ve geri kalan elemanların silinmesini sağlar. Her iki ileti de, işleyişleri sırasında hedef nesneyi değiştirecek olurlarsa true
, aksi takdirde false
döndürür. Son olarak, clear
komutu, hedef nesnedeki tüm elemanları siler.Eleman güncellemede kullanılan
set
iletisi, ilk argümanında sağlanan indisteki elemanı ikinci argümandaki değer ile değiştirir ve değişim öncesindeki eleman değerini sonucu olarak döndürür.Değineceğimiz bir sonraki ileti grubu, gezici nesnesi döndürenler.1
listIterator
adlı bu iletiler, hedef nesneyi çift yönlü dolaşıp, güncellememizi sağlayan ListIterator
türlü bir gezici nesne döndürür.2 Bunlardan argümansız olanı, dolaşmaya hedef nesnemizin ilk indisinden başlarken, tek argümanlı olanı, dolaşmaya argümanda belirtilen indisten başlar. İstenen yere konuşlanılmasını takiben, gezici nesneye sonraki/önceki elemanı döndürme (next
, previous
), o anki konuma ekleme (add
), o anki konumdaki elemanı güncelleme (set
) ve silme (remove
) imkanını veren iletiler gönderilebilir. Geziciye gönderilen iletiler esas etkilerini dolaşılmakta olan kap üzerinde gösterecektir. Bunu, Vector
nesnesi içindeki 3 değerine sahip elemanları gezici vasıtasıyla silen aşağıdaki kod parçasından görebilirsiniz.import java.util.ListIterator; ... ListIterator<Integer> gezici = intVec4.listIterator(); while (gezici.hasNext()) if (gezici.next() == 3) gezici.remove();Göz atacağımız son ileti grubu,
Vector
yerine bir başka yapının gerektiği veya daha uygun olduğu zamanlarda ihtiyacını duyduğumuz dönüşümü sağlar. Öncelikle, Veri Kapları Çerçevesi'nde ArrayList
, HashSet
ve LinkedList
'in de içinde olduğu pek çok sınıf, Collection
arayüzünü gerçekleştiren bir kap bekleyen yapıcıya sahiptir. Bu yapıcılar, geçirilen kabı baştan sona dolaşarak elemanları yaratılmakta olan yeni kaba eklerler. Dolayısıyla, Vector
ve diğer pek çok sınıfın Collection
arayüzünü gerçekleştirdiği anımsanacak olursa, Vector
nesnelerinin pek çok diğer türden kaba ve diğer türden kapların Vector
nesnesine çevrilmesi kolaylıkla mümkün olacaktır.Vector
nesneleri dizi nesnelerine copyInto
ve toArray
iletileri kullanılarak dönüştürülebilir. copyInto
hedef nesnenin içerdiği elemanları argümanında sağlanan dizinin içine doldururken dizinin yeterli uzunlukta olmamasını kullanıcıya IndexOutOfBoundsException
ayrıksı durumu ile bildirir. Buna karşılık, benzer imzaya sahip toArray
iletisi, argümanda geçirilen dizinin yetersiz kalması durumunda sızlanmaz ve gerekli uzunluğa sahip yeni bir dizi nesnesi yaratıp işini bu yeni dizi üstünde tamamladıktan sonra bu diziyi döndürür. toArray
'in argümansız ikinci uyarlaması da, hedef nesnenin eleman sayısına sahip bir dizi döndürerek işini görür.3-
elements
iletisiiterator
velistIterator
iletilerinin eklenmesi ile kullanımdan düşmüştür. Dolayısıyla, anlatımımız bu iletiyi kapsamayacaktır. ↑ - İletilerin adı olarak
vectorIterator
yerinelistIterator
(liste gezici) seçilmiş olması sizi şaşırtmasın. Ne de olsa baştan sona dolaşılmak tüm doğrusal veri yapıları için makul bir işlem ve bu yüzden bu işleme karşılık gelen iletilerList
arayüzüne konulmuş. ↑ - Bu iletinin
Vector
sınıfındaki gerçekleştirimiArrays.asList
metodu ile bir bütün olarak düşünülmelidir. ↑
Hiç yorum yok:
Yorum Gönder
Not: Yalnızca bu blogun üyesi yorum gönderebilir.