İçeriğe geç
academia.sh

Ders 08 / 10

Diziler

Java'da dizi bir nesnedir ve ölçüm bunu üç yerde gösterir: üretim tek bir komut bırakır, `d.length` alan okuması değil kendi başına bir komuttur, sınır denetimi ile dizi kovaryansının tip denetimi ise sınıf dosyasında hiç görünmeden çalışma zamanına bırakılır.

İçindekiler

Önceki ders akış biçimlerinin hangi komuta derlendiğini ölçtü: aynı seçim iki ayrı arama komutuna, aynı gelişmiş for döngüsü dizide ve listede ayrı adıma dönüşüyordu. Geriye üzerinde gezinilen şeyin kendisi kaldı.

Dizi, Veri Yapıları kursunda bir veri yapısı olarak kuruldu: sabit boyut, bitişik yerleşim, konumdan adrese giden aritmetik. Bu ders onu tekrarlamaz. Buradaki soru şudur: new int[3] satırı sınıf dosyasında kaç komut bırakır, d.length yazımı bir alan okuması mıdır, ve dizinin sınırını denetleyen adım kaynağın neresinde durur? Üçünün de yanıtı, dizinin Java’da bir nesne olmasından çıkar.

Dizi Bir Nesnedir

  • SD21 — Ölçülen kaynak metni dersin içinde yazılır. Ne yazdığımızı bildiğimiz için, sınıf dosyasında bulunan her fazlalık derleyicinin eklediğidir.
  • SD22Olcek çekirdeği ortak tanımdan gelir. Bu ders ona yalnız bir ölçüm ekler: bir yöntemin komut adlarını sırayla döndüren komutlar. Var olan ölçümlerin davranışı değişmez.
  • SD23 — Komut adları sınıf dosyasındaki adlardır ve Locale.ROOT ile küçük harfe çevrilerek basılır; yerel ayar sonucu etkilemez.
  • SD24 — Derleyici sıfırdan farklı bir sonuç döndürürse çekirdek istisna fırlatır. Ders bu istisnayı yakalayıp yalnız “derlenmedi” satırını basar; derleyicinin ileti metni basılmaz.
// Olcek.java — ortak ölçüm çekirdeği: derleyicinin sınıf dosyasına ne koyduğunu okur
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

class Olcek {
    record Yontem(String ad, int komut, List<String> eklenen) {}
    record Sinif(List<Yontem> yontem, List<String> alan) {}

    static ClassModel derle(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Path k = d.resolve(sinif + ".java");
        Files.writeString(k, kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow()
                .run(bos, bos, "-d", d.toString(), k.toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        return ClassFile.of().parse(d.resolve(sinif + ".class"));
    }

    static Sinif oku(String kaynak, String sinif) throws Exception {
        ClassModel cm = derle(kaynak, sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : cm.methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof InvokeInstruction iv)
                    eklenen.add(kisa(iv.owner().asInternalName()) + "." + iv.name().stringValue());
                else if (e instanceof NewPrimitiveArrayInstruction a)
                    eklenen.add("yeni dizi " + a.typeKind().name().toLowerCase(Locale.ROOT));
                else if (e instanceof NewReferenceArrayInstruction a)
                    eklenen.add("yeni dizi " + kisa(a.componentType().asInternalName()));
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, eklenen));
        }
        List<String> alanlar = new ArrayList<>();
        for (FieldModel f : cm.fields()) alanlar.add(f.fieldName().stringValue());
        return new Sinif(yontemler, alanlar);
    }

    static List<String> komutlar(String kaynak, String sinif, String yontem) throws Exception {
        List<String> adlar = new ArrayList<>();
        for (MethodModel m : derle(kaynak, sinif).methods())
            if (m.methodName().stringValue().equals(yontem))
                for (CodeElement e : m.code().orElseThrow())
                    if (e instanceof Instruction i)
                        adlar.add(i.opcode().name().toLowerCase(Locale.ROOT));
        return adlar;
    }

    static String kisa(String ic) { return ic.substring(ic.lastIndexOf('/') + 1); }
}
// Dizi.java — dizi üretimi sınıf dosyasında ne bırakıyor
import java.util.Arrays;

class Kayit {
    String ad;
    int adet;
    long agirlik;
}

