java.util etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
java.util etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

31 Ocak 2012 Salı

Doğrusal Veri Kapları

Bu yazımızda, Veri Kapları Çerçevesi'nce (VKÇ) desteklenen doğrusal veri yapılarına değineceğiz. Bunu yaparken izleyeceğimiz yöntem, söz konusu veri yapılarının ortak özelliklerini anlattığımız giriş bölümünü takiben, anılan kategoriye giren kapları en geniş anlamda temsil eden genel liste veri yapısının desteklediği arayüzleri açıklamak ve kapların doğrusallaştırılmaları sonrasında ardışık erişimli görüntülerini sağlayarak dolaşılmasını olanaklı kılan gezicilere bakmak olacak. Yazımızın devamında ise, değişik doğrusal yapıların farklı gerçekleştirimlerine göz atarak işimizi bitireceğiz.


Giriş


Doğrusal veri yapıları, eleman ekleme, silme ve güncelleme işlemlerinin hangi sırada uygulandığının bilinmesi durumunda, elemanların nasıl sıralanacağının bilindiği kaplardır. Örnek olarak, 1, 2 ve 3 sayılarının bir genel listenin, sırasıyla, başına, sonuna ve başına eklendikten sonra elde edilen kaptan ikinci elemanın silindiğini düşünün. Bu işlemlerin sonrasında listemiz, ne tür bir gerçekleştirim kullanılırsa kullanılsın, [3, 2] sıralamasıyla gezilecektir. Halbuki, küme veya eşlemlerde elde edilecek sıralama gerçekleştirime bağlı olarak oluşacak ve dolayısıyla kestirilemeyecektir.

Genel Liste Arayüzü: java.util.List


Doğrusal veri yapılarını en geniş anlamda temsil eden soyut veri yapısı genel listedir. VKÇ'deki List arayüzü ile karşılanan bu yapı, gerçekleştirim biçiminden bağımsız olarak bir genel listenin desteklemesi beklenen işlemleri tanımlar. Yani, ister dizi gibi doğrudan erişimli bir yapı kullanılarak gerçekleştirilsin isterse bağlaçlı bir yapı kullanılarak, tüm genel listeler List arayüzündeki işlemleri desteklemek zorundadır.


Collection'dan kalıtlanmak suretiyle List arayüzünde gözüken iletilerin başında, equals ve hashCode gelir. Bu iletilerin List arayüzüne konulmuş olmasını başta yadırgayabilirsiniz. Ne de olsa, tüm sınıfların [dolayısıyla List arayüzünü gerçekleştirecek sınıfların da] kalıtladığı Object sınıfında bu iletilere dair gerçekleştirimler sağlanmaktadır. Ancak, buradaki amaç gerçekleştirimciye bir hatırlatma yaparak onu uyarmaktır; tanımlayıcıları List arayüzünü gerçekleştirmek isteyenlere şunu demektedirler:
Tutacakları karşılaştırmak yoluyla işini gören Object sınıfındaki equals metodu, muhtemelen senin işine gelmeyecektir. Ne de olsa, bu metot eşitlik denetimi yerine aynılık denetimi yapar. Bunun bir sonucu olarak, iki listenin eşit olması ancak ve ancak aynı olmaları durumunda mümkün olacaktır; eşit içeriğe sahip iki farklı liste eşitsiz ilan edilecektir. Dolayısıyla, sen iyisi mi, equals iletisini gerçekleştirmeyi bir daha düşün. Ha bu arada; hashCode iletisini de gözden geçirmeyi unutma. Malum, birbirine eşit farklı nesnelerin kıyım değerleri de eşit olmalıdır. Bu koşulu sağlayabilmenin yolu ise hashCode ile equals'ı birlikte ele almaktan geçer. Çünkü, Object sınıfındaki equals metodunu ezip de hashCode metodunu ezmezsen, eşit kapların farklı kıyım değerleri üretmesi durumu ortadan kalkmaz.
List arayüzündeki iletilerin çoğu VKÇ'yi daha önceden kullanmış olanlara tanıdık gelecektir. Bunun başlıca sebebi, VKÇ'de desteklenen kapların bir standardizasyon çabası kapsamında birlikte ele alınmış olmasıdır.1 Doğal olarak, aynı işlemler için olabildiğince aynı ileti adları seçilmeye çalışılmış ve ilintili işlemler kategorizasyon amacıyla aynı arayüze konulmuştur. Örneğin, ne tür bir kap olursa olsun, isEmpty hedef nesnenin boş olup olmadığını denetlerken size kaç eleman içerdiğini döndürür. Benzer bir şekilde, ilişkisel olmayan veri yapıları (genel liste, kuyruk, yığıt, çift-uçlu kuyruk, küme) Collection arayüzünde belirlenen işlevselliği paylaşırlar. Mesela, ister Vector olsun isterse TreeSet, bir nesnenin hedef kapta olup olmadığı contains iletisi ile sorgulanabilir; hedef kabın sonuna add ile eleman eklenirken, hedef kaptan verilen bir nesneye eşit ilk elemanın silinmesi remove ile sağlanır. Bunlara paralel olarak, containsAll, addAll ve removeAll/retainAll iletileri, sırasıyla, sorgulama, sona ekleme ve silme işlemlerini kendilerine geçirilen kapta var olan tüm nesneleri dikkate alarak yerine getirirler. clear iletisi ise, hedef kabın tüm elemenlarını siler.

List arayüzünün Collection'dan kalıtladığı diğer iletiler hedef kabın dizi şeklindeki görüntüsünü döndüren toArray iletileridir. Kap içeriğini baştan sona dolaşılma sıralarını koruyacak şekilde diziye kopyalayan iletilerden argümansız olanı, sonucu Object dizisi olarak döndürürken, kopyalamanın yapılacağı dizinin bileşen türünü argümanındaki dizinin bileşen türünü belirleyen tür argümanıyla alan ikinci uyarlama, sonucu dönüş değerinin yanısıra, yeterli yere sahip olması durumunda, argümanda geçirilen dizinin içinde de döndürür. Argümandaki dizinin yeterli yere sahip olması halinde, dönüş değeri için ikinci bir dizi yaratılmayacak, aynı dizi nesnesi paylaştırılacaktır.

Üst arayüzlerden kalıtlanmayıp List arayüzünün eklediği doğrusal kaplara özel tüm iletiler, kendilerine sağlanan indis değerlerini kullanarak ya da sonuçlarında indis değeri döndürerek işlerini görürler. Bunlardan get/set çifti, dizi erişim işleci ([]) görevini görür: get argümanında sağlanan indisteki elemanı döndürürken, set belirtilen konumdaki elemanı ikinci argümanındaki yeni değerle güncelledikten sonra söz konusu konumdaki eski değeri döndürür. get'in tersi olarak da görülebilecek indexOf ve lastIndexOf iletileri ise, contains iletisinin boolean yerine sorgulanan nesnenin, sırasıyla, kap içindeki ilk ve son geçiş yerlerini döndüren uyarlamaları olarak düşünülebilir. Argümandaki nesnenin kap içinde bulunmaması halinde her iki ileti de -1 döndürecektir.

List arayüzüne özel add, addAll ve remove iletilerinin yukarıda anılan adaşlarından şöyle bir farkı vardır: ekleme kap sonu yerine sağlanan ekstra argümanla belirtilen konuma yapılırken, silme nesne-eleman eşitliği denetlenerek bulunan konumu değil argümandaki indisin gösterdiği konumu etkiler.

Değineceğimiz son ileti, ilk başta göründüğünden çok daha hünerli olan subList. Hedef kabın argümanlar ile sınırlanan [kapalı-açık] dilimini bir List görüntüsü olarak döndüren bu ileti, dilim işlemlerinin gerçekleştirilmesini kolaylaştırır. İstersek, bir nesnenin kabımızın belirli bir diliminde var olup olmadığını sorgulayabiliriz; istersek, aşağıda olduğu gibi, kabımızın bir bölümünü silebiliriz.
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
...
  List<Integer> ls = new LinkedList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
  ls.subList(3, 7).clear(); // ls → [0, 1, 2, 7, 8, 9]

Geziciler: java.util.Iterator ve java.util.ListIterator


List arayüzünün Collection vasıtasıyla Iterable'dan kalıtladığı düşünüldüğünde, liste elemanlarının, tıpkı diziler gibi, gezici for döngüsü ile dolaşılabileceği görülecektir. Yani, gezici nesne yaratıp başa konuşlandıktan sonra hasNext ve next iletilerinden yararlanarak dolaşmaktansa, listemizi aşağıdaki gibi dolaşmak da mümkündür ve kodun anlaşılabilirliğini artırmak adına bu yöntem yeğlenmelidir.
import java.util.List;
import java.util.ArrayList;
...
  List<ElmTürü> ls = new ArrayList<>();
  ... // ls'yi doldur]
  for (ElmTürü elm : ls) { ... }
Ön-derleme sonrasında yukarıdaki for döngüsü J2SE 5.0 öncesinde programcının yazmak zorunda kaldığı şu şablon koda çevrilecektir. Dolayısıyla, gezici for döngüsünün, dolaşılabilir kapların kullanımında yararlanılan sözdizimsel bir kolaylık olduğunu söyleyebiliriz.
import java.util.Iterator;
...
  for (Iterator<ElmTürü> gzc = ls.iterator(); gzc.hasNext();) {
    ElmTürü elm = gzc.next();
    ...
  }
Gezicinin soyutlanarak doğrudan kullanımının önüne geçilmesi nedeniyle, gezici for döngüsünün gövdesinde kap içeriğinin güncellenmesi olanaksızdır; dolaşma esnasında kabın güncellenebilmesi için gezicinin bizzat programcı tarafından yaratılması gerekir. Bu durumda, geziciye gönderilecek bir remove iletisi, next ile döndürülen son elemanı silecektir.
...
  for (Iterator<ElmTürü> gzc = ls.iterator(); gzc.hasNext();) {
    ElmTürü elm = gzc.next();
    if (...) gzc.remove();
    ...
  }
