Ders 03 / 10
Paketler ve İçe Aktarım
İçe aktarım bir yükleme yönergesi değil bir kısaltma kuralıdır: aynı program içe aktarımla ve tam nitelikli adla yazıldığında üretilen sınıf dosyaları bayt bayt aynıdır, statik içe aktarım da hiçbir adım eklemez; buna karşılık paket adı dosyayı değiştirir ve iki ad aynı kısaltmaya sığmaz.
İçindekiler
Önceki ders bir sınıfın ne zaman ve hangi sırayla çalıştığını ölçtü: giriş noktası dört koşulu birden sağlayan tek bir imzaydı, sınıf düzeyi başlatma yalnız bir kez ve ilk kullanımda çalışıyordu, ve kaynakta yazılmayan üyeler sınıf dosyasında duruyordu. Ama sanal makinenin o sınıfı nasıl bulduğu hiç sorulmadı.
İlk derste bir ayrıntı geçmişti: sınıf dosyası çağıracağı tipi adıyla taşıyor ve bu ad tam
biçimindeydi. Oysa kaynakta List yazmak yetiyor. Kısa ad ile tam ad arasındaki fark bir
yerde kapanmak zorunda — bu ders o yerin neresi olduğunu ölçer. Soru şudur: içe aktarımlı
yazım ile tam nitelikli yazım ayrı sınıf dosyaları mı üretiyor, yoksa aynısını mı?
Ad Alanı, Tam Nitelikli Ad, İçe Aktarım
Java’da bir sınıfın gerçek adı yazdığımız kısa ad değildir. Sınıflar paketlere (package)
konur ve bir sınıfın tam nitelikli adı (fully qualified name) paket adı ile kısa adın
birleşimidir. Paket, adların çakışmasını önleyen bir ad alanıdır (namespace): iki ayrı
paketteki iki Proxy ayrı sınıflardır, çünkü tam adları ayrıdır.
İçe aktarım (import) bu tam adı her seferinde yazmaktan kurtaran bir bildirimdir. Adı
yanıltıcıdır: hiçbir şey “içeri aktarılmaz”, hiçbir dosya yüklenmez, hiçbir kod
kopyalanmaz. Bildirim yalnız derleyiciye şunu söyler — bu dosyada List yazdığımda
java.util.List kastediyorum.
Bazı adlar bu bildirimi hiç gerektirmez. Dilin çekirdek paketindeki sınıflar — String,
Object, Math gibi — her kaynak dosyada kısa adlarıyla yazılabilir; o paket her dosyaya
kendiliğinden açıktır. Kaynakta hiçbir içe aktarım görünmemesi bu yüzden “hiçbir dış sınıf
kullanılmıyor” demek değildir. Kısaltmanın var olması ile bildirimin yazılmış olması ayrı
şeylerdir, ve bu ders ikisinin de sınıf dosyasında ne bıraktığını sorar.
JavaScript ve Python müfredatları modül sistemini ve paket yapısını kendi kurslarında kurdu; oradaki kavramlar burada tekrarlanmaz. Bu dersin sorduğu şey ayrıdır ve ölçülebilir: ad çözümlemesi sınıf dosyasında bir iz bırakıyor mu? Aynı biçimde Programlama Temelleri kursunda kurulan kapsam ve gölgeleme de tekrarlanmaz; burada ölçülen, bir adın hangi aşamada tam biçimine kavuştuğudur.
Ölçüm Çekirdeği
Bu derste sayılan şey ne komut ne üyedir: karşılaştırılan şey sınıf dosyasının ham baytlarıdır. Çekirdek bir kaynağı derler, çıkan dosyayı olduğu gibi okur, ve iki yardımcı bilgi verir — dosyanın taşıdığı tam nitelikli ad ile bir yöntem için kaydedilmiş satır numarası.
- CZ11 — Karşılaştırılan kaynaklar aynı satır düzenini taşır. Sınıf dosyası satır numarası kaydeder; düzen sabit tutulmazsa karşılaştırma ad yazımını değil satır düzenini ölçer. Bu yüzden ölçülen kaynaklar tek satıra yazılmıştır.
- CZ12 — Karşılaştırma sınıf dosyasının bütün baytları üzerinde yapılır; hiçbir alan dışarıda bırakılmaz.
- CZ13 — Derlenmeyen kaynak “derlenmedi” olarak yazılır; derleyicinin ürettiği hata metni basılmaz, çünkü ölçülen şey metnin kendisi değil derlemenin sonucudur.
- CZ14 — Dosya adı kısa sınıf adından, dizin ise paket adından üretilir; ölçüm hiçbir gerçek yolu basmaz.
- CZ15 — Ölçüm ortama bağlı hiçbir veri okumaz; sayılan tek şey bayt sayısı, satır numarası ve derlemenin sonucudur.
// Olcek.java — sınıf dosyasının ham baytları, taşıdığı ad ve kaydettiği satır numarası
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.classfile.instruction.LineNumber;
import java.nio.file.*;
import java.util.spi.ToolProvider;
class Olcek {
static byte[] derle(String kaynak, String sinif) throws Exception {
Path d = Files.createTempDirectory("olcek");
Path k = d.resolve(sinif.substring(sinif.lastIndexOf('.') + 1) + ".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 Files.readAllBytes(d.resolve(sinif.replace('.', '/') + ".class"));
}
static String ad(byte[] b) { return ClassFile.of().parse(b).thisClass().asInternalName(); }
static int satir(byte[] b) {
for (MethodModel m : ClassFile.of().parse(b).methods())
if (m.methodName().equalsString("uzunluk"))
for (CodeElement e : m.code().orElseThrow())
if (e instanceof LineNumber ln) return ln.line();
return -1;
}
static String dene(String kaynak, String sinif) {
try { derle(kaynak, sinif); return "derlendi"; }
catch (Exception e) { return "derlenmedi"; }
}
}
İçe Aktarım Sınıf Dosyasında İz Bırakmıyor
Aynı sınıfın dört yazımı derlenir. Birincisi içe aktarım kullanır, ikincisi tam nitelikli ad yazar, üçüncüsü hiç kullanılmayan bir içe aktarım daha ekler, dördüncüsü birincinin aynısıdır ama içe aktarımı alt satıra alır. İlk üçü tek satırdır ve bu bilinçlidir — dördüncüsü niçin gerektiğini birazdan söyleyecek.
// Ana.java — içe aktarım sınıf dosyasında iz bırakıyor mu
import java.util.Arrays;
public class Ana {
static String kaynak(String onek, String tip) {
return onek + "class Sayac { static int uzunluk(" + tip + " l) { return l.size(); } }\n";
}
public static void main(String[] args) throws Exception {
byte[] kisa = Olcek.derle(kaynak("import java.util.List; ", "List"), "Sayac");
byte[] tam = Olcek.derle(kaynak("", "java.util.List"), "Sayac");
byte[] fazla = Olcek.derle(
kaynak("import java.util.List; import java.util.ArrayList; ", "List"), "Sayac");
byte[] altSatir = Olcek.derle("import java.util.List;\n" + kaynak("", "List"), "Sayac");
System.out.printf("%-24s %4d bayt satir %d ad %s%n",
"ice aktarimli yazim", kisa.length, Olcek.satir(kisa), Olcek.ad(kisa));
System.out.printf("%-24s %4d bayt satir %d ad %s%n",
"tam nitelikli yazim", tam.length, Olcek.satir(tam), Olcek.ad(tam));
System.out.printf("%-24s %4d bayt satir %d%n",
"kullanilmayan ek ile", fazla.length, Olcek.satir(fazla));
System.out.printf("%-24s %4d bayt satir %d%n",
"ice aktarim alt satirda", altSatir.length, Olcek.satir(altSatir));
System.out.println("ilk ikisi bayt bayt ayni : " + Arrays.equals(kisa, tam));
System.out.println("birinci ile ucuncu ayni : " + Arrays.equals(kisa, fazla));
System.out.println("birinci ile dorduncu ayni : " + Arrays.equals(kisa, altSatir));
}
}
ice aktarimli yazim 304 bayt satir 1 ad Sayac tam nitelikli yazim 304 bayt satir 1 ad Sayac kullanilmayan ek ile 304 bayt satir 1 ice aktarim alt satirda 304 bayt satir 2 ilk ikisi bayt bayt ayni : true birinci ile ucuncu ayni : true birinci ile dorduncu ayni : false
İlk iki dosya bayt bayt aynıdır. Kaynaklardan biri List, öbürü java.util.List yazıyor
ve ikisi tek bir bayt farkla bile ayrılmıyor. Bunun anlamı kesindir: içe aktarım sınıf
dosyasında hiçbir iz bırakmaz. Sınıf dosyası zaten tam nitelikli adı taşır — kısa yazım,
derleyicinin o tam adı bulmasını sağlayan bir kuraldan ibarettir. Ad çözümlemesi derleme
zamanında biter; çalışma zamanına içe aktarım diye bir şey ulaşmaz. Aynı sonucun tersi de
doğrudur: tam nitelikli yazmak bir ek yük getirmez, çünkü kısaltılan şey yalnız kaynak
metindir.
Üçüncü satır aynı sonucu bir kez daha veriyor. Hiç kullanılmayan bir içe aktarım eklendiğinde dosya yine aynıdır. Kullanılmayan bir bildirim bir ağırlık değildir, çünkü kullanılan bildirim de bir ağırlık değildir: ikisinin de karşılığı sıfır adımdır.
Dördüncü satır ölçümün kendi denetimidir. İçe aktarım alt satıra alındığında dosya aynı boyda kalıyor ama artık bayt bayt aynı değil, ve ayrımın nerede olduğu görünüyor: kaydedilen satır numarası 1 yerine 2. Yani sınıf dosyası kaynağın her şeyine kayıtsız değildir — satır düzenini kaydeder. “İz bırakmıyor” iddiası bu yüzden gevşek bir iddia olamaz; ancak düzen sabit tutulduğunda ölçülen şey ad yazımı olur. Karşı örneği olmayan bir eşitlik iddiası neyi ölçtüğünü söylemiş sayılmaz.
Paket Adı Dosyayı Değiştirir
İçe aktarım iz bırakmıyorsa, paket bildirimi de bırakmaz mı? İkisi de dosyanın başında duran birer satırdır. Ölçüm ayrı yanıt verir.
// Paket.java — paket adı sınıf dosyasını değiştiriyor mu
public class Paket {
static final String GOVDE =
"class Sayac { static int uzunluk(java.util.List l) { return l.size(); } }\n";
public static void main(String[] args) throws Exception {
byte[] paketsiz = Olcek.derle(GOVDE, "Sayac");
byte[] paketli = Olcek.derle("package depo; " + GOVDE, "depo.Sayac");
System.out.println("paketsiz: " + paketsiz.length + " bayt, ad " + Olcek.ad(paketsiz));
System.out.println("paketli : " + paketli.length + " bayt, ad " + Olcek.ad(paketli));
}
}
paketsiz: 304 bayt, ad Sayac paketli : 309 bayt, ad depo/Sayac
Paket bildirimi eklendiğinde dosya beş bayt büyüyor ve taşıdığı ad Sayac yerine
depo/Sayac oluyor. Eklenen önek tam olarak beş karakterdir. Sınıf dosyası adı bir metin
olarak taşıdığına göre büyüme de o metnin büyümesidir.
Ayrım şuradan gelir: içe aktarım kaynağı yazan tarafın kolaylığıdır, paket ise sınıfın kimliğidir. Birincisi bir dosyada geçerlidir ve dosyayla biter; ikincisi sınıfın adının parçasıdır ve onunla birlikte her yere gider. Aynı gövdeden iki ayrı sınıf çıkmıştır, çünkü tam nitelikli adları ayrıdır.
Ölçümün kendisi ikinci bir şeyi daha kanıtlıyor. Çekirdek, derlenmiş dosyayı ararken yolu
tam nitelikli addaki noktaları bölü işaretine çevirerek kuruyor; depo.Sayac ölçümü
başarıyla okunduğuna göre derleyici o dosyayı bir depo dizininin altına yazmıştır. Paket bir
isimlendirme sözleşmesi değildir: hem adın parçasıdır hem de sınıfın nerede aranacağını
belirler. Kaynakta tek bir sözcük olarak duran bildirim, dosya düzeninde bir dizin olarak
karşılık bulur.
Sınırlayıcı Ölçüm: Sıfır Adım, Sıfır Kural Değil
Kısaltmanın bir biçimi daha var. Statik içe aktarım (static import) bir sınıfın statik
üyesini de kısa adıyla yazdırır: Math.max yerine yalnız max. Bu da sıfır adım mı ekliyor,
ve kısaltmanın hiç mi bedeli yok?
// Statik.java — statik içe aktarım ve ad çakışması
import java.util.Arrays;
public class Statik {
static String kaynak(String onek, String cagri) {
return onek + "class Enb { static int enb(int a, int b) { return " + cagri + "(a, b); } }\n";
}
public static void main(String[] args) throws Exception {
byte[] tam = Olcek.derle(kaynak("", "Math.max"), "Enb");
byte[] statik = Olcek.derle(kaynak("import static java.lang.Math.max; ", "max"), "Enb");
System.out.println("Math.max yazimi : " + tam.length + " bayt");
System.out.println("statik ice aktarimli : " + statik.length + " bayt");
System.out.println("bayt bayt ayni : " + Arrays.equals(tam, statik));
}
}
Math.max yazimi : 274 bayt statik ice aktarimli : 274 bayt bayt bayt ayni : true
Statik içe aktarım da hiçbir adım eklemez; iki dosya bayt bayt aynıdır. Yöntemin sahibi olan sınıfın adı çağrının içinde zaten yazılıdır, kısa yazım o adı yalnız kaynak metinden siler.
Buraya kadar her ölçüm sıfır verdi. Kısaltmanın hiç mi bedeli yok? Bedel çalışma zamanında değil, derleme zamanında ödenir ve gerçektir. Aşağıdaki beş durum aynı iki sınıfı — iki ayrı pakette bulunan, kısa adları aynı iki sınıfı — beş ayrı biçimde yazar.
// Cak.java — ad çakışması hangi durumda derleme hatasıdır
public class Cak {
public static void main(String[] args) {
String[][] durum = {
{"iki tam ad, kullanilmasa da",
"import java.net.Proxy; import java.lang.reflect.Proxy; class Cakisma { }"},
{"iki toplu ad, kullanilmadan",
"import java.net.*; import java.lang.reflect.*; class Cakisma { }"},
{"iki toplu ad, kullanilinca",
"import java.net.*; import java.lang.reflect.*; class Cakisma { Proxy p; }"},
{"tam ad + toplu ad, kullanilinca",
"import java.net.Proxy; import java.lang.reflect.*; class Cakisma { Proxy p; }"},
{"ice aktarimsiz, iki tam adla",
"class Cakisma { java.net.Proxy a; java.lang.reflect.Proxy b; }"},
};
for (String[] d : durum)
System.out.printf("%-32s %s%n", d[0], Olcek.dene(d[1], "Cakisma"));
}
}
iki tam ad, kullanilmasa da derlenmedi iki toplu ad, kullanilmadan derlendi iki toplu ad, kullanilinca derlenmedi tam ad + toplu ad, kullanilinca derlendi ice aktarimsiz, iki tam adla derlendi
Beş satır ad çözümlemesinin kuralını baştan sona veriyor. Birinci satır: iki sınıfı adlarıyla tek tek içe aktarmak, o adlar hiç kullanılmasa bile derleme hatasıdır — kısaltma tablosu kaynağın kendisinden önce kurulur ve bir kısa ad iki tam adı gösteremez. İkinci ile üçüncü satır bunun karşıtını gösteriyor: paketin tamamını toplu biçimde içe aktarmak çakışmayı tek başına doğurmuyor, hata ancak belirsiz ad kullanıldığında çıkıyor. Toplu içe aktarım bir karar değil, bir arama listesidir.
Dördüncü satır sıralamayı söylüyor: adıyla yazılmış bir içe aktarım, toplu içe aktarımdan öncelikli olduğu için belirsizlik doğmuyor. Beşinci satır ise sınırı çiziyor — iki sınıf tam adlarıyla yazıldığında yan yana sorunsuz kullanılabiliyor. Yani ikisini birlikte kullanmak yasak değildir, ikisini aynı kısaltmaya sığdırmak yasaktır.
Kursun ölçüsü buradan bir kez daha okunur. İlk derste eklenen adımın olmadığı bir durum sabit katlamayla gösterilmişti; burada eklenen adımın olmadığı üç durum daha var ve üçünde de bedeli ödeyen taraf derleyicidir. Bir biçimin çalışma zamanında hiçbir şeye mal olmaması, o biçimin kuralsız olduğu anlamına gelmez.
Kursun ölçüsü buradan bir kez daha okunur. İlk derste eklenen adımın olmadığı bir durum sabit katlamayla gösterilmişti; burada eklenen adımın olmadığı üç durum daha var ve üçünde de bedeli ödeyen taraf derleyicidir. Bir biçimin çalışma zamanında hiçbir şeye mal olmaması, o biçimin kuralsız olduğu anlamına gelmez.
Özet
- Bir sınıfın gerçek adı tam nitelikli adıdır; paket bir ad alanıdır ve sınıf dosyası bu adı metin olarak taşır.
- İçe aktarımlı yazım ile tam nitelikli yazımın ürettiği sınıf dosyaları bayt bayt aynıdır; kullanılmayan bir içe aktarım da dosyayı değiştirmez. Ad çözümlemesi derleme zamanında biter.
- Sınıf dosyası kaynağın satır düzenini kaydeder: içe aktarım alt satıra alındığında dosya aynı boyda kalır ama artık aynı değildir, çünkü kayıtlı satır numarası 1 yerine 2 olur.
- Paket bildirimi dosyayı değiştirir: taşınan ad
Sayacyerinedepo/Sayacolur ve dosya eklenen önek kadar, beş bayt büyür. - Statik içe aktarım da sıfır adım ekler; buna karşılık aynı kısa ada sahip iki sınıfı adıyla içe aktarmak, o adlar hiç kullanılmasa bile derleme hatasıdır. Toplu içe aktarımda hata ancak belirsiz ad kullanıldığında çıkar ve adıyla yazılmış içe aktarım önceliklidir.
- Kısaltmanın çalışma zamanındaki bedeli sıfırdır, derleme zamanındaki kuralı gerçektir: bir dosyada bir kısa ad tek bir tam adı gösterebilir, ama iki sınıf tam adlarıyla yan yana kullanılabilir.
Sonraki Adım
Bu konu boyunca ölçülen şey hep derleyicinin ne eklediği oldu: bir adım, bir üye, bir ad. Üçünde de karar derleme zamanında verildi ve çalışma zamanına yalnız sonucu ulaştı — hangi sınıfın kastedildiği, sınıf dosyası yazılırken çoktan bellidir. Ad çözümlemesi derleme zamanında bitiyorsa, değerin kendisi ne zaman belirleniyor? Sıradaki konu bunu ölçmeye ilkel tiple sarmalayıcı arasındaki geçişten başlar: bir tam sayıyı bir sarmalayıcıya atamak kaynakta tek bir eşittir işaretidir, sınıf dosyasında ise bir çağrı ve üretilen bir nesnedir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.