İçeriğe geç
academia.sh

Ders 01 / 15

Koleksiyon Çerçevesi

Aynı üç gerçekleştirim dört ayrı aile için yan yana koşturuluyor ve onbeş gözlemin yedisi arayüz garantisi, sekizi yalnız seçilen sınıfın gerçekleştirim davranışı çıkıyor. Sınırlayıcı ölçüm bu ayrımın kendisini sınırlıyor: dört ögeli bir kaynakta PriorityQueue'nun gezinme sırası çıkış sırasıyla rastlantı eseri eşleşiyor, beşinci öge eklenince ayrım yeniden görünür oluyor.

İçindekiler

Nesneye Dayalı Java kursu boyunca bir kararı hep dil verdi. Erişim belirteci hangi üyenin görüleceğini, final hangi değişkenin bir daha bağlanamayacağını, denetlenen istisna hangi hatanın imzada duracağını derleyici belirledi. Kapanış dersi üç sözleşmenin — önceki durum, sonraki durum, değişmez — hiçbirinin kusuru yok etmediğini, yalnız yerini değiştirdiğini ölçtü; ama karar yine dilin içindeydi ve derleyici onu her seferinde kaynaktan okuyabiliyordu.

Bu ders yazılmış bir kütüphaneyi çağırmakla başlıyor ve karar artık dilin dışında. List, Set, Queue, Map birer arayüzdür ve bir arayüz bir sözdür; ama o sözü kimin verdiği tek bir yerden okunmaz. Gözlemlenen bir davranış üç kaynaktan birine ait olabilir — arayüzün kendisine, seçilen sınıfa, ya da çağıranın uyduğu bir kurala — ve üçü çağrı yazımında hiçbir şekilde ayırt edilemez. ArrayList ile LinkedList aynı koy(0) çağrısını kabul eder, aynı get(0) çağrısını yanıtlar; hangi davranışın ikisinde de aynı kalacağı, hangisinin sınıf değiştiğinde değişeceği kaynak metinden görünmez. Bu dersin ve kursun sorusu budur: elde tutulan bir gözlemin garantisini kim veriyor.

Üç Gerçekleştirim, Tek Soru

Sorunun yanıtı tek bir gerçekleştirimle bulunamaz — tek sınıfı gözleyen biri onun davranışını arayüzün sözüyle karıştırır. Ölçüm bu yüzden aynı arayüzü konuşan üç gerçekleştirimi yan yana koyar ve üçüne aynı soruyu sorar: yanıt üçünde de aynıysa gözlem arayüzden geliyordur, üçü ayrışıyorsa gözlem gerçekleştirime aittir.

  • KO1 — Aşağıdaki Garanti sınıfı hiçbir aileye özgü değildir. sor yalnızca topladığı yanıtları karşılaştırır; liste, küme, kuyruk ya da sözlük olması ölçütü değiştirmez.
  • KO2 — Bir gerçekleştirim çağrı sırasında istisna fırlatırsa yanıtı "hata" sayılır ve öteki iki yanıtla aynı sütunda karşılaştırılır; istisna ayrı bir dal açmaz.
// Garanti.java — bir davranisin garantisini kim veriyor: arayuz mu, gerceklestirim mi
import java.util.*;
import java.util.function.*;

public class Garanti {
    static int arayuz = 0;
    static int gerceklestirim = 0;

    final List<String> adlar = new ArrayList<>();
    final List<Supplier<Object>> uretecler = new ArrayList<>();

    Garanti ekle(String ad, Supplier<Object> uretec) {
        adlar.add(ad);
        uretecler.add(uretec);
        return this;
    }

    void baslik(String aile) {
        StringBuilder sb = new StringBuilder(String.format("%-32s", aile));
        for (String ad : adlar) sb.append(String.format("%-15s", ad));
        sb.append("garantiyi veren");
        System.out.println(sb);
    }

    void sor(String soru, Function<Object, String> gozlem) {
        List<String> yanitlar = new ArrayList<>();
        for (Supplier<Object> u : uretecler) {
            String y;
            try {
                y = gozlem.apply(u.get());
            } catch (RuntimeException e) {
                y = "hata";
            }
            yanitlar.add(y);
        }
        boolean ortak = new HashSet<>(yanitlar).size() == 1;
        if (ortak) arayuz++; else gerceklestirim++;
        StringBuilder sb = new StringBuilder(String.format("%-32s", soru));
        for (String y : yanitlar) sb.append(String.format("%-15s", y));
        sb.append(ortak ? "arayuz" : "gerceklestirim");
        System.out.println(sb);
    }