iterator'ın döndürdüğü gezicinin anılan üç iletiye sınırlı olması bazen kısıtlayıcı olabilir. Doğru ya, ardışık elemanların ilişkilerini görebilmek için kabımız içinde ileriye doğru olduğu gibi geriye doğru da dolaşmak isteyebiliriz; gezme sırasında elemanları silmek isteyebileceğimiz gibi güncellemek, öncesine veya sonrasına yeni bir değer eklemek isteyebiliriz. Bu takdirde, List tutacağı aracılığıyla gönderilebilecek listIterator iletisinin kullanımı düşünülebilir. Hedef kabın ListIterator arayüzü tutacağı ile kullandırılan bir görüntüsünü döndüren bu ileti, geziciyi kabın başına veya argümanla belirtilen konuma konuşlandırabilir. İlkleme sonrasında, gezicinin sağladığı görüntü kullanılarak altyapıdaki kap ileri/geri yönde dolaşılabilir, uygun görülen yerlere yeni eleman eklenebildiği gibi, var olan elemanlar değiştirilebilir veya silinebilir. Bu noktada, yıkıcı (güncelleyici) işlemlerin etkisini altyapıdaki kap üzerinde göstereceği unutulmamalıdır. Örneğin, aşağıdaki kod parçasında gzc ls ile gösterilen kabın ortasına konuşlanmakta ve başa doğru dolaşırken değeri 3 olan elemanları 33 olacak şekilde güncellemektedir.
import java.util.ListIterator;
...
  List<Integer> ls = new ArrayList<>();
  // ls'yi doldur...
  int orta = ls.size / 2;
  for (ListIterator<Integer> gzc = ls.listIterator(orta); gzc.hasPrevious();) {
    int elm = gzc.previous(); // Derleyici Integer→int dönüşümünü yapıyor
    if (elm == 3) gzc.set(33);
    ...
  }
Kabın paylaşılması nedeniyle, yapılan değişikliklerin hem liste hem de gezici tutacağı ile görüleceği unutulmamalıdır. Bu noktanın gözden kaçırılması, çok-izlekli programlarda—mesela, kabın bir izlekte liste tutacağı yoluyla bir diğerinde ise gezici tutacağıyla değiştirildiği durumlarda—soruna yol açabilir.

Iterator arayüzünden kalıtlayan ListIterator aşağıdaki tabloda verilen iletileri içerir. Eklemenin yapılacağı nokta aynı kapıya çıkan iki şekilde tanımlanabilir: i) hasNext'in false döndürmesi durumunda listenin sonuna, aksi takdirde next ile döndürülecek elemanın öncesine, ii) hasPrevious'ın false döndürmesi durumunda listenin başına, aksi takdirde previous ile döndürülecek elemanın sonrasına. Ayrıca, remove ve set iletilerinin en son okunmuş olanın dışında bir elemanı etkilemeyeceği ve bu iletilerin add veya bir diğer remove iletisi sonrasında bir başka okuma yapılmaksızın kullanılamayacağı unutulmamalıdır. Aksi takdirde, gönderi IllegalStateException ayrıksı durumunun firlatılmasıyla sona erecektir.

ListIterator arayüzü
İletiİşlev
hasNextListe sonu sorgulama
nextBir sonraki eleman
removeEn son okunan elemanı silme
nextIndexBir sonraki elemanın indisi
hasPreviousListe başı sorgulama
previousBir önceki eleman
previousIndexBir önceki elemanın indisi
addEkleme
setEn son okunan elemanı güncelleme

Gezici kullanımı sırasında şu husus akılda tutulmalıdır: geziciler dolaştırdıkları listenin değişebilirlik, çok/tek-izleklilik ve tür güvenlilik gibi özelliklerini korur. Buna göre, aşağıdaki kod parçasında, lsGzc vasıtasıyla değişikliğe izin verilirken, sabitLsGzc'nin aynı kabı değiştirmek amacıyla kullanılması UnsupportedOperationException ayrıksı durumuna neden olacaktır.
import java.util.Collections;
...
  List<Integer> ls = new ArrayList<>();
  // ls'yi doldur...
  List<Integer> sabitLs = Collections.unmodifiableList(ls);
  ...
  ListIterator<Integer> lsGzc = ls.listIterator();
  ListIterator<Integer> sabitLsGzc = sabitLs.listIterator();
java.util.Enumeration arayüzüne değinerek geziciler bahsini kapatalım. İşlevsel olarak Iterator arayüzünün silme özelliği barındırmayan uyarlaması olarak düşünülebilecek bu arayüzün kullanımından kaçınılmalıdır. Daha önceden tanımlanmış olup ıslah edilerek VKÇ'ye katılan Hashtable ve Vector gibi kapların bu arayüzün tutacağı ile görüntü döndüren iletileri yerine, eşdeğer bir diğer iletinin kullanılması yerinde olacaktır. Örneğin, Vector nesnelerine elements yerine iterator iletisi gönderilmelidir. Bu, VKÇ ile birlikte tanımlanan Vector benzeri (ArrayList gibi) kapların elements'i desteklemeyip iterator'ı desteklemesi nedeniyle kodun yeniden kullanılabilirliği adına daha doğru olacaktır.

Genel Liste Gerçekleştirimleri


Altyapıda yararlanılan kabın erişim özelliğine bağlı olarak List arayüzünü gerçekleştiren sınıflar, doğrudan erişimli ve ardışık erişimli olmak üzere ikiye ayrılırlar. Bu ayrım, kimi iletilerin performanslarını etkilemesi ve desteklenebilecek ekstra iletiler nedeniyle, söz konusu kapların kullanıldıkları programların çalışma hızını ve anlaşılabilirliğini etkileyecektir; hangi somut sınıfın kullanılacağına dair yapılan seçimde bu nokta göz önünde bulundurulmalıdır. Kodun yeniden kullanılabilirliğini artırmak ve yapılacak muhtemel değişikliklerin etkisini en aza indirmek adına, tutacak türü olarak arayüz ve soyut sınıf adları kullanmaya çalışılmalı, somut sınıfların kullanımı nesne yaratma noktalarına sınırlı tutulmalıdır. Tercih edilen somut sınıfın kesinleşmesinden sonra bile arayüzlerde listelenen iletiler sınıfa özel eşdeğer iletilere yeğlenmeli, sınıfa özel iletiler ancak ek getirileri olmaları durumunda seçilmelidir. Örneğin, Vector'de karar kılınsa dahi List arayüzündeki get kullanılmalıdır, Vector sınıfına özel elementAt değil. Buna karşılık, subList ve tek argümanlı indexOf iletisinin birlikte kullanımı yerine iki argümanlı indexOf iletisinin kullanımı daha anlaşılabilir olması ve [marjinal] hız farkı nedeniyle tercih edilebilir.
adlar.indexOf("Ali", 5); // adlar.sublist(5, adlar.size()).indexOf("Ali") 
Java VKÇ, gerçekleştirimcinin işini kolaylaştırmak amacıyla somut sınıfların üzerine inşa edileceği soyut sınıflar sağlar. Doğrudan erişimli bir altyapı üzerine oturtulan bir genel liste için bu soyut sınıf AbstractList iken, ardışık erişimli altyapı kullanarak aynı işe soyunanların doğru adresi, AbstractList'ten kalıtlayan AbstractSequentialList'tir. İskelet görevi gören bu sınıflar, yararlanılacak altyapının ayrıntılarına dair varsayımlarda bulunulamaması nedeniyle, kimi iletilere karşılık gerçekleştirim sağlamazken kimi iletiler için genel [ve yavaş] gerçekleştirimler sağlar. Dolayısıyla, VKÇ'ye yeni bir kap gerçekleştirimi eklemek isteyenler, soyut sınıflardan uygun olanından kalıtlayarak iskeleti ete büründürmelidir.

VKÇ'ye yeni bir kap gerçekleştirimi eklemek isteyenlerin sabit içeriklilik ve değişken uzunlukluluk özelliklerini destekleyip desteklememelerine bağlı olarak sorumlulukları da değişir. Örneğin, tüm doğrudan erişimli listeler get ve size iletilerine karşılık gelen metotları sağlamak zorundayken, listenin değişken içerikli olmasının istenmesi durumunda, AbstractList'te UnsupportedOperationException ayrıksı durumu fırlatacak şekilde gerçekleştirilen set ezilmelidir. Benzer şekilde, listenin değişken uzunluklu olması isteniyorsa, iki argümanlı add ve int argüman alan remove metotlarının da ezilmesi gerekir. Ardışık erişimli listelerin gerçekleştiriminde de aynı ayrım geçerlidir: AbstractSequentialList'ten kalıtlayıp size ve listIterator iletilerini gerçekleştirmesi beklenen geliştiriciler, listIterator'ın döndürdüğü gezicinin alacağı iletileri gerçekleştirirken sabit içeriklilik ve değişken uzunlukluluk özelliklerini hesaba katmalıdırlar. Ardışık erişimli listelerden sabit içerikli [ve dolayısıyla sabit uzunluklu] olanlar, ListIterator arayüzünde bulunan next, hasNext, nextIndex, previous, hasPrevious ve previousIndex için gerçekleştirim sağlamak zorundayken, diğer iletilerin gerçekleştiriminde UnsupportedOperationException döndürmelidirler. Buna karşılık, değişken içerikli, sabit uzunluklu listeler, anılan arayüzün add adlı iletisini de gerçekleştirmek durumundadır. Son olarak, değişken uzunluklu listeler, arayüzde bulunan tüm iletileri gerçekleştirmelidir.

Doğrudan Erişimli Listeler: java.util.ArrayList ve java.util.Vector


VKÇ'de doğrudan erişimli genel liste gerçekleştirimi sağlayan iki sınıf vardır: ArrayList ve Vector🔎. Diziden yararlanarak işini gören bu sınıflar arasında işlevsellik ve performans açısından pek fark yoktur. Ancak, yine de şu noktaların akılda tutulmasında yarar olacaktır:

  • Java'nın ilk uyarlamasından bu yana var olan Vector, diğer J2SE 1.2 öncesi kaplar gibi, çok-izlekli iken ArrayList tek-izleklidir. Dolayısıyla, ArrayList türlü kapların çok-izlekli bir programda kullanılması durumunda, farklı izleklerden eşzamanlı erişimin sebep olabileceği tutarsızlıkları önlemek adına bazı kod bölgelerinin synchronized bloğu içine alınarak çok-izlekli hale getirilmeleri gerekir. Buna karşılık, kamuya açık tüm metotları synchronized olan Vector sınıfının tek-izlekli veya erişim serileştirme gereksiniminin düşük olduğu çok-izlekli programlarda kullanılması, çalışma hızının gereksiz yere yavaşlamasına neden olacaktır. Bu gibi durumlarda, ArrayList türlü bir kabın kullanımı ve gerekli noktalarda Collections sınıfındaki synchronizedList metodu ile döndürülen görüntü üzerinde işlem yapılması da bir çözüm olarak hesaba katılmalıdır.2
  • Her iki sınıf da, eleman sayısının artmasını gerektirecek bir işlem sırasında sığanın otomatik olarak büyümesini sağlar. Ne var ki, Vector sınıfında ilişkin kabın yaratılması esnasında sağlanacak bir argüman ile sığa artımına müdahale edilebilirken, ArrayList sınıfında böyle bir olanak yoktur.