public class Dizi {
    static final String K = """
        class DiziOrnek {
            static class Kayit { String ad; int adet; long agirlik; }
            static int[] bos() { return new int[3]; }
            static Kayit[] kayitlar() { return new Kayit[3]; }
            static int[] sabitli() { return new int[] {12, 18, 7}; }
            static int uzunluk(int[] d) { return d.length; }
            static int adet(Kayit k) { return k.adet; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-10s %6s  %s%n", "yontem", "komut", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(K, "DiziOrnek").yontem())
            if (!y.ad().equals("<init>"))
                System.out.printf("%-10s %6d  %s%n", y.ad(), y.komut(),
                        y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));

        System.out.println();
        System.out.println("d.length okuma: " + Olcek.komutlar(K, "DiziOrnek", "uzunluk"));
        System.out.println("k.adet okuma:   " + Olcek.komutlar(K, "DiziOrnek", "adet"));

        int[] sayilar = new int[3];
        Kayit[] kayitlar = new Kayit[3];
        System.out.println();
        System.out.println("int[] sinifi:      " + sayilar.getClass().getName());
        System.out.println("Kayit[] sinifi:    " + kayitlar.getClass().getName());
        System.out.println("dizinin ust sinifi: " + sayilar.getClass().getSuperclass().getName());
        System.out.println("baslangic degerleri: " + Arrays.toString(sayilar)
                + " " + Arrays.toString(kayitlar));

        try {
            Olcek.oku(K.replace("return d.length;", "d.length = 5; return 0;"), "DiziOrnek");
        } catch (IllegalStateException e) {
            System.out.println("d.length = 5   -> " + e.getMessage());
        }
    }
}
yontem      komut  kaynakta yazilmayan adim
bos             3  yeni dizi int
kayitlar        3  yeni dizi DiziOrnek$Kayit
sabitli        15  yeni dizi int
uzunluk         3  -
adet            3  -

d.length okuma: [aload_0, arraylength, ireturn]
k.adet okuma:   [aload_0, getfield, ireturn]

int[] sinifi:      [I
Kayit[] sinifi:    [LKayit;
dizinin ust sinifi: java.lang.Object
baslangic degerleri: [0, 0, 0] [null, null, null]
d.length = 5   -> derlenmedi: DiziOrnek

İlk iki satır dizi üretiminin üç komut olduğunu söylüyor: boyutu yükle, diziyi üret, döndür. Ölçek bu üretimi yeni dizi diye kaydediyor, çünkü sınıf dosyasında dizi üretimi nesne üretimiyle aynı sınıfa girer. Kayit[3] satırının kaydında sınıfın tam adı duruyor, öğe tipi diziye yazılmıştır.

Üçüncü satır bir fark koyuyor. Köşeli ayraç içinde üç değer yazan sabitli yöntemi 15 komut üretiyor; boyu aynı olan boş dizi 3 komut. Aradaki on iki komut, üç öğenin tek tek yazılmasıdır: her öğe için diziyi çoğalt, konumu yükle, değeri yükle, yaz. Kaynakta tek bir sabit gibi duran {12, 18, 7} yazımının sınıf dosyasında sabit karşılığı yoktur. İlk derste ölçülen sabit katlama sayılarda işler; dizide işlemez, çünkü katlanacak şey bir değer değil, çalışma zamanında üretilen bir nesnedir.

Son üç satır bunu adıyla koyuyor. Dizinin bir sınıfı var — int[] için [I, Kayit[] için [LKayit; — ve o sınıfın üst sınıfı java.lang.Object. Dizi bir nesnedir: öbekte üretilir, bir referansla tutulur, Object bekleyen her yere geçer. Üretim öğeleri üretmez; sayı dizisi sıfırlarla, kayıt dizisi null ile başlar. Kayıt dizisi kurmak üç kayıt kurmaz, üç boş yer kurar.

Uzunluk satırları ayrımın ikincisini veriyor. d.length yazımı arraylength komutuna, k.adet yazımı getfield komutuna derleniyor. Sözdizim ikisinde de aynı — nokta ve bir ad — ama karşılıkları ayrı komutlardır. length bir alan değildir; sınıf dosyasında alan listesinde bulunmaz, kendi komutu vardır. Bunun görünen sonucu son satırda: d.length = 5 yazan kaynak derlenmiyor. Bir diziyi büyütmek bir yasak yüzünden değil, karşılığı olan bir komut bulunmadığı için olanaksızdır.

Sınırı Denetleyen Adım Kaynakta Yok

  • SD25 — Sınır ölçümünde yalnız istisnanın sınıf adı basılır. İstisna iletisi konumu ve uzunluğu içerir; o metin basılmaz, çünkü ölçülen şey denetimin varlığıdır.
// Erisim.java — sınır denetimi hangi komutun içinde
public class Erisim {
    static final String K = """
        class ErisimOrnek {
            static int oku(int[] d, int i) { return d[i]; }
            static void yaz(int[] d, int i, int v) { d[i] = v; }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.println("d[i] okuma: " + Olcek.komutlar(K, "ErisimOrnek", "oku"));
        System.out.println("d[i] yazma: " + Olcek.komutlar(K, "ErisimOrnek", "yaz"));