    static void ozet() {
        System.out.printf("gozlem: %d arayuz garantisi, %d gerceklestirim davranisi%n",
                arayuz, gerceklestirim);
    }
}

ekle bir aileye bir gerçekleştirim ekler; her aile üç kez çağırır. sor bir soruyu üç gerçekleştirime birden sorar ve tek bir sayaç işletir: yanıtlar kümesi tek elemana düşüyorsa (HashSet<>(yanitlar).size() == 1) soru arayüz sütununa, düşmüyorsa gerçekleştirim sütununa yazılır. Kural mekanik ve öznesizdir — hangi ailenin sorulduğunu bilmez, yalnız yanıtları sayar.

Dört Aile, On Beş Gözlem

  • KO3 — Kaynak liste KAYNAK beş sabit ögedir ve dört aile boyunca değişmeden kalır; sözlük doldurulurken de aynı sırayla yazılır.
  • KO4 — Sayılan onbeş satırın hepsi aynı programın tek koşumundan gelir; ozet bu koşumun toplamını, başka hiçbir satırı saymadan, verir.
// Cerceve.java — dort ailenin gozlemlenebilir sozlerini ayirir
import java.util.*;

public class Cerceve {
    static final List<String> KAYNAK = List.of("zulu", "delta", "alfa", "carli", "bravo");

    static Map<String, Integer> doldur(Map<String, Integer> m) {
        for (int i = 0; i < KAYNAK.size(); i++) m.put(KAYNAK.get(i), i);
        return m;
    }

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        Garanti liste = new Garanti()
                .ekle("ArrayList", () -> new ArrayList<>(KAYNAK))
                .ekle("LinkedList", () -> new LinkedList<>(KAYNAK))
                .ekle("List.of", () -> List.copyOf(KAYNAK));
        liste.baslik("liste");
        liste.sor("ekleme sirasi korunuyor mu",
                o -> String.valueOf(new ArrayList<>((List<String>) o).equals(KAYNAK)));
        liste.sor("indeksle erisim var mi", o -> ((List<String>) o).get(1));
        liste.sor("esitlik icerige mi bakiyor",
                o -> String.valueOf(o.equals(new ArrayList<>(KAYNAK))));
        liste.sor("oge eklenebiliyor mu", o -> {
            try { ((List<String>) o).add("ekstra"); return "evet"; }
            catch (RuntimeException e) { return "hayir"; }
        });
        liste.sor("bos deger tutulabiliyor mu", o -> {
            try { ((List<String>) o).add(null); return "evet"; }
            catch (RuntimeException e) { return "hayir"; }
        });
        System.out.println();

        Garanti kume = new Garanti()
                .ekle("HashSet", () -> new HashSet<>(KAYNAK))
                .ekle("LinkedHashSet", () -> new LinkedHashSet<>(KAYNAK))
                .ekle("TreeSet", () -> new TreeSet<>(KAYNAK));
        kume.baslik("kume");
        kume.sor("yinelenen oge kabul ediliyor mu", o -> {
            Set<String> s = (Set<String>) o;
            s.add("alfa");
            return String.valueOf(s.size());
        });
        kume.sor("gezinme sirasi ekleme sirasi mi",
                o -> String.valueOf(new ArrayList<>((Set<String>) o).equals(KAYNAK)));
        kume.sor("bos deger kabul ediliyor mu", o -> {
            try { ((Set<String>) o).add(null); return "evet"; }
            catch (RuntimeException e) { return "hayir"; }
        });
        System.out.println();

        Garanti kuyruk = new Garanti()
                .ekle("ArrayDeque", () -> new ArrayDeque<>(KAYNAK))
                .ekle("LinkedList", () -> new LinkedList<>(KAYNAK))
                .ekle("PriorityQueue", () -> new PriorityQueue<>(KAYNAK));
        kuyruk.baslik("kuyruk");
        kuyruk.sor("bastan cikan oge", o -> ((Queue<String>) o).poll());
        kuyruk.sor("bosalinca ne donuyor", o -> {
            Queue<String> q = (Queue<String>) o;
            while (q.poll() != null) { }
            return String.valueOf(q.poll());
        });
        kuyruk.sor("gezinme sirasi cikis sirasi mi", o -> {
            Queue<String> q = (Queue<String>) o;
            List<String> gezinme = new ArrayList<>(q);
            List<String> cikis = new ArrayList<>();
            String s;
            while ((s = q.poll()) != null) cikis.add(s);
            return String.valueOf(gezinme.equals(cikis));
        });
        System.out.println();