Yukarıda da söylediğimiz gibi, her iki sınıf da benzer performansa sahiptir. get, set, size, isEmpty, iterator ve listIterator iletileri sabit zamanlı performans sergilerler. Ayrıca, kabın sığa artımına ihtiyaç duyduğu durumlarda yaşanan düşüşe karşın, kap sonuna eleman ekleyen add iletisi de ortalamada sabit zamanlı bir performans gösterir. Buna karşılık, diğer iletilerin arama temelli olanları, kap içeriğini dolaşmak zorunluluğu nedeniyle doğrusal veya daha pahalı bir performans sergileyecektir.

Kısıtlı Listeler: java.util.PriorityQueue ve java.util.ArrayDeque


C'den başka programlama dili bilmeyenlerin yaydığı bir efsanedir: liste denince akla bağlaçlar, bağlaç denince ise göstericiler gelir. Eğer bu bedbahtlardansanız, olasıdır ki yazımızın bağlaçlı listelerle başlamamış olması sizi şaşırtmıştır. Size kötü bir haber daha verelim, genel liste arayüzünün bir altkümesine sınırlı işlevsellik sunan kısıtlı listelere değinmeden bağlaçlı listelere geçmeyeceğiz.

VKÇ'de desteklenen ve tür sıradüzeni aşağıda verilen kısıtlı liste çeşitlerini anımsayarak başlayalım. Üç temel işlemin de üst olarak nitelenen aynı liste ucunu etkilediği bir kısıtlı liste olan yığıt (İng., stack), herhangi bir noktada çıkarılacak (veya sorgulanılacak) ilk elemanın en son eklenen eleman olması nedeniyle son giren ilk çıkar listesi olarak da adlandırılır. Bir diğer kısıtlı liste, gerçek yaşamdaki aynı adlı kavramı temsil eden ve uygarlıktan nasibini almış herkesin takdir edeceği nedenlerden ötürü ilk giren ilk çıkar listesi olarak adlandırılan kuyruktur (İng., queue). Kuyruğun elemanları arasında bir sıralamanın istenmesi halinde—yani, kuyruğun belirli bir ölçüte göre oluşturulması istendiğinde—ekleme işlemi kuyruğun sonu yerine herhangi bir yerine yapılabilir. Böylesine kuyruklara, öncelikli kuyruk (İng., priority queue) denir. Son olarak, temel işlemlerin aynı listenin her iki ucuna da uygulanabildiği kısıtlı listelere çift-uçlu kuyruk adı verilir.


Yukarıdaki şeklin incelenmesi kuyruk ve yığıt yapılarının VKÇ tarafından desteklenmediği kanısını yaratabilir. Java uzmanı olanlarınız ise, aslında bu desteğin var olduğunu, sorunun şeklin eksik olmasında yattığını iddia edeceklerdir. Her iki görüşte de haklılık payı olduğunu, duruma daha sonra açıklık getirileceğini söyleyelim ve Queue arayüzünün açıklaması ile devam edelim. Collection'dan kalıtlayan bu arayüz, aynı işlemin istisnai durumlarda iki farklı biçimde davranması nedeniyle, aşağıdaki tabloda listelenen altı iletiyi içerir. Sabit uzunluklu, dolu bir kuyruğa add ile ekleme yapılması IllegalStateException ayrıksı durumuna neden olurken, aynı kuyruğa offer ile eleman eklenmeye çalışılması, C fonksiyonlarını andırırcasına, null değerinin döndürülmesine neden olacaktır. Sorgulama ve çıkarma işlemleri de boş bir kuyruğa uygulanmaları halinde benzer bir davranış sergileyecektir.

Kuyruk: İlk giren ilk çıkar listesi
İşlemİleti (Queue)Başarısızlıkta döndürülen değerDenk ileti (Deque)
EklemeaddIllegalStateExceptionaddLast
offerfalseofferLast
SorgulamaelementNoSuchElementExceptiongetFirst
peeknullpeekFirst
ÇıkarmaremoveNoSuchElementExceptionremoveFirst
pollnullpollFirst

Queue arayüzüne dayalı kuyruk gerçekleştirimini kolaylaştırmak amacıyla sağlanan AbstractQueue'dan kalıtlayan sınıflara baktığımızda, değişken uzunluklu öncelikli kuyruk yapısını ikili yığın (İng., binary heap) kullanarak gerçekleştiren PriorityQueue sınıfını görürüz. Dizi üzerine ikili ağaç benzeri bir yapı oturtarak işini gören bu gerçekleştirim stratejisi sayesinde, temel işlemlerden sorgulama sabit zamanda sonuç verirken, ekleme ve çıkarma logaritmik zaman almaktadır. Öncelikli kuyruğun eleman sayısını öğrenme, boşluk yüklemi ve gezici yaratma da sabit zamanda mümkün olmaktadır. Buna karşılık, geri kalan iletiler doğrusal zaman gerektirmektedir.

PriorityQueue sınıfının yapıcılarına bakıldığında iki nokta dikkat çeker. Öncelikle, değişken uzunluklu olması hasebiyle taşmaması için büyütülen kuyruklar, istenildiği takdirde yaratıldıkları noktada belirli bir ilk sığaya sahip olacak şekilde yaratılabilir. Kuyruğun eleman eklenerek büyümesi sırasında altyapıdaki dizinin büyütülmesinden kaynaklanacak performans kaybını azaltacak bu parametre, sabit uzunluklu kuyruklarda yer ve zaman kayıplarını sıfıra indirecektir. Dikkatimizi çeken ikinci husus, iki argümanlı yapıcıdaki ikinci argümanın Comparator türlü olmasının hatırlattığı ayrıntıdır: öncelikli kuyruk içine konulmak istenecek nesnelerin, hangi sınıftan olurlarsa olsunlar, karşılaştırılabilir olmaları gerekir. Durup düşünüldüğünde bu önkoşulun çok doğal olduğu görülecektir: karşılaştırılamayan nesneler sıraya dizilemezler!3 Dolayısıyla, nesnelerin Comparable arayüzünü gerçekleştiren bir sınıfa ait olması veya nesneleri karşılaştırmayı bilen bir karşılaştırıcı nesnenin fazladan sağlanması zorunludur.

Gelelim ortalıkta gözükmeyen yığıt ve kuyruk gerçekleştirimlerinin nerede olduklarına. Bunun için, çift-uçlu kuyruk yapısının tanımlandığı Deque arayüzünü gerçekleştiren ArrayDeque sınıfına bakmak yetecektir. Çünkü, VKÇ'de bir kabın yığıt veya kuyruk gibi davranması, bu yapılara uyumlu iletilerin ArrayDeque nesnelerine gönderilmesiyle sağlanır.4
import java.util.Deque;
import java.util.Queue;
import java.util.ArrayDeque;
...
  Deque<Integer> yığıt = new ArrayDeque<>();
  Queue<Integer> kuyruk = new ArrayDeque<>();
Örneğimizdeki asimetrik kullanım dikkatinizi çekmiştir: tutacak türü olarak, kuyruk için Queue arayüzü kullanılırken, yığıt için çok-uçlu kuyruğa dair Deque arayüzü kullanılmaktadır. Dolayısıyla, kuyruk adlı kap kuyruklara özel iletilerle kullanılacakken, yığıt'ın kullanımında seçtiğimiz tanımlayıcı adı aldatıcı olacaktır. Zira, söz konusu tutacak Deque arayüzünde tanımlı olan ve aşağıdaki tabloda sağlanan yığıt iletilerinin yanısıra diğer tüm iletilere de izin verecektir.

Yığıt: Son giren ilk çıkar listesi
İşlemİletiBaşarısızlıkta döndürülen değerDenk ileti
EklemepushIllegalStateExceptionaddFirst
falseofferFirst
SorgulamapeeknullpeekFirst
NoSuchElementExceptiongetFirst
ÇıkarmapopNoSuchElementExceptionremoveFirst
nullpollFirst

Bu noktada, bazılarınız, Java'nın ilk uyarlamasından bu yana var olan Stack sınıfı nerelerde, diye soruyor olabilir. Hemen yanıtlayalım: eskiden olduğu yerde, emrinize amade sizi bekliyor. Ama, sakın ola ki, sırf adı yığıt sözcüğünün İngilizce karşılığı olduğu için bu sınıfı kullanmanın daha iyi bir fikir olduğunu düşünmeyin. Çünkü, geliştiricilerin, muhtemelen işi aceleye getirmeleri nedeniyle, Vector sınıfından kalıtlayarak gerçekleştirdiği bu sınıf, yığıt kavramıyla ilgili ilgisiz çok sayıda iletiyi desteklemektedir. Mesela, aşağıdaki kod parçasında yaratılan kap, yığıta dair iletilerin yanısıra, Vector sınıfının desteklediği diğer iletileri de, doğrudan erişimliler dahil olmak üzere, itiraz etmeden kabul eder.
import java.util.Stack;
...
  Stack<Integer> yığıt = new Stack<>();
  // List arayüzündeki tüm iletiler yığıt'a gönderilebilir!
Geldiğimiz noktada, üç farklı kısıtlı liste için sağladığı destek ve söz konusu yapıların farklı terminoloji kullanması nedeniyle, Deque arayüzünü (ve gerçekleştirimcisi ArrayDeque sınıfını) anlamakta zorluk çekiyor olabilirsiniz; bazı işlemlerin istisnai durumlarda iki değişik şekilde tepki vermesi işi daha da kötüleştirebilir. Ancak ürkmeyin, aşağıdaki tablonun yorumu sizi işinizin düşündüğünüz kadar zor olmadığına inandıracaktır.

