İçeriğe geç
academia.sh

Ders 06 / 10

İşleçler ve İfadeler

Tek karakterlik yazımlar sınıf dosyasına sessiz adımlar bırakır: `int` ile `long` toplandığında bir çevirme eklenir, `adet += 300` kaynakta yazılmayan bir daraltma koyar ve sonuç 45 çıkar, kısa devre bir dal komutu olarak belirir — sabitlerde ise hiçbir adım eklenmez.

İçindekiler

Önceki ders adın sınıf dosyasında nereye yazıldığını ölçtü: alana mı, yuvaya mı. Adlar yerlerine oturduğunda sıra onları birleştiren işleçlere gelir. Bir işleç kaynakta tek karakterdir; sınıf dosyasında ise bir ya da birkaç komuttur ve bu komutların bir bölümü kaynakta hiç yazılmaz. Bu ders o bölümü sayar.

Programlama Temelleri kursu işleçleri, önceliği, kısa devre değerlendirmeyi ve açık/örtük dönüşümü kavram olarak kurmuştu — bunlar tekrarlanmaz. Orada bir dönüşümün ne zaman genişletme, ne zaman daraltma olduğu ve daraltmanın neden sessiz bilgi kaybı üretebildiği anlatıldı. Burada ölçülen şey, o daraltmayı kimin eklediğidir: kaynakta hiçbir yerde dönüşüm yazmayan bir satır, sınıf dosyasında bir çevirme komutu taşıyabilir.

İşleç Bir Kısaltmadır

Java’nın aritmetik işleçleri yalnız dört genişlikte çalışır: int, long, float ve double. byte, short ve char için ayrı toplama komutu yoktur. Bunun sonucu şudur: küçük tiplerdeki değerler işleme girmeden önce int’e yükseltilir ve iki işlenen farklı genişlikteyse dar olan geniş olana çevrilir. Buna sayısal yükseltme denir ve kaynakta görünmez.

Yükseltmenin tersi de vardır. Bileşik atama — +=, -=, *= — sol tarafın tipini korumak zorundadır. byte bir değişkene += ile bir int eklendiğinde toplama int genişliğinde yapılır, sonra sonuç geri byte’a daraltılır. Bu daraltma kaynakta yazılmaz; yazılsaydı derleyici zaten istemezdi. Aynı işlem adet = adet + 300 biçiminde yazılsaydı derleme hatası verirdi, çünkü orada daraltmayı programcının yazması gerekir.

İki yön arasındaki asıl ayrım güvenliktir. Dar bir tipten geniş bir tipe geçiş genişletmedir ve hiçbir bilgi kaybetmez; derleyici onu sormadan yapar. Geniş bir tipten dar bir tipe geçiş daraltmadır ve sığmayan bitler atılır; derleyici onu normalde yapmaz, programcının yazmasını ister. Bileşik atama bu kuralın tek gediğidir: sol tarafın tipi zaten bellidir, bu yüzden derleyici daraltmayı isteyeceği yerde kendisi koyar. Ölçüm bu gediğin sınıf dosyasındaki karşılığını arar.

Ders bu iki yönü kurs boyunca sürdürülen depo kaydının iki alanı üzerinde ölçer: int adet ve long agirlik. Aradaki genişlik farkı, yükseltmenin ölçülebileceği en dar örnektir.

Ölçüm Çekirdeği

Çekirdek önceki derslerinkidir; bu derste iki ölçüm okur. Birincisi çevirme komutlarıdır — sınıf dosyasında bir genişliği başka bir genişliğe çeviren komutlar ayrı bir aileyi oluşturur ve adları çevrilen iki tipi taşır (i2l, i2b). İkincisi dal komutlarının sayısıdır: akışı bir noktadan başka bir noktaya götüren her komut sayılır.

  • SD11 — Sağ sütun sınıf dosyasındaki çevirmeleri, dinamik çağrıları ve çağrıları listeler; kaynakta yazılmış olanı okuyucu düşer, kalanı derleyici eklemiştir.
  • SD12 — Ölçülen bütün yöntemler statiktir ve gövdeleri tek satırdır; komut sayısındaki fark yöntem çağrısı düzeninden değil, yalnız ifadenin kendisinden gelir.