        int[] d = {12, 18, 7};
        System.out.println();
        for (int i : new int[] {0, 2, 3, -1}) {
            try {
                System.out.printf("d[%d] -> %d%n", i, d[i]);
            } catch (RuntimeException e) {
                System.out.printf("d[%d] -> %s%n", i, e.getClass().getSimpleName());
            }
        }
        int boy = -1;
        try {
            System.out.println("new int[boy] -> " + new int[boy].length);
        } catch (RuntimeException e) {
            System.out.println("new int[boy] -> " + e.getClass().getSimpleName() + " (boy = " + boy + ")");
        }
    }
}
d[i] okuma: [aload_0, iload_1, iaload, ireturn]
d[i] yazma: [aload_0, iload_1, iload_2, iastore, return]

d[0] -> 12
d[2] -> 7
d[3] -> ArrayIndexOutOfBoundsException
d[-1] -> ArrayIndexOutOfBoundsException
new int[boy] -> NegativeArraySizeException (boy = -1)

Okuma dört, yazma beş komut. İkisinde de bir karşılaştırma, bir dallanma ya da bir çağrı yok: diziyi yükle, konumu yükle, oku ya da yaz. Sınır denetimi kaynakta yazılmamıştır ve sınıf dosyasında da ayrı bir komut olarak durmaz. Denetim iaload ve iastore komutlarının tanımının içindedir; sanal makine her erişimde onu yapar ve konum aralık dışındaysa istisna fırlatır.

Bu, eklenen adımın üçüncü biçimidir. Birincisi sınıf dosyasına konan bir komuttur — çevirme, çağrı, üretim. İkincisi kaynakta hiç yazılmamış bir üyedir. Üçüncüsü ise komutun kendi tanımına gömülüdür ve komut sayısını hiç değiştirmez; ölçek onu sayamaz, ancak çalışma zamanındaki sonucundan okur. Ölçümdeki dört erişimin ikisi değer veriyor, ikisi istisna; eksi konum ayrı bir kural değil, aynı kuralın sonucudur.

Son satır denetimin ikinci yerini gösteriyor: boyutu eksi olan üretim isteği de çalışma zamanında düşer ve ayrı bir istisna verir, çünkü boyut derleme zamanında bilinmek zorunda değildir.

Çok Boyutlu Dizi Bir Dizi Dizisidir

  • SD26 — Satır uzunlukları ölçümde tek tek yazılır; hiçbiri rastgele üretilmez, hiçbiri ortamdan okunmaz.
// CokBoyut.java — çok boyutlu dizi bir dizi dizisidir
public class CokBoyut {
    static final String K = """
        class BoyutOrnek {
            static int[][] duzgun() { return new int[2][3]; }
            static int[][] tirtikli() { return new int[2][]; }
            static int satirUzunlugu(int[][] d, int i) { return d[i].length; }
        }
        """;