Çift-uçlu kuyruktan yararlanarak kuyruk ve yığıt
Denk iletiler
C tarzı dönüşAyrıksı durum
KuyrukEklemeaddaddLast
offerofferLast
SorgulamaelementgetFirst
peekpeekFirst
ÇıkarmaremoveremoveFirst
pollpollFirst
YığıtEklemepushofferFirstaddFirst
SorgulamapeekpeekFirstgetFirst
ÇıkarmapoppollFirstremoveFirst

Çift-uçlu kuyruklarda üç temel işlemin—ekleme, çıkarma (silme) ve sorgulama (göz atma)—kabın her iki ucuna da uygulanabileceğini hatırlatarak başlayalım. Söz konusu altı işlemin istisnai durumlarda farklı tepki veren iki ileti ile karşılandığı da düşünüldüğünde, yedi tanesi tablomuzun sağ kısmında yer alan on iki iletinin gizemi çözülür. Bunlara, temel işlemlerin yığıt ve kuyruk terminolojisindeki karşılıkları olan üçüncü sütunda listelenen dokuz iletiyi de eklersek, toplamda yirmi bir iletiyi buluruz. Collection'dan gelenler ve Deque'in geri kalan üç iletisi (descendingIterator, removeFirstOccurrence ve removeLastOccurrence) ile beraber düşünüldüğünde, bu sayı başta korkutucu gelebilir. Ancak, çift-uçlu kuyruğu ne şekilde kullanacağınıza karar vermenizle birlikte yirmi bir ileti aniden yığıt için üçe, kuyruk için üç veya altıya, çift-uçlu kuyruk içinse altı veya on ikiye düşecektir.

ArrayDeque dairesel arabellek (İng., circular buffer) şeklinde kullanılan bir dizi ile gerçekleştirim sağlar. Bu sayede, temel işlemlerin her iki uca uygulanmasına karşılık gelen tüm iletiler sabit zamanda işlerini bitirirler. Aynı nedenden ötürü, temel işlemlerin toptan uygulanmaları da eleman başına sabit bir maliyet sergileyecektir. Buna karşılık, kabın taranmasını gerektiren arama iletileri doğrusal zamanlı performansa sahiptirler. Son olarak, altyapıda kullanılan dizinin yetersiz sığaya sahip olduğunun görülmesi dizinin genişletilmesine neden olacağı için, ekleme iletilerinin nadiren de olsa bazen reklam edilenden düşük bir performans sergileyebileceği unutulmamalıdır. Bu duruma düşmemek için ArrayDeque nesnesini muhtemel kullanıma uygun bir sığa ile yaratmak yardımcı olacaktır.

Bağlaçlı Liste: java.util.LinkedList


VKÇ'nin genel liste gerçekleştirimi olarak sunduğu ikinci seçenek, List ve Deque arayüzlerini ardışık erişimli bir yapı kullanarak gerçekleştiren LinkedList sınıfıdır. Baş ve son düğümlerin tutacaklarını barındıran çift bağlaçlı bir yapıya sahip bu sınıfta ekleme ve çıkarma, liste içindeki bağlaçların güncellenmesiyle tamamlanır. Bunun doğal bir sonucu olarak, herhangi bir elemana erişmenin tek yolu, get/set gibi iletiler kullanılıyor olsa bile, ardışık erişimdir; istenen bir elemana erişilmesi için, söz konusu eleman öncesindeki/sonrasındaki tüm elemanların ziyaret edilmesi zorunludur. Dolayısıyla, doğrudan erişim üzerine kurulu ikili arama gibi algoritmalarda bu sınıf yerine ArrayList veya Vector gibi bir sınıfın kullanılması daha mantıklı olacaktır. Buna karşılık, araya eklemenin sık yapıldığı ve ardışık erişime mecbur olunan durumlarda—mesela, ekleme sıralaması (İng., insertion sort) algoritmasının gerçekleştiriminde—seçimin LinkedList'ten yana olması gerekir. Çünkü, bağlaçlı bir yapı kullanılarak gerçekleştirilen bir kabın ortasına yapılan eklemeler/çıkarmalar doğrudan erişimli kapların neden olduğu gibi kaydırmanın getireceği olumsuzluktan etkilenmez. Son olarak, kabın iki ucuna erişimin sabit maliyetli olması, size yığıt ve kuyruk gibi kısıtlı listeler için LinkedList'in çekici bir seçenek olduğunu düşündürebilir.
...
import java.util.LinkedList;
...
  Deque<Integer> yığıt = new Linked<>();
  Queue<Integer> kuyruk = new LinkedList<>();
Yukarıda sağlanan çözümün ArrayDeque kullanılarak sağlanan çözümle daha sağlıklı bir şekilde karşılaştırılabilmesi için, ArrayDeque'in altyapıda kullanılan dizinin boş konumları için maliyet ödetmesine karşılık, LinkedList'in her bir eleman için fazladan iki bağlaç tutmak zorunda olduğu unutulmamalıdır. ArrayDeque nesnesinin duruma uygun bir ilk sığaya sahip yaratılması imkanı ile birlikte ele alındığında, bu durumun temel işlemlerin zaman maliyetini marjinal de olsa ArrayDeque lehine çevirdiği görülecektir. Dolayısıyla, yığıt ve kuyruk kullanımı için ilk önerdiğimiz yöntemin daha doğru olduğu söylenebilir.


  1. VKÇ'nin ortaya konduğu J2SE 1.2 öncesinde tasarlanıp gerçekleştirilmiş olan veri kaplarının dokümantasyonunda iki noktayı kafa karıştırıcı bulabilirsiniz. [Islah edilerek çerçeveye oturtulması olanaksız gözüken] Dictionary ve Stack sınıflarını kullanmaktan kaçınılması tavsiye edilirken, Hashtable ve Vector sınıflarında tamamıyla aynı şeyi yapan farklı adlara sahip pek çok ileti çifti göze çarpar. Her iki durumda da sebep, VKÇ'nin ortaya koyduğu standart ve söz konusu sınıfların bu standarda uyumdaki başarısı(zlığı)dır. Programcı olarak bizim payımıza düşen ise, dokümantasyonda önerildiği gibi, ilk iki sınıf yerine önerilen seçeneklerden yararlanmak ve diğer iki sınıfta standartta bulunan ada sahip iletileri kullanmaktır. Böyle bir yaklaşım, yazdığımız kodun yeniden kullanılabilirliğini artıracaktır.
  2. Metotlarının synchronized ilan edilmiş olması, Vector sınıfını tamamıyla izlek güvenlikli kılmaz. Bu şekilde sağlanan güvence metotların teker teker kullanımlarına ilişkindir; Vector sınıfındaki iki veya daha fazla sayıda metodun aynı atomik işlemin parçası olarak icra edilmesini gerektiren durumlarda, iş yine programcıya düşecektir.
  3. Aslına bakarsanız, sıralanmak istenen verinin ikillerini 0 veya 1 olmasına göre gruplara ayırarak işini gören bazı algoritmalar karşılaştırma işleminden yararlanmazlar. Ne var ki, bu algoritmaların uygulanabilirliği bazı ilkel türlere kısıtlı olduğu için, bu istisna genelleştirilerek vardığımız yargıyı boşa çıkarmak için kullanılamaz.
  4. Aslına bakacak olursanız, ayrıcalıklı kuyruktan yararlanarak yığıt ve kuyruk displinine sahip kapların oluşturulması mümkündür. Bunun için yapılması gereken, elemanların ekleme zamanlarına dair bilgi içermeleri ve bu bilginin ayrıcalıklı kuyruğun sıralama ölçütü olarak kullanılmasıdır.

2 Aralık 2011 Cuma

Hızlı Arama Zamanlı Kaplar-2

Kaldığımız yerden devam ediyoruz. Dizimizin ikinci ve son yazısında, ilk yazıyı okuyanlara tanıdık gelecek aşağıdaki kısmi sıradüzenin eşlemler için dengeli ağaç temelli gerçekleştirim sağlayan kısmına değineceğiz. Buraya ilk yazıyı okumadan yolunuz düşüp de kavramsal olarak kendinizi hazır hissetmiyorsanız, size tavsiyem ilk yazıya bir göz atıp daha sonra buraya dönmeniz.🔎

TreeMap Sınıfı


Eşlem yapısı, altyapıda anahtar bilgisine göre oluşturulan bir dengeli ağaç kullanılarak da gerçekleştirilebilir. Bu yol izlenecek olursa çözüm, eşleme gönderilen iletilerin dengeli ağaca yönlendirilmesiyle sağlanır. Bu tür ağaçların ekleme, silme ve sorgulama işlemlerini O(lgn) zamanda yapması nedeniyle, anılan işlemlerin eşlemde de O(lgn) performanslı gerçekleştirilmesi mümkün olacaktır. Bu noktadan hareketle, standart Java kitaplığındaki TreeMap sınıfı eşlem yapısını kırmızı-siyah ağaçlardan yararlanarak gerçekleştirmiştir.1

Altyapıda dengeli ağaç kullanılıyor olması, TreeMap sınıfının pek çok ekstra iletiyi düşük maliyetli bir gerçekleştirimle desteklemesini olanaklı kılar. Bunun başlıca nedeni, kök-ortada (İng., inorder) dolaşmanın ağaç içindeki bilgileri anahtara göre sıralı vermesidir. Bu sayede eşlem içeriği anahtara göre sıralı bir şekilde oluşturulabilir; ayrıca sıralama aşamasına gerek yoktur. Bunun doğal bir sonucu olarak, TreeMap sınıfı SortedMap ve NavigableMap arayüzlerinde listelenen eşlem içeriğinin uç değerleri ve dilimlerine dair iletileri de destekler. Ayrıca, Map🔎 arayüzünde tanımlanmış olan entrySet, keySet ve values iletilerinin gerçekleştirimleri de sonuçlarını anahtara göre sıralı değerlerden oluşan birer kap içinde döndürürler.

Kök-ortada dolaşmanın düğümleri anahtara göre sıralı üretebilmesi, anahtar-tutanak çiftlerinin altyapıdaki dengeli ağacın anahtar değerine göre belirlenen uygun düğümlerine eklenmeleri ile mümkün olur. Bu ise, eklenmekte olan anahtar değerinin eşlemde var olanların bir kısmı ile karşılaştırılmasıyla olanaklıdır. Benzer şekilde, silme ve sorgulama işlemlerinde de söz konusu anahtara karşılık gelen düğümün karşılaştırma işlemleri yardımıyla saptanıp işlemin tamamlanması mümkün olacaktır. Bütün bunlar, anahtar türünün karşılaştırılabilir olması zorunluluğunu beraberinde getirir. Yani, anahtar türü ya Comparable kategorisinde olmalıdır ya da java.util.Comparator arayüzünü gerçekleştirilen bir sınıf anahtar türüne ait nesneleri karşılaştırma desteğini sağlamalıdır. Gelin bu konuyu TreeMap sınıfının yapıcılarına açıklık getirerek anlamaya çalışalım.
...
import java.util.TreeMap;