        Garanti sozluk = new Garanti()
                .ekle("HashMap", () -> doldur(new HashMap<>()))
                .ekle("LinkedHashMap", () -> doldur(new LinkedHashMap<>()))
                .ekle("TreeMap", () -> doldur(new TreeMap<>()));
        sozluk.baslik("sozluk");
        sozluk.sor("anahtar tekil mi", o -> {
            Map<String, Integer> m = (Map<String, Integer>) o;
            m.put("alfa", 99);
            return String.valueOf(m.size());
        });
        sozluk.sor("anahtar gorunumu canli mi", o -> {
            Map<String, Integer> m = (Map<String, Integer>) o;
            Set<String> g = m.keySet();
            m.put("ekstra", 5);
            return String.valueOf(g.contains("ekstra"));
        });
        sozluk.sor("gezinme sirasi ekleme sirasi mi",
                o -> String.valueOf(new ArrayList<>(((Map<String, Integer>) o).keySet())
                        .equals(KAYNAK)));
        sozluk.sor("bos anahtar kabul ediliyor mu", o -> {
            try { ((Map<String, Integer>) o).put(null, 0); return "evet"; }
            catch (RuntimeException e) { return "hayir"; }
        });
        System.out.println();

        Garanti.ozet();
    }
}
liste                           ArrayList      LinkedList     List.of        garantiyi veren
ekleme sirasi korunuyor mu      true           true           true           arayuz
indeksle erisim var mi          delta          delta          delta          arayuz
esitlik icerige mi bakiyor      true           true           true           arayuz
oge eklenebiliyor mu            evet           evet           hayir          gerceklestirim
bos deger tutulabiliyor mu      evet           evet           hayir          gerceklestirim

kume                            HashSet        LinkedHashSet  TreeSet        garantiyi veren
yinelenen oge kabul ediliyor mu 5              5              5              arayuz
gezinme sirasi ekleme sirasi mi false          true           false          gerceklestirim
bos deger kabul ediliyor mu     evet           evet           hayir          gerceklestirim

kuyruk                          ArrayDeque     LinkedList     PriorityQueue  garantiyi veren
bastan cikan oge                zulu           zulu           alfa           gerceklestirim
bosalinca ne donuyor            null           null           null           arayuz
gezinme sirasi cikis sirasi mi  true           true           false          gerceklestirim

sozluk                          HashMap        LinkedHashMap  TreeMap        garantiyi veren
anahtar tekil mi                5              5              5              arayuz
anahtar gorunumu canli mi       true           true           true           arayuz
gezinme sirasi ekleme sirasi mi false          true           false          gerceklestirim
bos anahtar kabul ediliyor mu   evet           evet           hayir          gerceklestirim

gozlem: 7 arayuz garantisi, 8 gerceklestirim davranisi

Onbeş satırın yedisi arayüz sütununda, sekizi gerçekleştirim sütununda topladı. Ayrım rastgele değildir ve tabloyu ikiye bölen bir çizgi çizilebilir: ne tutulduğu arayüzde durur, nasıl ve hangi sırayla tutulduğu gerçekleştirime bırakılmıştır. Bir listenin ekleme sırasını koruyacağı, içindekini indeksle vereceği, eşitliğinin içeriğe bakacağı — üçü de üç sınıfta aynı. Aynı ögenin ikinci kez eklenmesinin kümeyi büyütmeyeceği, boş bir kuyruktan çekmenin null vereceği — bunlar da öyle. Buna karşılık bir kümenin veya sözlüğün hangi sırayla gezileceği, bir kuyruğun başından hangi ögenin çıkacağı, bir kabın boş değeri kabul edip etmeyeceği satır satır ayrışıyor. Tek bir sınıfı — yalnızca HashMap’i, yalnızca PriorityQueue’yu — gözleyerek çıkarılan bir davranış listesinin çoğunluğu, arayüzün verdiği söz değildir.

İsteğe Bağlı İşlem: Arayüzün Kendi Çekincesi