    public static void main(String[] args) throws Exception {
        for (String y : new String[] {"duzgun", "tirtikli", "satirUzunlugu"})
            System.out.printf("%-14s %s%n", y, Olcek.komutlar(K, "BoyutOrnek", y));

        int[][] t = new int[3][];
        t[0] = new int[] {12};
        t[1] = new int[] {18, 7, 4};
        t[2] = new int[0];
        System.out.println();
        System.out.println("dis dizinin sinifi: " + t.getClass().getName());
        System.out.println("bir satirin sinifi: " + t[0].getClass().getName());
        System.out.printf("dis uzunluk %d, satir uzunluklari %d %d %d%n",
                t.length, t[0].length, t[1].length, t[2].length);

        int[][] d = new int[2][3];
        System.out.println("duzgun dizide satirlar ayni nesne mi: " + (d[0] == d[1]));
        d[0][0] = 12;
        System.out.println("d[0][0] = 12 sonrasi d[1][0]: " + d[1][0]);
    }
}
duzgun         [iconst_2, iconst_3, multianewarray, areturn]
tirtikli       [iconst_2, anewarray, areturn]
satirUzunlugu  [aload_0, iload_1, aaload, arraylength, ireturn]

dis dizinin sinifi: [[I
bir satirin sinifi: [I
dis uzunluk 3, satir uzunluklari 1 3 0
duzgun dizide satirlar ayni nesne mi: false
d[0][0] = 12 sonrasi d[1][0]: 0

Üçüncü satır dersin cümlesini yazıyor: d[i].length okuması iki adımdır. Önce aaload ile satır yüklenir — çünkü satır bir referanstır, bir nesnedir — sonra o nesnenin uzunluğu arraylength ile okunur. İki boyutlu dizi tek bir bitişik blok değildir; dış dizinin öğeleri iç dizilere giden referanslardır. Sınıf adı da bunu yazıyor: dış dizinin sınıfı [[I, bir satırınki [I.

İlk iki satır iki ayrı üretimi ayırıyor. Bütün boyutları verilen new int[2][3] tek bir multianewarray komutudur ve dış diziyle birlikte satırları da üretir. Yalnız dış boyutu verilen new int[2][] ise anewarray komutudur; dış dizi üretilir, satırları null kalır. İkincisi satırların ayrı uzunlukta kurulmasına izin verir: ölçümdeki üç satırın uzunlukları 1, 3 ve 0. Son iki satır ise düzgün dizide bile satırların ayrı nesneler olduğunu doğruluyor — d[0] ile d[1] aynı nesne değil, ve birinci satıra yazmak ikinciyi değiştirmiyor. Java’da ayrı bir çok boyutlu dizi tipi yoktur.

Yazma Başına Bir Tip Denetimi

  • SD27 — Aynı yazma üç ayrı kaynakta denenir. İkisi derlenir ve komutları basılır, biri derlenmez ve “derlenmedi” satırıyla geçer; hangisinin hangi zamanda düştüğü ölçümün kendisidir.
// Kovaryans.java — dizi kovaryansı: derlenen yazma, düşen yazma
public class Kovaryans {
    static final String DIZGI = "class KovOrnek { static void yaz(String[] d) { d[0] = %s; } }";
    static final String NESNE = "class KovOrnek { static void yaz(Object[] d) { d[0] = %s; } }";

    static void dene(String ad, String kaynak) {
        try {
            System.out.printf("%-28s -> %s%n", ad, Olcek.komutlar(kaynak, "KovOrnek", "yaz"));
        } catch (Exception e) {
            System.out.printf("%-28s -> %s%n", ad, e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        dene("String[] d; d[0] = \"kuzey\"", DIZGI.formatted("\"kuzey\""));
        dene("String[] d; d[0] = 12", DIZGI.formatted("12"));
        dene("Object[] d; d[0] = 12", NESNE.formatted("12"));

        String[] adlar = new String[2];
        Object[] gorunum = adlar;
        System.out.println();
        System.out.println("Object[] gorunum = adlar;  ayni nesne mi: " + (gorunum == adlar));
        System.out.println("gorunum sinifi: " + gorunum.getClass().getName());
        gorunum[0] = "kuzey";
        System.out.println("gorunum[0] = \"kuzey\" -> adlar[0] = " + adlar[0]);
        try {
            gorunum[1] = 12;
            System.out.println("gorunum[1] = 12 -> " + adlar[1]);
        } catch (RuntimeException e) {
            System.out.println("gorunum[1] = 12 -> " + e.getClass().getSimpleName()
                    + ", adlar[1] = " + adlar[1]);
        }
        int[] sayilar = new int[2];
        sayilar[1] = 12;
        System.out.println("int[] tarafinda ayni yazma -> " + sayilar[1]);
    }
}
String[] d; d[0] = "kuzey"   -> [aload_0, iconst_0, ldc, aastore, return]
String[] d; d[0] = 12        -> derlenmedi: KovOrnek
Object[] d; d[0] = 12        -> [aload_0, iconst_0, bipush, invokestatic, aastore, return]

Object[] gorunum = adlar;  ayni nesne mi: true
gorunum sinifi: [Ljava.lang.String;
gorunum[0] = "kuzey" -> adlar[0] = kuzey
gorunum[1] = 12 -> ArrayStoreException, adlar[1] = null
int[] tarafinda ayni yazma -> 12

Java’da diziler kovaryanttır (array covariance): öğe tipi bir üst tipin altındaysa, dizi de o üst tipin dizisi yerine geçer. String[] bir Object[] yerine geçer, ve ölçümün dördüncü satırı bunu doğruluyor — iki ad aynı nesneyi tutuyor, sınıfı hâlâ [Ljava.lang.String;.

İlk üç satır kovaryansın bedelini yazıyor. String[] olarak adlandırılan diziye sayı yazan kaynak derlenmiyor: derleyici bildirilen tipe bakar ve hatayı derleme zamanında verir. Aynı dizi Object[] olarak adlandırıldığında aynı yazma derleniyor — üstelik sayıyı sarmalayıcıya çeviren bir çağrı da eklenerek. Yazma sözdizim düzeyinde geçerlidir, çünkü Object[] içine her nesne yazılabilir.

Kararı çalışma zamanı veriyor: dizgi yazması geçiyor ve çağırana görünüyor, sayı yazması ArrayStoreException ile düşüyor ve adlar[1] hâlâ null — yazma yapılmadı, yarım da kalmadı. Sanal makine aastore komutunu yürütürken değerin dizinin gerçek öğe tipine uyup uymadığını denetler; uymuyorsa yazmaz.

Buradaki sınırlayıcı ölçüm iki yönlüdür. Birincisi: bu denetim komut sayısına hiç yansımaz. aastore tek komuttur ve iastore da tek komuttur; sınıf dosyasına bakarak denetimin varlığını sayamayız. İkincisi: son satırdaki int[] yazmasında böyle bir denetim yoktur. Öğe tipi ilkelse dizi kovaryansı diye bir soru doğmaz, çünkü ilkel tipli bir dizi başka bir dizi tipinin yerine geçmez. Eklenen adım burada kaybolur — ve kaybolduğu yer, tam olarak alt tip ilişkisinin bittiği yerdir.

Özet

  • Dizi bir nesnedir: kendi sınıfı vardır, üst sınıfı java.lang.Object’tir ve üretimi sınıf dosyasında 3 komuttur; köşeli ayraçlı {12, 18, 7} yazımı ise 15 komuttur, çünkü öğeler tek tek yazılır. Üretim öğeleri üretmez: sayı dizisi sıfırlarla, referans dizisi null değerlerle başlar.
  • d.length bir alan okuması değildir; arraylength komutuna derlenir, k.adet ise getfield komutuna. Uzunluğa yazan kaynak derlenmez.
  • Sınır denetimi ne kaynakta ne de ayrı bir komutta durur; erişim komutunun tanımının içindedir, komut sayısını değiştirmez ve aralık dışı erişimde istisna fırlatır.
  • int[][] ayrı bir tip değil, bir int[] dizisidir: d[i].length iki komuttur, satırlar ayrı nesnelerdir ve ayrı uzunlukta olabilir.
  • Dizi kovaryansı yüzünden Object[] olarak adlandırılan bir dizgi dizisine sayı yazmak derlenir ama çalışma zamanında ArrayStoreException ile düşer; aynı denetim ilkel tipli dizide hiç yoktur.

Sonraki Adım

Bu derste String[] dizisine yazmanın çalışma zamanında denetlendiği ölçüldü; yazılan değer her seferinde hazır bir dizgi nesnesiydi. Sıradaki ders o nesneye bakar: bir dizgi sabiti sınıf dosyasında nerede durur, aynı sabiti iki kez yazmak kaç nesne üretir, ve döngü içinde birleştirilen bir metin yol boyunca kaç nesne bırakır? Dizginin değiştirilemez olması da, dizinin sabit boyutlu olması gibi, kaynakta görünmeyen adımları belirler.

İ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