public class Eşlemler {
  public static void main(String[] ksa) {
    ...
    Map<String, String> sözlük3 = new TreeMap<>();
    sözlüğüDoldur(sözlük3);
    System.out.println("Sözlük içeriği: " + sözlük3);
    ...
  } // void main(String[]) sonu
  ...
} // Eşlemler sınıfının sonu
Yukarıdaki kod parçasının işaretli satırındaki yapıcı çağrısı sonucunda yaratılan eşlem, karşılaştırma amacıyla, herhangi bir bilgi verilmediği için, anahtar bilgilerinin üyesi olduğu String sınıfınca gerçekleştirilen Comparable arayüzündeki compareTo iletisini kullanacaktır. Bunun sonucunda, sözlük3 adlı eşlemi standart çıktıya basan komut, sözcükleri büyük harfler önce olmak üzere alfabetik sıraya göre dizecektir. Ayrıca, bu iş yapılırken Türkçe'ye özel harfler beklediğimiz sıranın aksine sona doğru yer alacaktır. Bu sorunun çözümü, Türkçe'ye özel bir sözcük karşılaştırma sınıfı gerçekleştirmektir.
...
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;

public class Eşlemler {
  public static void main(String[] ksa) {
    ...
    Map<String, String> trSözlük =
      new TreeMap<>(new TürkçeSözcükKarşılaştırıcı());
    sözlüğüDoldur(trSözlük);
    System.out.println("Sözlük içeriği: " + trSözlük);
    ...
  } // void main(String[]) sonu
  ...
} // Eşlemler sınıfının sonu

class TürkçeSözcükKarşılaştırıcı implements Comparator<String> {
  private final static Collator trKarşılaştırıcısı =
    Collator.getInstance(new Locale("tr"));

  public int compare(String s1, String s2) {
    return trKarşılaştırıcısı.compare(s1.toLowerCase(), s2.toLowerCase());
  }
} // TürkçeSözcükKarşılaştırıcı sınıfının sonu
Yukarıdaki kod parçasının işaretli satırları, dengeli ağaç altyapılı boş bir eşlem yaratırken ağacın, ve dolayısıyla eşlemin, oluşturulması amacıyla kullanılacak karşılaştırma ölçütünün argümanda geçirilen nesne tarafından belirlendiğini söylemektedir. Bu nesnenin sınıfına bakacak olursak, sözcüklerin büyük-küçük ayrımı yapılmaksızın Türkçe alfabeye göre karşılaştırılacağını ve sıranın Türkçe'den alıştığımız şekilde oluşacağını görürüz.

TreeMap sınıfının diğer yapıcıları kopyalayan yapıcı olarak çalışır. Bunlardan yegâne argümanında SortedMap arayüzünü gerçekleştiren bir eşlem alanı, yaratılmakta olan eşlemi argümanda geçirilenin içeriği ile dolduracaktır. Birbirinden bağımsız iki eşlem oluşmasına yol açan bu işlemin sonrasında yeni eşleme uygulanacak işlemler argümandaki eşlemin kullandığı karşılaştırma ölçütü ile aynı olacaktır. Sonuncu yapıcı da buna benzer bir mantıkla çalışır. Bu sefer, kopyalamanın kaynağı olan eşlem Map arayüzünü gerçekleştiren herhangi bir eşlem olabilir. Yani, argüman olarak TreeMap nesnesi geçirilebileceği gibi, diğer altbölümlerde göreceğimiz sınıfların nesneleri de geçirilebilir ki, bu yaratılmakta olan eşlemin taklit edeceği bir karşılaştırma ölçütünün olmayabileceği anlamına gelir. Dolayısıyla, bu yapıcı çağrısının kullanımı sonrasında yeni yaratılan eşlemin içeriğinin düzenlenmesinde ölçüt olarak anahtar sınıfının compareTo iletisi kullanılacaktır.

Yaratılan eşlemin içeriğini var olan bir eşlemden alırken farklı bir ölçüt kullanmak olanaklı değil mi, diye sorabilirsiniz. Hemen sorunuzun yanıtının olumlu olduğunu söyleyelim. Bunun için yapmanız gereken, eşlemi uygun bir karşılaştırma ölçütü ile boş yaratıp putAll iletisi ile doldurmaktan ibarettir.
...
public class Eşlemler {
  public static void main(String[] ksa) {
    ...
    Map<String, String> sözlük4 =
      new TreeMap<>(new TürkçeSözcükKarşılaştırıcı());
    sözlük4.putAll(sözlük3);
    System.out.println("Sözlük içeriği: " + sözlük4);
    ...
  } // void main(String[]) sonu
  ...
} // Eşlemler sınıfının sonu

SortedMap Arayüzü


Gelelim, SortedMap arayüzünde olup TreeMap sınıfınca gerçekleştirilen iletilere. Bunlardan comparator, hedef eşlemin hangi ölçüte göre düzenlendiğini döndürür. Ancak, aklınızda olsun: bu iletinin null döndürmesi herhangi bir ölçüt kullanılmadığı anlamına gelmez; sadece, Comparable arayüzünü destekleyen anahtar sınıfının compareTo gerçekleştiriminin kullanıldığı anlamına gelir.

SortedMap arayüzünde listelenen diğer iletiler uç değer ve eşlem dilimi döndürmek suretiyle görüntü sağlayanlar olmak üzere iki grupta toplanabilir. TreeMap türlü eşlemlerin dengeli ağaçlarla temsil edilmesi sayesinde ucuz maliyetlerle gerçekleştirilebilen bu iletilerden firstKey ve lastKey, sırasıyla, hedef eşlemin ilk ve son anahtar bilgisini döndürür. İkinci gruptaki iletiler, argüman veya argümanlarında belirtilen anahtar bilgileri ile belirlenen anahtar-tutanak çiftlerinden oluşan ve SortedMap tutacağı ile gösterilen eşlem görüntüleri döndürür. Bunlardan subMap, ilk argümanı ile başlayıp ikinci argümanının hemen öncesinde biten eşlem dilimini döndürürken, headMap başlangıçtan argümandaki anahtarın öncesine kadar olan dilimi, tailMap ise argümanındaki anahtar ile başlayıp eşlemin sonuna kadar giden dilimi döndürür.
SortedMap<String, String> adanbye = trSözlük.subMap("a", "b"); // "b" hariç!
SortedMap<String, String> jyeKadar = trSözlük.headMap("j");
SortedMap<String, String> jdenSonra = trSözlük.tailMap("j");
Her üç iletinin de, görüntüleme grubundaki diğer iletiler gibi, yeni bir eşlem yaratmaktansa ileti alıcının gösterdiği eşlemi paylaştırdığı unutulmamalıdır. Bundan dolayı, yukarıdaki kodun işlenmesi sonrasında trSözlük dört farklı tutacak yoluyla değiştirilebilecektir. Mesela, adanbye tutacağı ile gösterilen dilimin herhangi bir eşlemesinin değiştirilmesi trSözlük yoluyla gösterilen eşlemi de etkileyecektir. Ancak; trSözlük yoluyla sözlüğe "eşlem" anahtarı ve anlamının girilmesi sadece jyeKadar ile temsil edilen dilimi etkileyecektir, diğerlerini değil. Ayrıca, hiçbir eşlemin geçerli olduğu dilimin dışındaki herhangi bir anahtara dair bilgiyi eklemekte kullanılamayacağı da bilinmelidir. Örneğin, jdenSonra aracılığıyla "eşlem" anahtarı ve ilişkin anlamın girilmeye çalışılması IllegalArgumentException ayrıksı durumuna neden olacaktır.


Java SE6 ile birlikte eklenen NavigableMap arayüzü, SortedMap'tekilere benzer iletiler sunar; tanımlanan işlevsellik uç değer işleme ve görüntüleme iletilerinden oluşur. Örneğin, SortedMap'ten kalıtlanan firstKey ve lastKey iletilerine ceilingKey, floorKey, higherKey ve lowerKey iletileri eklenmiştir. ceilingKey argümanındaki anahtara eşit veya ondan büyük ilk anahtar değerini döndürürken, floorKey argümanındaki anahtara eşit veya ondan küçük son anahtar değerini döndürür; higherKey ve lowerKey iletileri ise eşitlik denetimi yapmayan, sırasıyla, ceilingKey ve floorKey gibi çalışır.
String cVeyaSonrasındakiİlkSözcük = trSözlük.ceilingKey("c");
String cSonrasındakiİlkSözcük = trSözlük.higherKey("c");
String cVeyaÖncesindekiSonSözcük = trSözlük.floorKey("c");
String cÖncesindekiSonSözcük = trSözlük.lowerKey("c");
Bir diğer ileti grubu, yukarıda adını andığımız iletilerle aynı şekilde çalışmakla birlikte anahtarı döndürmektense Map.Entry türlü bir tutacak aracılığıyla gösterilen eşlemeyi sarmalayan nesneyi döndürür. Bu iletilerin adları, ilişkin iletinin adındaki Key kısmı yerine Entry koymakla elde edilebilir. İstenecek olursa, bu noktadan sonra sarmalanan nesne Map.Entry arayüzündeki iletilerle sorgulanabilir.

NavigableMap arayüzünce eklenen görüntüleme iletilerinden olan descendingKeySet, adının da haykırdığı gibi hedef eşlemdeki anahtarları bir küme içinde döndürür. Ancak, döndürülen kümenin dolaşılması anahtarları keySet'in döndürdüğünün tersi sırada verecektir. Anahtarlara dair görüntüyü döndüren bir diğer ileti olan navigableKeySet, keySet'e daha da çok benzer; iki iletinin döndürdükleri kümenin içeriği ve sırası tümüyle aynıdır. Ne var ki, keySet Set dönüş türüne sahipken, navigableKeySet NavigableSet döndürür.

Tanıdık gelecek bir diğer ileti grubu dilim döndüren görüntüleme iletileridir. NavigableMap arayüzü, SortedMap'ten kalıtladıklarına ek olarak, uç değerlerin dahil edilip edilmeyeceğini belirleyen ekstra parametrelere sahip headMap, subMap ve tailMap adlı üç ileti destekler. Söz konusu iletiler, her bir uç değeri takiben geçirilen boolean değerlerle döndürülecek dilimin uç noktalarının nasıl ele alınacağını belirler; true geçirilmesi uç noktayı sonuca katarken, false geçirilmesi dışlar. Bu iletilerin bir diğer farkı, SortedMap'ten kalıtlanan aynı adlı iletilerin SortedMap döndürmesine karşılık, NavigableMap döndürmeleridir.

Bir önceki paragrafta anılanlarla ele alınabilecek bir ileti de, hedef eşlemi döndürülen NavigableMap tutacağı vasıtasıyla ters sırada görüntülemeye yarayan descendingMap'tir. Yani, sözlüğümüzü ters sırada işlemek isteyecek olursak, yapmamız gereken aşağıdaki tanımlamadan ibarettir.
NavigableMap<String, String> trSözlük2 = trSözlük.descendingMap();
Değineceğimiz son iletiler, uç değerleri denetleyerek [var olmaları halinde] silen pollFirstEntry ve pollLastEntry iletileridir. pollFirstEntry, hedef eşlemde ilk sırada bulunan eşlemeyi yoklayıp silerken, pollLastEntry son eşlemeyi siler. Her iki ileti de sildikleri eşlemeye dair bilgiyi Map.Entry tutacağı ile döndürürken, eşlemin boş olması halinde null döndürülür.

Uzmanlaşmış Eşlemler


Veri Kapları Çerçevesi, yukarıda anlatılanlara ek olarak içerebileceği anahtarların niteliklerinden dolayı özel bir şekilde gerçekleştirilmek suretiyle daha yüksek performanslı kullanılabilecek iki eşlem sınıfı daha sunar: EnumMap ve WeakHashMap.

Anahtar türünün bir sabit listesi (İng,. enum) olması durumunda, eşlemdeki anahtar sayısı ilişkin sabit listesindeki simgesel sabitlerin sayısı ile sınırlanacaktır. Bu gibi bir durumda, anlattığımız eşlem sınıflarından birini kullanmaktansa, anahtarlarının sabit listesi değerleri olduğunu varsayarak eniyileme yapan EnumMap sınıfının kullanılması daha doğru olacaktır.

WeakHashMap sınıfı da, tıpkı HashMap gibi, kıyım tablosu temelli bir gerçekleştirim sağlar. Ancak; WeakHashMap içine konulabilecek anahtar değerleri WeakReference türünden olmak zorundadır.2 Bu ise tutanakların, eşlem kullanıcısının göndereceği remove iletilerinin yanısıra, çöp toplayıcısının vereceği bir karar sonrasında [kullanıcı kontrolunun dışında] usulca silinebileceği anlamına gelir. Henüz silinmesi istenmeyen bir tutanağın bu sondan korunması kuvvetli bir tutacak ile gösterilmesi sayesinde sağlanabilir.


  1. Kırmızı-siyah ağaçlar, B-tree ailesindeki dengeli ağaçlardan [azami] dört düğümlü olanların, ki bu ağaçlara tepeden aşağıya 2-3-4'lü ağaçlar da denir, ikili ağaç şeklinde gerçekleştirilmiş biçimleridir.
  2. java.lang.ref paketinin Reference köklü sıradüzenindeki sınıfların tutacakları, bir nesne göstermektense bir başka nesnenin tutacağını sarmalayarak onu nesneye çevirir. JSM tarafından özel muameleye tabii tutulan bu nesnelerin kullanım amacı, çöp toplayıcı ile etkileşerek kaynak yönetim sürecine uygulama gereksinimlerine göre müdahele edilmesini olanaklı kılmaktır.

Hızlı Arama Zamanlı Kaplar-1

Dizi🔎, Vector🔎 veya liste yapısını gerçekleştiren ArrayList🔎 ve LinkedList🔎 gibi sınıfların bir zaafı, doğrusal bir yapıya sahip olmaları nedeniyle ortalama eleman arama zamanlarının yavaş olmasıdır. Örnek olarak, sırasız bir listede gözlerinizi baştan sona doğru gezdirerek adınızı aradığınızı düşünün. Yapmanız gereken, listenin başından adınızın bulunduğu yere kadar her sıradaki ad ile kendi adınızın eşitlik denetimini yapmaktır. Şanslıysanız, adınız başlardadır ve kısa zamanda kendinize dair bilgileri bulabilirsiniz; ancak, şansınızın yardım etmediği bir gün adınız listenin sonunda da olabilir ve aradığınız bilgiyi çok daha uzun bir zamanda elde edersiniz. Aynı listede adını arayanların harcadığı ortalama zaman ise—n kişi için toplam 1 + 2 + ... + n eşitlik denetimi olduğuna göre—n(n+1)/2n, yani (n+1)/2, eşitlik denetimi zamanı olacaktır.

Her eşitlik denetimi sonrasında arama uzayını sadece bir küçülten bu yöntem işi görür fakat büyük listelerde işkence halini alan bir performansa sahiptir. Biraz düzenli bir yapıya sahip olanlarınız, performans çözümlemede O(n) şeklinde ifade edilen bu sonucun aşağılara çekilmesinin olanaklı olduğunu bilirler. Mesela, bir kütüphaneye yolu düşüp de kitap arayanlarınız—iyi örnek olmadı galiba... müzik dükkânından albüm alanlarınız diyelim—hemen konuya [ya da tarza] göre indeksleme ve aynı konu içinde alfabetik sıraya dizmeyi önereceklerdir. Java standart kitaplığında doğrudan karşılığı olmadığı için, indekslemeyi köşeye koyup sıralamanın bize ne kazandırabileceğini görelim; bunu yaparken de—unutmayın, asıl konumuz işi bilgisayarlara yaptırmak—adımızın özelliklerine dayalı eniyilemelerin yapılamadığını varsayalım. İyi bir arama zamanını garanti etmek için izlenmesi gereken strateji, her eşitlik denetimi sonrasında arama uzayını olabildiğince yüksek oranda küçültmelidir. Konulan bu hedefe, "İlk elemanın ad bilgisi adıma eşit mi?" şeklinde sorulan sorunun "Ortadaki elemanın ad bilgisi adıma eşit mi, eşit değilse adımdan önce mi geliyor, sonra mı?" şeklinde değiştirilmesiyle erişilebilir. Böylesine bir değişiklik arama uzayımızı bir azaltmak yerine ikiye bölerek küçültecektir. Çünkü, sıralı olduğu bilinen listemizin orta elemanının aradığımız değere eşit olmaması durumunda, adımız ya ilk yarıdadır ya da ikinci yarıda; böylece, adımız hangi yarıda olursa olsun, arama uzayımız yarı yarıya küçülecektir. Bu ise, sorgumuzun olumlu veya olumsuz sonucunun O(n) yerine O(lgn) performansla verileceği anlamını taşır. İkili arama adı verilen ve java.util paketindeki Arrays🔎 ve Collections sınıflarının binarySearch metotlarında gerçekleştirilen bu algoritmanın ifade edilen performansı verebilmesi için veri kümesinin sıralı ve altyapıda kullanılan veri yapısının doğrudan erişimli dizi, Vector veya ArrayList gibi bir yapı olması gerekir. Çok özel durumlar1 dışında sıralamanın O(nlgn) ve daha fazla bir ek yük getireceği düşünüldüğünde, bu algoritmanın, özellikle de ekleme ve silmelerle veri kümesinin büyüyüp küçüldüğü kullanım desenlerinde, her zaman sözü edilen performansı sağlayamayacağı görülür. Çözüm, ekleme/silme işlemlerinin de hızlı bir şekilde yapıldığı hızlı arama zamanlı kaplar olan ve iki yazılık dizimizin konusunu oluşturan eşlemlerdedir.


Giriş


Standart Java kitaplığına bakıldığında eşlemlerin Veri Kapları Çerçevesi'nin bir parçası olarak java.util paketi içinde tanımlanıp gerçekleştirildiği görülür. Temel işlevselliği tanımlayan Map arayüzü ve kimi işlemlerin altyapıda kullanılan veri yapısından bağımsız bir şekilde gerçekleştirildiği AbstractMap sınıfının temelini oluşturduğu eşlemlere dair sıradüzeninin önemli bir bölümü aşağıda verilmiştir. Object, Cloneable ve Serializable dışındakilerin java.util paketinde olduğu türlerden Hashtable, Java'nın ilk uyarlamasından itibaren var olup J2SDK 1.2 ile gelen Veri Kapları Çerçevesi'ne monte edilmiştir. Dolayısıyla, J2SDK 1.2 öncesi yazılmış programları çökertmemek adına Hashtable sınıfı, AbstractMap'ten kalıtlamaktansa, Map arayüzünü doğrudan gerçekleştirmiştir. Kadük olan Dictionary adlı soyut sınıfın kullanımından ise kesinlikle kaçınılmalıdır.
Map arayüzü ile başlamadan önce, nasıl kullanılabileceğini örneklendirerek eşlemlerin biçimsel olmayan tanımını yapalım. Eşlemler, anahtar-tutanak çifti olarak temsil edilen eşlemelerin hızlı bir biçimde işlenmesini sağlayacak şekilde düzenlenerek tutulduğu veri yapılarıdır. Doğal olarak temel işlemler, eşleme bir anahtar-tutanak çiftinin eklenmesi, sağlanan bir anahtara dair tutanak bilgisinin sorgulanması veya eşlemenin silinmesidir. Bu işlemlerin hızlı bir şekilde tamamlanması ise gerçekleştirimci tarafından karşılanacağı varsayılan temel bir beklentidir.

Kimi eşlemelerin eşlemlerin yanısıra diğer veri yapıları ile de temsil edilebileceği bir gerçektir. Örnek olarak, ülkemizdeki plaka kodları ile il adları arasındaki eşlemeleri düşünün. Söz konusu eşlemeler bir eşlemle tutulabileceği gibi, doğrudan erişimi destekleyen bir veri yapısı kullanılarak da tutulabilir. Aslına bakarsanız, veri kümemizin durağan yapısı—her dakika yeni bir il eklenmeyecek—ve plaka kodları ile doğrudan erişimde kullanılan indis arasındaki neredeyse mükemmel uyuşma nedeniyle—0 nolu indis boş bırakılırsa 1-1 bir uyuşmadan söz edebiliriz—dizinin kullanılması çok daha yerinde bir seçim olacaktır. Ancak; eşlemenin ters yönde (İl adı→Plaka kodu) olmasının istenmesi halinde işler değişir: doğrudan erişimden yararlanılabilmesi il adının kıyımdan geçirilerek bir tamsayıya dönüştürülmesi ile olanaklıdır. Bu gibi bir durumda, fazladan kıyım fonksiyonu yazıp belki de düşük verimli bir çözüm üretmektense, eşlem kullanmak daha doğru olacaktır.

Eşlemleri çekici kılan bir diğer kullanım deseni, veri kümesinin anahtar-tutanak eşlemelerinin eklenmesi ve silinmesi ile büyüyüp küçüldüğü problemlerdir. Bu gibi durumlarda, kıyım fonksiyonunun maliyetine ek olarak, altyapıda kullanılan veri yapısının büyümesi veya küçülmesi ve ekleme ile silmelerin kaydırmalar nedeniyle sebep olacağı maliyet, eşlem kullanımını cazip hale getirmektedir.

Map Arayüzü ve AbstractMap Sınıfı


Gelelim, Map arayüzüne. Anahtar ve tutanak bilgisi türlerinin tür parametresi olarak tanımlandığı bu soysal arayüzdeki isEmpty iletisi, hedef eşlemin boş olup olmadığı sorusuna yanıt verirken, size iletisi hedef eşlemdeki eşleme sayısını döndürür. Bu bağlamda, standart kitaplıkta sağlanan gerçekleştirimlerin aynı anahtara karşılık tek bir tutanak bilgisi tuttuğu hatırlatılmalıdır.2

Eşlemlere ekleme, argüman olarak, sırasıyla, anahtar ve tutacak bilgisini alan put iletisi ile yapılabilir. Anahtar değerinin eşlendiği bir önceki tutanağı döndüren bu işlemin aynı anahtar değeri kullanılarak birden çok kez icra edilmesi durumunda, en son ekleme etkili olacaktır. Birden çok sayıda eşlemenin eklenmesinin istenmesi halinde, arda arda kullanılacak put iletileri yerine eklenecek anahtar-tutanak bilgilerini içeren bir eşlem bekleyen putAll iletisi kullanılabilir.

get iletisi argümanındaki anahtarın hedef eşlemde eşlendiği tutanak bilgisini döndürür. Benzer bir ileti, kendisine geçirilen anahtarın bir tutanak ile eşlenip eşlenmediğini döndüren containsKey yüklemidir. containsValue ise containsKey yükleminin tersi olarak düşünülebilir; bu yüklem, argümanındaki tutanak bilgisinin herhangi bir anahtar ile eşlenip eşlenmediği sorusunu yanıtlar. Bir an için, get iletisinin containsKey iletisini gereksiz kıldığı düşünülebilir. Ne var ki, get iletisinin eşlemde olmayan bir anahtarın sorgulanmasına yanıt olarak null döndürmesi ve kimi eşlem yapısı gerçekleştirimlerinde anahtarların null ile eşlenebilir olması sonucu ortaya çıkan muğlaklık ancak containsKey iletisinin kullanımıyla ortadan kaldırılabilir. containsKey aynı anahtar için true döndürüyorsa anahtar null değerine eşlenmiş demektir, aksi takdirde anahtara dair bir eşleme yoktur.

Hedef eşlemden eleman silmek için iki ileti kullanılabilir. Bunlardan clear, eşlemi hızlı bir biçimde boşaltırken, remove argümanındaki anahtara ilişkin eşlemeyi silmekle yetinir ve sonucu olarak işlem öncesinde anahtarın ilişkilendirildiği tutanak bilgisini döndürür. Geçirilen anahtar değerinin hedef eşlemde bulunmaması halinde ise, remove bu gerçeği kullanıcısına null döndürerek bildirir.

Map arayüzünü gerçekleştiren sınıfların desteklemesi gereken bir diğer ileti grubu, hedef eşlemdeki anahtar-tutanak eşlemelerinin değişik görüntülerini sağlayanlardır. Bu gruptaki iletiler tarafından döndürülen kaplar ile hedef eşlemin belleği paylaştıkları ve dolayısıyla görüntü üzerinden yapılan değişikliklerin eşleme de yansıtılacağı akılda tutulmalıdır. Bunlardan keySet, anahtarları barındıran küme nesnesini gösteren bir java.util.Set tutacağı döndürürken, values iletisi tutanak bilgilerini içeren nesneyi temsil eden bir java.util.Collection tutacağı döndürür. İlk ileti için Set ikinci ileti içinse Collection türlü bir tutacak döndürülmesinin nedeni, eşlemlerin aynı anahtardan en fazla bir tane bulundururken, aynı tutanak bilgisinin birden çok sayıda eşlemede geçmesinin mümkün olmasıdır. Örnek olarak; anahtar türü String, tutanak türü Integer tanımlanmış bir eşleme "İzmir"→35 ve "Karşıyaka"→35 bilgilerinin eklendiğini varsayalım. Bu eşleme gönderilecek values iletisi, Collection tutacağı ile gösterilen iki elemanlı bir kap döndürürecektir: halbuki, dönüş türü olarak Set'in kullanılması her iki değerin de 35 olması nedeniyle tek elemanlı bir küme döndürülmesine neden olacaktı.

values ve keySet iletileri gibi görüntüleme grubuna ait olan entrySet, hedef eşlemdeki anahtar-tutanak çiftlerini bir küme içinde döndürür. Kümelerin tek tür ile parametrize edilebiliyor olmasından ötürü, döndürülen küme anahtar ve tutacak türleri ile parametrize edilen Map.Entry ile gösterilen nesneler içerir. Başka bir deyişle, Map arayüzünün iç arayüzü olan bu tür anahtar ve tutanak bilgilerini sarmalar; anahtar veya tutanak bilgilerine dair bir şeyler yapmak istediğimizde, entrySet iletisinin döndürdüğü kümenin elemanlarına uygun iletileri göndermemiz gerekir. Bu iletiler, anahtarın sorgulanması (getKey) tutanak bilgisinin sorgulanıp değiştirilmesi (getValue ve setValue) ve eşitlik denetimi (equals) ile kıyım fonksiyonundan (hashCode) ibarettir. Döndürülen küme nesnesinin eşlem nesnesi ile aynı altyapıyı kullandığı unutulmamalıdır. Yani, entrySet'in döndürdüğü küme yoluyla güncellenen bilgiler eşleme yansıyacaktır; benzer şekilde, küme tutacağı eşlem tutacağı ile yapılan değişiklikleri görebilecektir.

Son olarak göz atacağımız iletiler, Object sınıfından tanıdık gelecek olan equals ve hashCode. Sırasıyla, eşitlik denetimi ve kıyım fonksiyonuna karşılık gelen bu iletilerin, Map arayüzüne konulmasının nedeni, geliştiricinin yeni bir eşlem sınıfı gerçekleştirimi sunmak istemesi halinde dikkatsizlikle bu iletileri es geçmesinin önüne geçmektir. Diğer veri kapları gibi eşlemlerin de eşitlik denetimlerinin kap içeriği göz önüne alınarak yapılması equals iletisinin gerçekleştirilmesini zorunlu kılmaktadır. Elemanların belki de hepsini dolaşarak tamamlanacak bu pahalı işlemin maliyetinin düşürülmesi içinse kıyım fonksiyonundan yararlanılması olasılığı hashCode iletisinin gerçekleştirilmesini dikte etmektedir; ne de olsa, kıyım değerleri farklı olan iki nesnenin eşit olması olanaklı değildir.🔎

Bu noktada, önceki paragrafta andığımız iletilerin eşlemleri ilgilendirdiği, anahtar ve tutanak türleri için herhangi bir zorunluluk olmadığı vurgulanmalıdır. Ne var ki; eşlem yapısının arayüzünde yer alan iletilerin gerçekleştirimleri, alternatif bir yöntem olmadığı savlanamasa da, yüksek olasılıkla anahtar ve tutacak bilgilerinin eşitlik denetiminden yararlanarak işlerini göreceklerdir. Bir ihtimal eşlem yapısının gerçekleştirimcisi, eşitlik denetimini anahtar/tutacak bilgisinin kıyım fonksiyonundan yararlanarak kısa yoldan bitirmek isteyecektir. Dolayısıyla, eşlem yapısına konulacak nesnelerin sınıflarında equals ve hashCode iletilerinin gerçekleştirilip gerçekleştirilmeyeceği dikkatle düşünülmeli ve karara bağlanmalıdır.

Map arayüzündeki kimi iletiler, altyapıda kullanılan veri yapısından bağımsız bir biçimde gerçekleştirilebilir. Örneğin, get iletisi eşlemdeki entrySet'in döndürdüğü anahtar-tutanak çiftlerinin gezilerek eşitlik denetimi yapılmasıyla gerçekleştirilebilir. Bunun, farklı sınıflarda tekrarlanmaktansa, ortak bir üst sınıfa (AbstractMap) konulması ve eşlem sınıflarının (HahsMap, TreeMap, vd.) bu sınıftan kalıtlayarak gerçekleştirilmesi kodun yeniden kullanımını artıracaktır. Eşlem gerçekleştirimcisinin daha uygun bir çözümü olması durumunda ise, ilişkin metot ezilerek istenen yapılabilir.

HashMap ve LinkedHashMap Sınıfları


Eşlemlerin bir gerçekleştirim yöntemi, indis ile erişimin yüksek performansından yararlanmak amacıyla altyapıda doğrudan erişimli bir yapı kullanır. Amaç, eşleme gönderilen iletilerin arka plandaki yapı üzerinde uygulanan indis işlemleri haline dönüştürülmesi ve doğrudan erişimin keyfini sürmektir. Ancak, bu söylendiği kadar kolay gerçekleştirilemez; anahtar değerlerinin türü ve alabileceği değerler işi zora sokabilir. Plaka kodu→İl adı örneğimizde işimiz çok basitti ve bundan dolayı dizinin kullanılması düşünülebilir demiştik. Ne var ki, anahtar değerlerinin çok daha geniş ve seyrek bir aralığa dağılması veya tamsayı olmaması söz konusu olabilir. Mesela, farklı yıllarda kaydolmuş öğrencilerin aldığı bir derse dair No→Ad eşlemelerini tutan diziyi düşünün. 8-10 basamaklı bir tamsayı olan öğrenci numarası ve bu numaraların değişik yıllara dağılmış olması, numaradan indise geçişi zorlaştıracaktır. Peki ya, öğrenci numaraları tamsayı olarak değil de String olarak temsil ediliyor olsaydı? Ya da, sözcükler ve anlamları arasındaki eşlemeleri tutmak isteseydik?

Bir önceki paragrafta yöneltilen soruların yanıtı, anahtar değerlerin uygun bir şekilde kıyılıp tamsayıya çevrilmesinden geçer. Yapmamız gereken, önce anahtar değeri hashCode iletisini göndererek kıymak ve elde edilen tamsayıyı arka plandaki doğrudan erişimli yapıya indisli erişimde kullanmaktır. İşin püf noktası olan kıyım fonksiyonunu düzgün gerçekleştirirsek, kullandığımız yöntem neredeyse dizi veya Vector içindeki bir elemana erişim kadar hızlı olacaktır. Kıyım fonksiyonunun kötü olması ise, bir o kadar yavaş bir erişim zamanımız olacağı anlamına gelir. Dolayısıyla, mümkünse, işi bir bilene bırakmakta yarar vardır. Java'da bu yüce makam, HashMap sınıfıdır.

HashMap nesnelerinin performansını etkileyen iki parametre vardır: sığa ve doluluk oranı. Bunlar, sırasıyla, eşlem içeriğinin tutulduğu doğrudan erişimli yapının uzunluğunu ve eşlemdeki anahtar-tutanak çifti sayısının sığaya oranını bildirir. Eşleme eklenmek istenen bir anahtar-tutanak çifti, anahtar değerinin kıyılarak 0 ila sığa-1 arasında bir tamsayıya dönüştürülmesi sonrasında yapının ilişkin konumuna, ki bu konumlara kova denir, eklenecektir. Bu kovanın boş olmaması durumunda ise, anahtar-tutanak çifti daha önceden kovaya konulmuş olan diğer eşlemelerin bulunduğu bir listeye eklenir. Sorgulama ve silme işlemleri de işe anahtar bilgisini kıyarak başlar. Bunun takiben, altyapıdaki doğrudan erişimli yapının ilişkin kovasına bakılır. Söz konusu kova boşsa, her iki işlem de null döndürür. Aksi takdirde, kovada tutulan eşlemelerin bulunduğu listenin aranması sonrasında sonuç döndürülür. Kovanın boş olmaması, eşlemin anahtara dair bilgi içerdiği anlamını taşımaz; kovada yer alan eşlemeler aynı kıyım değerine sahip farklı anahtar değerlerine ilişkin eşlemeler de olabilir.

Zaman içinde ekleme yapıldıkça eşlem dolacak, hem altyapıda kullanılan doğrudan erişimli yapı kalabalıklaşacak hem de kovalar içindeki listeler uzayacaktır. Bu; ekleme, sorgulama ve silme için harcanan zamanların uzayacağı anlamına gelir. Böyle bir durumun oluşmasını engellemek adına HashMap, doluluk oranının belli bir değeri aşması üzerine doğrudan erişimli yapıyı büyütür ve içeriği yeniden düzenler. Oldukça pahalı bir işlem olması nedeniyle, yeniden düzenleme sayısının en aza indirilmesi performans açısından yararlı olacaktır. Bunun için, HashMap nesnesinin yaratılması sırasında sığa ve doluluk oranı parametrelerinin probleme uygun bir biçimde seçilmesi gerekir.

HashMap nesneleri dört yapıcıdan biri kullanılarak yaratılabilir. Bunlardan int ve float argüman alanı, nesneyi istenen ilk sığa ve doluluk oranına sahip olacak şekilde yaratır. Yapıcıya sağlanmayıp işlem sırasında çıkarsanamaması durumunda doluluk oranı 0.75, ilk sığa ise 16 olarak kabul edilir. Dolayısıyla, tek int argüman bekleyen yapıcının kullanılması durumunda, yaratılan eşlem argümanda geçirilen büyüklükte bir ilk sığaya ve 0.75 doluluk oranına sahip olacaktır. Varsayılan yapıcının kullanılması halinde ise, 16 ilk sığalı ve doluluk oranı 0.75 olan bir eşlem yaratılacaktır. Her üç durumda da yaratılan eşlem boş olacaktır. Nesnemizin içeriğe sahip olarak yaratılmasını istiyorsak, yapmamız gereken Map arayüzünü destekleyen bir sınıfın nesnesinin argüman geçirildiği yapıcıyı kullanmaktır. Ancak, şunu unutmayın: argümanındaki eşlem ile aynı sayıda eşlemeye sahip yaratılan yeni nesne doluluk oranını miras almayacak, 0.75 olarak kabul edecektir.

HashMap sınıfının akılda tutulması gereken bir özelliği, eşlem içeriğine dair görüntülerin (entrySet, keySet ve values) anahtar-tutanak çiftlerini herhangi bir sırada vermiyor olmasıdır. Hatta; doluluk oranının aşılması sonrasında yeniden düzenlenen eşlemlerin görüntüleri düzenleme öncesindekiyle alakalı olmayabilir. Bunu, aşağıdaki örneğin işaretli satırındaki örtük toString kullanımından da görebiliriz. Üretilen çıktıda ne ekleme zamanı, ne son erişim zamanı, ne de anahtar bilgileri arasındaki bir sıralama ölçütü dikkate alınmaktadır.

Eşlemler.java
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Eşlemler {
  public static void main(String[] ksa) {
    Map<String, String> sözlük = new HashMap<>();
    sözlüğüDoldur(sözlük);
    System.out.println("Sözlük içeriği: " + sözlük);
    ...
  } // void main(String[]) sonu

  private static void sözlüğüDoldur(Map<String, String> sözlük) {
    Scanner grdKnlı = new Scanner(System.in);
    for (;;) {
      System.out.print("Yeni bir sözcük giriniz: ");
      String sözcük = grdKnlı.nextLine();
      if (sözcük.toLowerCase().equals("bitti")) break;
      System.out.print("Anlam: ");
      String anlam = grdKnlı.nextLine();
      sözlük.put(sözcük, anlam);
    } 
  } // void sözlüğüDoldur(Map<String, String>) sonu 
  ...
} // Eşlemler sınıfının sonu
Derdimizin çaresi, sergilenen bu kaotik davranışı değiştiren LinkedHashMap sınıfındadır. HashMap'ten kalıtlayan bu sınıf, doğrudan erişimli yapıya ek olarak, altyapıda tuttuğu çift bağlaçlı bir liste ile eşlem içeriğini yapıcı çağrısı sırasında belirlenen iki ölçütten birine göre sıralı tutarak üretilen çıktıyı beklentinize göre oluşturacaktır. Aksi söylenmediği takdirde ekleme sırasında üretilecek olan çıktı, ilk sığa ve doluluk oranına ek olarak sağlanacak üçüncü bir argümanla en son erişim sırasına göre de oluşturulabilir. Örneğin, aşağıdaki kod parçasının işaretli satırındaki yapıcı çağrısı, sözlük2 tarafından temsil edilen eşlemin en son erişilen anahtar-tutanak çiftleri başta gelecek şekilde görüntülenmesini sağlar.
...
import java.util.LinkedHashMap;

public class Eşlemler {
  public static void main(String[] ksa) {
    Map<String, String> sözlük2 = new LinkedHashMap<>(100, 0.80, true);
    sözlüğüDoldur(sözlük2);
    // sözlük2'yi kullan...
    System.out.println("Sözlük içeriği: " + sözlük2);
    ...
  } // void main(String[]) sonu
  ...
} // Eşlemler sınıfının sonu
Diğer yapıcıları HashMap'tekilerle aynı şekilde çalışan LinkedHashMap sınıfı, HashMap'te olduğu gibi Map arayüzündekinden başka bir işlevsellik sunmaz.

Hashtable Sınıfı


Buraya kadar gelip de pes etmeyenlere müjde, Hashtable sınıfı HashMap ile neredeyse tamamıyla aynıdır. J2SDK 1.2 öncesinde tanımlanmış olan bu sınıfın HashMap'ten farklı olarak desteklediği kimi iletiler herhangi bir ekstra işlevsellik katmaz. Ancak, sakın ola ki, bu iki sınıfın her zaman birbirlerinin yerine kullanılabileceğini sanmayın. Bunun başlıca üç sebebi vardır:
  1. Fazladan bir işlev katmamakla beraber Hashtable tarafından sağlanan J2SDK 1.2 öncesinden kalma contains, elements ve keys iletileri HashMap nesnelerine gönderilemez. [Eski kodlarınızı elden geçirirken bu iletileri, sırasıyla, containsValue, values ve keySet iletileri ile değiştirmenizi tavsiye ederim.]
  2. Diğer eşlem sınıflarının aksine, Hashtable anahtar değeri olarak null değerinin kullanılmasına izin vermez.
  3. Diğer eşlem sınıflarının aksine, Hashtable çok izlekli kullanım düşünülerek yazılmıştır. Yani, HashMap [ve TreeMap] nesnelerinin farklı izleklerden aynı anda kullanılmak istenmeleri durumunda programcının yapının tutarlılığını korumak adına eşgüdüm kodu yazması gerekirken, Hashtable için böyle bir şey söz konusu değildir.

  1. Çok özel eleman türü ve dağılımı istemeleri nedeniyle O(n) performans sergileseler de kimi algoritmaların bu gibi genel kullanımlarda zikredilmeleri mantıklı değildir.
  2. Bu aslında o kadar da kısıtlayıcı bir şey değil. Anahtarın birden çok değer ile ilişkilendirilmesini istiyorsanız, tutanak türü kısmında List veya Set gibi bir kap türünün belirtilmesi yeterli olacaktır.

14 Eylül 2011 Çarşamba

Değişken Uzunluklu Diziler: java.util.Vector

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.
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 akaklara 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.

Vector sınıfındaki eşdeğer iletiler
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

  1. elements iletisi iterator ve listIterator iletilerinin eklenmesi ile kullanımdan düşmüştür. Dolayısıyla, anlatımımız bu iletiyi kapsamayacaktır.
  2. İletilerin adı olarak vectorIterator yerine listIterator (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 iletiler List arayüzüne konulmuş.
  3. Bu iletinin Vector sınıfındaki gerçekleştirimi Arrays.asList metodu ile bir bütün olarak düşünülmelidir.