Tablodaki iki satır ayrı bir okumayı hak ediyor: “öge eklenebiliyor mu” ve “bos deger tutulabiliyor mu” ikisi de gerceklestirim diye işaretlendi, çünkü List.of ile üretilen kap ikisini de reddediyor, ArrayList ile LinkedList ikisini de kabul ediyor. Ama Collection arayüzünün kendi tanımı add yöntemini isteğe bağlı işlem (optional operation) diye adlandırır: arayüz yöntemi tanımlar, aynı zamanda bir gerçekleştirimin onu reddedebileceğini de söyler. Bu, garanti sorusunu ikiye ayırıyor — çağrının başarılı olup olmayacağı ayrı bir sorudur, başarısız olduğunda ne olacağı ayrı bir sorudur. Birincisinin yanıtı yukarıdaki tabloda gerçekleştirime bağlıydı; ikincisi aşağıda ölçülüyor.

  • KO5 — Dört kap türü aynı denemeyi paylaşır: tek bir öge eklemeyi dene, reddedilirse istisnanın sınıf adını yaz, kabul edilirse "basarili" yaz.
// IsteyeBagli.java — reddeden gerceklestirimlerin hepsi ayni istisnayi mi atiyor
import java.util.*;

public class IsteyeBagli {
    static String dene(Runnable islem) {
        try { islem.run(); return "basarili"; }
        catch (RuntimeException e) { return e.getClass().getSimpleName(); }
    }

    public static void main(String[] args) {
        List<String> degisebilirListe = new ArrayList<>(List.of("a"));
        List<String> sabitListe = List.of("a");
        Set<String> sabitKume = Set.of("a");
        Map<String, Integer> sabitSozluk = Map.of("a", 1);

        System.out.println("ArrayList.add     : " + dene(() -> degisebilirListe.add("b")));
        System.out.println("List.of(...).add  : " + dene(() -> sabitListe.add("b")));
        System.out.println("Set.of(...).add   : " + dene(() -> sabitKume.add("b")));
        System.out.println("Map.of(...).put   : " + dene(() -> sabitSozluk.put("b", 2)));
    }
}
ArrayList.add     : basarili
List.of(...).add  : UnsupportedOperationException
Set.of(...).add   : UnsupportedOperationException
Map.of(...).put   : UnsupportedOperationException

Üç ayrı kap türü, üç ayrı paket, aynı reddi aynı sınıfla veriyor. Bu satır yukarıdaki tabloya girseydi ortak yanıt yüzünden arayuz diye işaretlenirdi — oysa reddin kendisi ArrayList için hiç gerçekleşmiyor, yalnız üç sabit kap için gerçekleşiyor. Çelişki görünüşte kalıyor, çünkü iki ayrı soru karışıyor. “Ekleme kabul edilir mi” sorusunun yanıtı gerçekleştirime bağlıdır ve önceki tabloda öyle sayıldı. “Kabul edilmezse hangi istisna düşer” sorusunun yanıtı ise Collection arayüzünün javadoc’unda adı geçen tek bir sınıfa bağlanmıştır ve üç paket boyunca değişmiyor. Arayüz burada bir davranışı garanti etmiyor, bir reddin biçimini garanti ediyor: reddeden her gerçekleştirim aynı adla, denetlenmeyen aynı istisna sınıfıyla reddediyor. Bu, “isteğe bağlı işlem” ifadesinin neden “işlem yoktur” değil “işlem olabilir ya da adıyla düşer” anlamına geldiğini gösteriyor.

Sınırlayıcı Ölçüm: Birlik Zayıf Kanıttır

Çerçevenin kuralı basit bir çıkarımdır: üç yanıt aynıysa arayüz. Bu bir çıkarımdır, belgeye bakmaz — ve çıkarımın kendisi yanılabilir. Kuyruk tablosundaki üçüncü satır (gezinme sirasi cikis sirasi mi) PriorityQueue için false verdi; ArrayDeque ile LinkedList içinse true. Bu satırın beş ögeli kaynakla ayrıştığı zaten görüldü. Aynı soru dört ögeli, daha küçük bir kaynağa sorulursa ne olur.

  • KO6 — Dört ögeli kaynak, beş ögeli KAYNAK’ın ilk ögesi (zulu) çıkarılmış hâlidir; beşinci öge geri eklendiğinde kaynak yeniden birebir KAYNAK’a döner.
// Rastlanti.java — gozlem birligi garanti kaniti degil
import java.util.*;

public class Rastlanti {
    static final List<String> KAYNAK = List.of("zulu", "delta", "alfa", "carli", "bravo");

    static boolean gezinmeCikisaEsitMi(List<String> kaynak) {
        PriorityQueue<String> pq = new PriorityQueue<>(kaynak);
        List<String> gezinme = new ArrayList<>(pq);
        List<String> cikis = new ArrayList<>();
        String s;
        while ((s = pq.poll()) != null) cikis.add(s);
        return gezinme.equals(cikis);
    }