// Olcek.java — işlecin sınıf dosyasına bıraktığı çevirme, çağrı ve dal komutunu 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, int dal, List<String> eklenen) {}

    static List<Yontem> oku(String kaynak, String sinif) throws Exception {
        Path d = Files.createTempDirectory("olcek");
        Files.writeString(d.resolve(sinif + ".java"), kaynak);
        PrintWriter bos = new PrintWriter(Writer.nullWriter());
        if (ToolProvider.findFirst("javac").orElseThrow().run(bos, bos, "-d", d.toString(),
                d.resolve(sinif + ".java").toString()) != 0)
            throw new IllegalStateException("derlenmedi: " + sinif);
        List<Yontem> yontemler = new ArrayList<>();
        for (MethodModel m : ClassFile.of().parse(d.resolve(sinif + ".class")).methods()) {
            if (m.code().isEmpty()) continue;
            List<String> eklenen = new ArrayList<>();
            int komut = 0, dal = 0;
            for (CodeElement e : m.code().get()) {
                if (e instanceof Instruction) komut++;
                if (e instanceof BranchInstruction) dal++;
                if (e instanceof ConvertInstruction c)
                    eklenen.add("cevirme " + c.opcode().name().toLowerCase(Locale.ROOT));
                else if (e instanceof InvokeDynamicInstruction id)
                    eklenen.add("dinamik:" + id.name().stringValue());
                else if (e instanceof InvokeInstruction iv)
                    eklenen.add(iv.name().stringValue());
            }
            yontemler.add(new Yontem(m.methodName().stringValue(), komut, dal, eklenen));
        }
        return yontemler;
    }
}

On Üç İfade, Sınıf Dosyasında Ne Bırakıyor

Ölçülen sınıf on üç yöntem taşır ve her biri tek bir ifadedir. daralt ile acikDaralt aynı işi yapar; tek fark, daraltmanın kaynakta yazılıp yazılmadığıdır. gecCevirme ile erkenCevirme aynı çarpımı yapar; tek fark, çevirmenin çarpımdan önce mi sonra mı istendiğidir. sabitAritmetik, sonSabitCarpim ve degiskenCarpim aynı çarpımı üç ayrı yazımla verir.

// Islec.java — on üç ifade, sınıf dosyasında ne bırakıyor
public class Islec {
    static final String KAYNAK = """
        class Depo {
            static long yukselt(int adet, long agirlik) { return adet + agirlik; }
            static byte daralt(byte adet) { adet += 300; return adet; }
            static byte acikDaralt(byte adet) { adet = (byte) (adet + 300); return adet; }
            static int kucukToplam(byte adet, short raf) { return adet + raf; }
            static long gecCevirme(int adet, int birim) { return adet * birim; }
            static long erkenCevirme(int adet, int birim) { return (long) adet * birim; }
            static String birlestir(String ad, int adet) { return ad + adet; }
            static String sabitBirlestir() { return "vi" + "da"; }
            static int sabitAritmetik() { return 24 * 60 * 60; }
            static final int RAF = 24;
            static int rafSayisi = 24;
            static int sonSabitCarpim() { return RAF * 60 * 60; }
            static int degiskenCarpim() { return rafSayisi * 60 * 60; }
            static boolean kisaDevre(int adet, long agirlik) { return adet > 0 && agirlik > 0; }
            static boolean tamDeger(int adet, long agirlik) { return (adet > 0) & (agirlik > 0); }
        }
        """;

    public static void main(String[] args) throws Exception {
        System.out.printf("%-16s %6s %4s  %s%n", "yontem", "komut", "dal", "kaynakta yazilmayan adim");
        for (Olcek.Yontem y : Olcek.oku(KAYNAK, "Depo")) {
            if (y.ad().startsWith("<")) continue;
            System.out.printf("%-16s %6d %4d  %s%n", y.ad(), y.komut(), y.dal(),
                    y.eklenen().isEmpty() ? "-" : String.join(", ", y.eklenen()));
        }
    }
}
yontem            komut  dal  kaynakta yazilmayan adim
yukselt               5    0  cevirme i2l
daralt                7    0  cevirme i2b
acikDaralt            7    0  cevirme i2b
kucukToplam           4    0  -
gecCevirme            5    0  cevirme i2l
erkenCevirme          6    0  cevirme i2l, cevirme i2l
birlestir             4    0  dinamik:makeConcatWithConstants
sabitBirlestir        2    0  -
sabitAritmetik        2    0  -
sonSabitCarpim        2    0  -
degiskenCarpim        6    0  -
kisaDevre            10    3  -
tamDeger             14    4  -

İlk satır sayısal yükseltmenin sınıf dosyasındaki adıdır. adet + agirlik ifadesinde adet bir int, agirlik bir long; toplama komutu ikisini birden alamaz, bu yüzden derleyici araya bir i2l koyar. Kaynakta hiçbir dönüşüm yazmaz. Beş komutun biri, programcının yazmadığı bir çeviridir.

İkinci satır bu dersin en sessiz adımıdır. adet += 300 yazımı yedi komut üretir ve içinde bir i2b vardır: toplama int genişliğinde yapılır, sonuç byte’a daraltılır. Daraltmayı derleyici kendiliğinden ekler. Bu, önceki kursta anlatılan sessiz bilgi kaybının sınıf dosyasındaki karşılığıdır — kavram orada kuruldu, burada eklendiği yer gösteriliyor.

Bir alt satır bunun ne kadar birebir olduğunu ölçüyor. acikDaralt daraltmayı kaynakta yazıyor ve sınıf dosyasında daralt ile aynı çıkıyor: aynı 7 komut, aynı i2b. İki yazım sınıf dosyasında ayırt edilemez. += bir işleç değil, bu satırın kısaltmasıdır; kısalttığı şeyin içinde bir dönüşüm vardır ve kısaltma onu gizler.

Dördüncü satır yükseltmenin her zaman komut üretmediğini gösteriyor. byte ile short toplandığında ikisi de int’e yükselir, ama sınıf dosyasında hiçbir çevirme yoktur: dört komut, sıfır eklenen adım. Bu tipler zaten int genişliğinde yüklenir, dolayısıyla yükseltmenin ödediği bir komut yoktur. Yükseltme bir kural olarak her yerde vardır; komut olarak yalnız genişlik gerçekten değiştiğinde belirir.

Sonraki iki satır çevirmenin yerini karşılaştırıyor. İkisi de aynı çarpımı long olarak döndürüyor. gecCevirme tek bir i2l taşıyor ve o çevirme çarpımdan sonradır: iki int çarpılıyor, sonuç long’a çevriliyor. erkenCevirme iki i2l taşıyor — biri kaynakta yazılan dönüşüm, öteki ikinci işlenen için eklenen yükseltme — ve çarpım artık long genişliğinde yapılıyor. Bir komut fazla, ama başka bir hesap.

Birleştirme satırları aynı + işlecini iki ayrı sonuca bağlıyor. Değişken içeren birleştirme dört komutta bir dinamik çağrı bırakıyor; iki sabitin birleştirilmesi ise iki komut ve hiçbir eklenen adım. Aynı işleç, biri bir çağrı, biri sıfır adım.

Son iki satır kısa devre değerlendirmenin sınıf dosyasındaki izidir. && kullanan biçim 10 komut ve 3 dal, & kullanan biçim 14 komut ve 4 dal üretiyor. & biçimi iki karşılaştırmayı da doğruluk değerine çevirmek zorundadır; && ilk karşılaştırma yanlış çıktığında ikinciye hiç girmeyen bir dal koyar. Kısa devre bir çalışma zamanı kolaylığı değil, sınıf dosyasında duran bir atlamadır.

Eklenen Çevirmenin Görünen Sonucu

Buraya kadar sayılan adımların hepsinin bir değeri değiştiren karşılığı vardır.

  • SD13 — Taşma ölçümünde kayıt sayısı 100000, birim ağırlık 50000 gramdır; çarpım int genişliğinde tutulamayacak kadar büyüktür ve iki yazım arasındaki tek fark çevirmenin yeridir.
  • SD14 — Kısa devre ölçümünde depo üç kayıt taşır ve ikisinin adet alanı sıfırdır; sağ işlenen bir yöntem çağrısıdır ve o yöntem her çağrılışında sayacı artırır, böylece atlanan dalın kaç kez atlandığı sayılabilir.
// Sonuc.java — eklenen çevirmenin ve atlanan dalın görünen sonucu
public class Sonuc {
    record Kayit(String ad, byte adet, long agirlik) {}

    static int cagri = 0;

    static boolean agirMi(Kayit k) { cagri++; return k.agirlik() > 0; }

    public static void main(String[] args) {
        byte adet = 1;
        adet += 300;
        System.out.println("byte adet = 1; adet += 300      ->  " + adet);

        int kayit = 100000, birim = 50000;
        System.out.println("long gecCevirme(100000, 50000)  ->  " + (long) (kayit * birim));
        System.out.println("long erkenCevirme(100000,50000) ->  " + (long) kayit * birim);

        Kayit[] depo = { new Kayit("vida", (byte) 0, 40L),
                         new Kayit("civata", (byte) 3, 90L),
                         new Kayit("somun", (byte) 0, 12L) };
        cagri = 0;
        int a = 0;
        for (Kayit k : depo) if (k.adet() > 0 && agirMi(k)) a++;
        int kisaDevre = cagri;
        cagri = 0;
        int b = 0;
        for (Kayit k : depo) if ((k.adet() > 0) & agirMi(k)) b++;
        System.out.println("&&  sonuc: " + a + "  agirMi cagrisi: " + kisaDevre);
        System.out.println("&   sonuc: " + b + "  agirMi cagrisi: " + cagri);
    }
}
byte adet = 1; adet += 300      ->  45
long gecCevirme(100000, 50000)  ->  705032704
long erkenCevirme(100000,50000) ->  5000000000
&&  sonuc: 1  agirMi cagrisi: 1
&   sonuc: 1  agirMi cagrisi: 3

İlk satır i2b komutunun ödettiğidir. Bir kayda 300 adet eklendi ve sonuç 45 oldu. Kaynakta hiçbir dönüşüm yazılı değildir, derleyici uyarı vermez ve program çalışmaya devam eder. Kararı programcı değil, eklenen adım vermiştir.

İkinci ve üçüncü satırlar çevirmenin yerinin neye mal olduğunu gösteriyor. Çarpım int genişliğinde yapıldığında sonuç 705032704, long genişliğinde yapıldığında 5000000000. Sol taraftaki değişkenin long olması yetmez — sonucun genişliği, çarpımdan sonra yapılan bir çeviriyle kurtarılamaz. Yükseltmenin işlem başına uygulandığını gösteren en açık ölçüm budur.

Son iki satır kısa devrenin çalışma zamanındaki karşılığıdır. İki biçim de aynı sonucu buluyor: 1 kayıt. Ödenen çağrı sayısı ayrı: && biçimi sağ işleneni 1 kez, & biçimi 3 kez çalıştırıyor. Sınıf dosyasındaki bir dal komutu farkı, burada iki yöntem çağrısı farkına dönüşüyor. Sağ işlenen bir yan etki taşısaydı iki biçim aynı programı vermezdi; & ile && arasındaki seçim üslup değildir.

Sınırlayıcı Ölçüm: Sabitte Eklenen Adım Yok

Tablodaki üç satır aynı çarpımı üç ayrı yazımla veriyor ve üçünde de eklenen adım sıfır. Komut sayıları ayrı: 2, 2 ve 6.

  • SD15 — Üç yöntem de aynı sayıyı döndürür; aralarındaki tek fark, çarpanların derleme zamanında bilinip bilinmediğidir.

sabitAritmetik iki komutta bitiyor: sınıf dosyasında çarpma komutu yok, doğrudan sonuç yazılı. Derleme zincirinde tanıtılan sabit katlama, burada ölçülen bir şeye dönüşüyor. sonSabitCarpim de iki komut — çünkü RAF bir static final alandır ve başlangıç değeri derleme zamanında bellidir; derleyici adı değil, değeri yerine koyar ve katlamayı yapar. degiskenCarpim altı komut: rafSayisi sonradan değişebileceği için değeri alınıyor ve iki çarpma çalışma zamanında yapılıyor.

Bu üçlü dersin tezini sınırlıyor. Eklenen adım bir kural değil, bir koşula bağlı karşılıktır: işlenenlerin genişlikleri ayrıldığında çevirme eklenir, sol taraf dar olduğunda daraltma eklenir, işlenenlerden biri değişken olduğunda dinamik çağrı eklenir. İşlenenlerin hepsi derleme zamanında biliniyorsa eklenecek bir şey kalmaz — tersine, o zaman kaynakta yazılı olan işlem sınıf dosyasından çıkar. Eklenen adım bazen çıkarmadır.

Sınırın pratik karşılığı da vardır: bir sabiti static final bildirmek yalnız değişmezlik bildirimi değildir, derleyiciye o değeri yerine koyma izni verir. Aynı sabit final olmadan bildirildiğinde çalışma zamanında iki çarpma ödenir. Aradaki fark bu ölçümde dört komuttur ve kaynağa bakarak görünmez.

Özet

  • Sayısal yükseltme sınıf dosyasında bir çevirme komutudur: int ile long toplandığında beş komutun biri kaynakta yazılmayan bir i2l’dir.
  • Bileşik atama daraltmayı kendiliğinden ekler; adet += 300 yedi komutta bir i2b bırakır ve sonucu 45 yapar, derleyici uyarı üretmez. Daraltmanın kaynakta yazıldığı biçim sınıf dosyasında bundan ayırt edilemez.
  • Yükseltme her zaman komut üretmez: byte ile short toplandığında sınıf dosyasında hiçbir çevirme yoktur, çünkü ikisi de zaten int genişliğinde yüklenir.
  • Yükseltme işlem başına uygulanır: aynı çarpım int genişliğinde 705032704, long genişliğinde 5000000000 verir ve sonucu long bir değişkene atamak bunu düzeltmez.
  • Kısa devre bir dal komutudur: && biçimi 10 komut ve 3 dal, & biçimi 14 komut ve 4 dal üretir; çalışma zamanında sağ işlenen 1 ve 3 kez çalışır.
  • İşlenenlerin hepsi derleme zamanında biliniyorsa eklenen adım kalmaz: aynı çarpım sabitlerle 2, değişkenle 6 komuttur ve üçünde de eklenen adım sıfırdır.

Sonraki Adım

Bu derste akışı bir noktadan başka bir noktaya götüren dal komutu ilk kez ölçüldü ve bir işlecin yan ürünü olarak ortaya çıktı. Sıradaki ders dalı asıl konusu yapar: if ve switch ile yazılan aynı davranış sınıf dosyasında iki ayrı komuta derlenir ve iki komutun arama maliyeti aynı değildir. Etiketli atlamanın iç içe döngülerde ne kazandırdığı adım sayısıyla ölçülür, gelişmiş for döngüsünün dizide ve listede neden ayrı adım ürettiği gösterilir — ve kazancın okunabilirlikte ne ödettiği aynı yerde yazı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