    public static void main(String[] args) {
        List<String> dortOgeli = KAYNAK.subList(1, 5);
        System.out.println("dort ogeli kaynak              : " + dortOgeli);
        System.out.println("gezinme sirasi cikis sirasi mi : " + gezinmeCikisaEsitMi(dortOgeli));

        List<String> besOgeli = new ArrayList<>(dortOgeli);
        besOgeli.add(0, "zulu");
        System.out.println("besinci oge eklenince kaynak   : " + besOgeli);
        System.out.println("gezinme sirasi cikis sirasi mi : " + gezinmeCikisaEsitMi(besOgeli));
    }
}
dort ogeli kaynak              : [delta, alfa, carli, bravo]
gezinme sirasi cikis sirasi mi : true
besinci oge eklenince kaynak   : [zulu, delta, alfa, carli, bravo]
gezinme sirasi cikis sirasi mi : false

Dört ögelik kaynakla PriorityQueue’nun gezinme sırası çıkış sırasıyla birebir eşleşiyor — çıkarımın kuralına göre bu bir arayüz garantisi olurdu. Ama beşinci öge eklenip kaynak yeniden KAYNAK’ın kendisi olduğunda aynı soru false veriyor: kuyruk tablosunda zaten görülen sonuç. Aradaki tek fark ögelerin kendisidir; kuyruk arayüzü gezinme sırası için hiçbir zaman söz vermedi, ne dört ögede ne beşte. Dört ögelik durumda ortaya çıkan eşleşme kütüphanenin verdiği bir söz değil, o beş ismin heap dizilimiyle rastlantı eseri örtüşmesiydi.

Ders boyunca kurulan ölçütü tersinden okumanın yolu budur: birlik gözlemi garantiyi zayıf biçimde destekler, ayrılık gözlemi garantisizliği kesin gösterir. Üç gerçekleştirimin aynı yanıtı vermesi bir arayüz sözü olabileceği gibi, veri kümesinin küçüklüğünden doğan bir rastlantı da olabilir; bunu ayırt etmenin tek yolu veri kümesini değiştirip aynı soruyu yeniden sormaktır. Ayrılığın kanıt gücü bundan yüksektir, çünkü hiçbir rastlantı üç ayrı sınıfı üç ayrı yanıta zorlayamaz — ayrışan bir satır her zaman gerçek bir gerçekleştirim farkını gösterir.

Özet

  • Bir davranışın garantisini kimin verdiği çağrı yazımından görünmez; üç gerçekleştirimi yan yana koşturup yanıtları karşılaştırmak gerekir. Yanıt üçünde aynıysa gözlem arayüzden, ayrışıyorsa seçilen sınıftan geliyordur.
  • Dört ailede (liste, küme, kuyruk, sözlük) sorulan onbeş sorunun yedisi arayüz garantisi, sekizi gerçekleştirim davranışı çıktı. Ne tutulduğu arayüzde durur, nasıl ve hangi sırayla tutulduğu gerçekleştirime bırakılır.
  • Collection arayüzü add gibi yöntemleri isteğe bağlı işlem diye tanımlar: kabul edilip edilmeyeceği gerçekleştirime bağlıdır, ama reddedildiğinde hangi istisnanın düşeceği üç ayrı paket boyunca aynı kalır — reddin kendisi değil, reddin biçimi arayüzün sözüdür.
  • Sınırlayıcı ölçüm: dört ögeli bir kaynakta PriorityQueue’nun gezinme sırası çıkış sırasıyla rastlantı eseri eşleşir; beşinci öge eklendiğinde ayrım yeniden görünür olur. Gözlem birliği garanti kanıtı değildir, ayrılık gözlemi garantisizliği kesin gösterir.
  • Olcek çekirdeği (sınıf dosyasından bilgi okuma aracı) bu derste kullanılmadı; ölçüm yalnız çalışma zamanı gözlemine dayanıyor, çünkü sorulan soru derleyicinin değil kütüphanenin verdiği sözle ilgili.

Sonraki Adım

Bu derste dört aile bir bütün olarak ölçüldü ve her birinin kaç sözünün arayüzden geldiği sayıldı. Ama “liste”, “küme” ve “kuyruk” ayrımının kendisi hiç sorgulanmadı — üçü de birer koleksiyondur, üçü de aynı iş yükünü kabul edebilir. Sıradaki ders aynı veriyi üç arayüze birden verip her birinin hangi sözü verdiğini ve hangi bilgiyi sessizce düşürdüğünü ölçer; LinkedList’in hem liste hem kuyruk olmasının aynı çağrıya iki ayrı anlam kazandırdığı da orada görülür.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat