Ders 02 / 13
Erişim Belirteçleri
Dört görünürlük düzeyi ile dört çağrı konumu çaprazlandığında on altı çiftin onu derlenir; kararın tamamı derleme zamanında verilir ve çalışma zamanında üye yerinde durur — sınıf dosyasında dört alanın dördü de vardır, ayrımı yalnız bir bayrak taşır. Korumalı düzey paket içini de açar.
İçindekiler
Önceki ders bir nesnenin kuruluş anını ölçtü ve sonunda beklenmedik bir yerden bir belirteç
çıktı: private yazılan yöntem alt sınıfa hiç miras kalmadığı için yapıcı tuzağı orada
doğmadı. O tek sözcük bir kararın tarafını değiştirdi, ama sözcüğün kendisi ölçülmedi.
Bu ders onu ölçer. Sorusu şudur: bir üyenin kimden görüldüğüne kim karar veriyor, ve bu karar ne zaman veriliyor? Yanıt bu kursta beklenenden daha kesindir. Görünürlük, kursun ölçtüğü sekiz kararın en uçtaki örneğidir: kararın tamamı bildirilen tipe ve derleme zamanına aittir, çalışma zamanının hiçbir katkısı yoktur. Ölçüm bunu iki yönden gösterir — derlenen ve derlenmeyen çiftleri sayarak, sonra üyenin çalışma zamanında nerede durduğuna bakarak.
Görünürlük Kimin Sorusu
Java dört görünürlük düzeyi tanır ve üçünün adı vardır: private, protected, public.
Dördüncüsü adsızdır — hiçbir belirteç yazılmadığında geçerli olan düzeydir ve paket
düzeyi olarak anılır. Bu adsızlık bir ayrıntı değildir: bir üyeyi belirteçsiz bırakmak bir
seçim yapmamak değil, paket içine açık bırakmayı seçmektir.
Düzeylerin tek başına anlamı yoktur; anlam erişimin nereden denendiğiyle birlikte doğar. Bu yüzden ölçüm bir çaprazlamadır: dört düzeyin her biri dört ayrı konumdan denenir. Konumlar şunlardır — üyeyi bildiren sınıfın kendi içi, aynı paketteki ilgisiz bir sınıf, başka paketteki bir alt sınıf, başka paketteki ilgisiz bir sınıf. On altı çift çıkar ve her çift tek bir soruya indirgenir: bu kaynak derleniyor mu?
Nesneye Dayalı Python ve Tipler kursu kapsüllemeyi ad düzeyinde bir sözleşme olarak kurmuştu; orada görünürlük bir anlaşmaydı ve tekrarlanmaz. Buradaki ayrım yapısaldır: kararı veren dilin kendi derleyicisidir, ve reddi bir uyarı değil, derlenmemiş bir kaynaktır.
Ölçüm Çekirdeği
Ölçüm hiçbir programı çalıştırmaz. Her çift için bir kaynak kümesi üretilir, derleyici program içinden çağrılır ve yalnız çıkış durumu okunur.
- SN7 — Ölçümün kâhini kurgunun kendisidir: her senaryoda üye tek bir yerde bildirilir ve tek bir yerden okunur; derleme başarısızlığının başka bir nedeni yoktur.
- SN8 — Derleyicinin ürettiği bütün iletiler yutulur; ölçülen tek şey çıkış durumudur. Kaynağın neden reddedildiği metinden değil, kurgudan okunur.
- SN9 — Bütün senaryolarda aynı üye kullanılır:
Ustsınıfınınetiketadlı alanı. Düzeyler arasında değişen tek şey o alanın belirtecidir.
// Derle.java — kaynak kümesini derler; sonuç yalnız derlendi ya da derlenmedi
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.classfile.*;
import java.lang.reflect.AccessFlag;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;
class Derle {
static boolean olur(Map<String, String> dosya) throws Exception {
Path d = Files.createTempDirectory("erisim");
List<String> arg = new ArrayList<>(List.of("-d", d.resolve("cikti").toString()));
for (var g : dosya.entrySet()) {
Path p = d.resolve(g.getKey());
Files.createDirectories(p.getParent());
Files.writeString(p, g.getValue());
arg.add(p.toString());
}
PrintWriter bos = new PrintWriter(Writer.nullWriter());
return ToolProvider.findFirst("javac").orElseThrow()
.run(bos, bos, arg.toArray(String[]::new)) == 0;
}
static List<String> alanBayraklari(String kaynak, String sinif) throws Exception {
Path d = Files.createTempDirectory("bayrak");
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);
List<String> satir = new ArrayList<>();
for (FieldModel f : ClassFile.of().parse(d.resolve(sinif + ".class")).fields()) {
var b = f.flags();
satir.add(f.fieldName().stringValue() + " " + (b.has(AccessFlag.PRIVATE) ? "ACC_PRIVATE"
: b.has(AccessFlag.PROTECTED) ? "ACC_PROTECTED"
: b.has(AccessFlag.PUBLIC) ? "ACC_PUBLIC" : "(bayrak yok)"));
}
return satir;
}
}
On Altı Çiftin Kaçı Derleniyor
// Erisim.java — dört görünürlük düzeyi × dört çağrı konumu
import java.util.*;
public class Erisim {
static final String[] DUZEY = { "private", "", "protected", "public" };
static final String[] DUZEY_ADI = { "private", "(varsayilan)", "protected", "public" };
static final String BICIM = "%-13s %-11s %-11s %-11s %s%n";
static String ust(String duzey) {
return "package depo;\npublic class Ust {\n"
+ " " + duzey + " String etiket = \"ust alan\";\n"
+ " public String kendi() { return etiket; }\n}\n";
}
static Map<String, String> senaryo(String duzey, int konum) {
Map<String, String> d = new LinkedHashMap<>();
d.put("depo/Ust.java", ust(duzey));
switch (konum) {
case 1 -> d.put("depo/Komsu.java",
"package depo;\nclass Komsu { String oku(Ust u) { return u.etiket; } }\n");
case 2 -> d.put("dis/AltDis.java", "package dis;\nimport depo.Ust;\n"
+ "public class AltDis extends Ust { public String oku() { return this.etiket; } }\n");
case 3 -> d.put("dis/Yabanci.java", "package dis;\nimport depo.Ust;\n"
+ "class Yabanci { String oku(Ust u) { return u.etiket; } }\n");
default -> { }
}
return d;
}
public static void main(String[] args) throws Exception {
System.out.printf(BICIM, "duzey", "ayni sinif", "ayni paket", "alt sinif", "dis paket");
int derlenen = 0;
for (int i = 0; i < DUZEY.length; i++) {
String[] h = new String[4];
for (int j = 0; j < 4; j++) {
boolean ok = Derle.olur(senaryo(DUZEY[i], j));
h[j] = ok ? "derlendi" : "derlenmedi";
if (ok) derlenen++;
}
System.out.printf(BICIM, DUZEY_ADI[i], h[0], h[1], h[2], h[3]);
}
System.out.println("16 ciftin derlenen sayisi: " + derlenen);
}
}
duzey ayni sinif ayni paket alt sinif dis paket private derlendi derlenmedi derlenmedi derlenmedi (varsayilan) derlendi derlendi derlenmedi derlenmedi protected derlendi derlendi derlendi derlenmedi public derlendi derlendi derlendi derlendi 16 ciftin derlenen sayisi: 10
On altı çiftin onu derleniyor, altısı derlenmiyor. Tablonun biçimi bir merdivendir: her satır bir öncekinin açtıklarını açar ve üzerine bir konum ekler. Düzeyler bu yüzden dört ayrı kural değil, iç içe geçmiş dört halkadır; bir üyeyi bir düzeyden ötekine taşımak yeni bir kapı açar, hiçbirini kapatmaz.
İlk sütun dört satırda da aynıdır. Bir sınıf kendi üyesini her düzeyde görür; private bile
sınıfın kendi içinde hiçbir şey kapatmaz. Görünürlük bir üyeyi sahibinden saklamaz, çağıran
tarafı sınırlar.
İlk satır ile ikinci satır arasındaki tek fark bir sözcüğün yokluğudur, ve o yokluk aynı
paketteki bütün sınıfları içeri alır. Belirteçsiz bir alan Komsu sınıfından okunabiliyor;
Komsu, Ust ile hiçbir kalıtım ilişkisi taşımıyor, yalnız aynı pakette duruyor. Paket bu
tabloda bir düzenleme birimi değil, bir erişim sınırıdır.
Son satırın söylediği ise dördüncü sütunun ilk kez dolmasıdır. public, üyeyi bildiren
paketin dışına açılan tek düzeydir; kalan üçü şu ya da bu genişlikte pakete bağlıdır.
Bütün bu kararlar tek bir yerde verilir: derleyicide. On altı senaryonun hiçbirinde program çalıştırılmadı. Altı çift için ortada bir sınıf dosyası bile yoktur — kusur, çalışan bir programda bir hata olarak değil, hiç var olmayan bir program olarak ortaya çıkar. Kursun ölçtüğü sekiz karar arasında görünürlük, çalışma zamanı tipinin payının sıfır olduğu uçtur.
Sınırlayıcı Ölçüm: Korumalı Düzey Paketi de Açar
Tabloda bir satır ilk okumaya direniyor. protected düzeyi çoğunlukla “yalnız alt sınıflara
açık” diye okunur; oysa üçüncü satırın ikinci sütunu derlendi diyor. Alt sınıf olmayan,
yalnız aynı pakette duran bir sınıf korumalı alanı okuyabiliyor.
Bu tek ölçüm bir kuralı sınırlamakla kalmaz, protected düzeyinin biçimini de değiştirir.
Ayrıntıyı görmek için aynı korumalı alan beş ayrı yerden denenir.
- SN10 — Beş denemede de
Ustsınıfı veetiketalanı birebir aynıdır; değişen tek şey erişimi deneyen sınıfın paketi, kalıtım ilişkisi ve kullandığı referansın bildirilen tipidir.
// Koruma.java — korumalı düzey tam olarak neyi açıyor
import java.util.*;
public class Koruma {
static final String UST = "package depo;\npublic class Ust {\n"
+ " protected String etiket = \"ust alan\";\n}\n";
static final String ONEK = "package dis;\nimport depo.Ust;\n";
static final String[][] DENEME = {
{ "ayni paket, ilgisiz sinif", "depo/Komsu.java",
"package depo;\nclass Komsu { String oku(Ust u) { return u.etiket; } }\n" },
{ "dis paket, alt sinif, this uzerinden", "dis/A.java",
ONEK + "public class A extends Ust { String oku() { return this.etiket; } }\n" },
{ "dis paket, alt sinif, kendi tipi uzerinden", "dis/B.java",
ONEK + "public class B extends Ust { String oku(B b) { return b.etiket; } }\n" },
{ "dis paket, alt sinif, Ust tipi uzerinden", "dis/C.java",
ONEK + "public class C extends Ust { String oku(Ust u) { return u.etiket; } }\n" },
{ "dis paket, ilgisiz sinif", "dis/D.java",
ONEK + "class D { String oku(Ust u) { return u.etiket; } }\n" },
};
public static void main(String[] args) throws Exception {
System.out.printf("%-44s %s%n", "erisim yeri", "sonuc");
for (String[] d : DENEME) {
Map<String, String> k = new LinkedHashMap<>();
k.put("depo/Ust.java", UST);
k.put(d[1], d[2]);
System.out.printf("%-44s %s%n", d[0], Derle.olur(k) ? "derlendi" : "derlenmedi");
}
}
}
erisim yeri sonuc ayni paket, ilgisiz sinif derlendi dis paket, alt sinif, this uzerinden derlendi dis paket, alt sinif, kendi tipi uzerinden derlendi dis paket, alt sinif, Ust tipi uzerinden derlenmedi dis paket, ilgisiz sinif derlenmedi
Beş denemenin üçü derleniyor. İlk satır sınırlayıcı ölçümün kendisidir: korumalı üye paketin tamamına açıktır ve bunun için kalıtım gerekmez. “Korumalı, yalnız alt sınıfa açık demektir” cümlesi ölçümle yanlışlanıyor.
Üçüncü ile dördüncü satır arasındaki fark ise protected düzeyinin en kolay gözden kaçan
yanıdır. İki sınıf da Ust’un alt sınıfıdır, ikisi de dış pakettedir, ikisi de aynı alanı
okumaya çalışır. Ayrım okumanın hangi referans üzerinden yapıldığındadır: B kendi tipinden
bir referans kullanıyor ve derleniyor, C bildirilen tipi Ust olan bir referans kullanıyor
ve derlenmiyor. Nesne aynı nesne olabilir; karar nesneye değil, referansın bildirilen
tipine bakılarak veriliyor.
Bu, dersin başındaki iddianın en keskin biçimidir. Aynı alan, aynı alt sınıf, aynı satır biçimi — ve iki ayrı sonuç. Farkı yaratan tek şey, derleyicinin o adı çözerken gördüğü tiptir. Dış paketteki bir alt sınıf korumalı üyeyi kendi kalıtım hattı üzerinden kullanabilir, üst tipin genel bir aracı olarak kullanamaz.
Üye Nerede Duruyor
Derleme zamanında reddedilen erişimlerin çalışma zamanında bir karşılığı var mı? Soru, üyenin sınıf dosyasında ne hâlde durduğuna bakılarak yanıtlanır.
// Bayrak.java — dört düzey sınıf dosyasında ne bırakıyor
public class Bayrak {
static final String KAYNAK = """
class Uye {
private String gizli = "";
String paket = "";
protected String korumali = "";
public String acik = "";
}
""";
public static void main(String[] args) throws Exception {
var alanlar = Derle.alanBayraklari(KAYNAK, "Uye");
System.out.println("sinif dosyasindaki alan sayisi: " + alanlar.size());
for (String s : alanlar) System.out.println(" " + s);
}
}
sinif dosyasindaki alan sayisi: 4 gizli ACC_PRIVATE paket (bayrak yok) korumali ACC_PROTECTED acik ACC_PUBLIC
Dört alanın dördü de sınıf dosyasındadır. private bildirilen alan silinmemiş, gizlenmemiş,
başka bir yere taşınmamıştır; ötekilerle aynı listede, aynı biçimde durur. Aralarındaki tek
fark bir bayraktır.
Paket düzeyinin karşılığı da burada görünür. Öteki üç düzeyin her birinin sınıf dosyasında bir adı vardır; belirteçsiz alanın hiçbir bayrağı yoktur. Kaynaktaki adsızlık sınıf dosyasında da adsızdır: paket düzeyi, üç bayrağın hiçbirinin konmamış olmasıdır.
Buradan dersin ikinci yarısı çıkıyor. Görünürlük bir saklama düzeneği değildir; üyeyi kimsenin ulaşamayacağı bir yere koymaz. Yaptığı şey, derleyiciye hangi kaynakları reddedeceğini söylemektir. Kapsülleme bir engel değil, bir sözleşmedir — ve o sözleşmenin yaptırımı, çalışan programda değil, hiç doğmayan programda görünür.
Kusur Kimin Derlemesinde Doğuyor
Bir düzeyi daraltmak, sahibinin kaynağında tek bir sözcük değiştirmektir. Bu değişikliğin
kimi vurduğu ölçülebilir: aynı Ust sınıfı iki düzeyle, önce tek başına, sonra dış paketteki
bir çağıranla birlikte derlenir.
// Bedel.java — bir düzey daraltıldığında kusur kimin derlemesinde doğuyor
import java.util.*;
public class Bedel {
static final String CAGIRAN = "package dis;\nimport depo.Ust;\n"
+ "class Yabanci { String oku(Ust u) { return u.etiket; } }\n";
static String ust(String duzey) {
return "package depo;\npublic class Ust {\n " + duzey
+ " String etiket = \"ust alan\";\n}\n";
}
public static void main(String[] args) throws Exception {
for (String duzey : new String[] { "public", "protected" }) {
Map<String, String> ikisi = new LinkedHashMap<>();
ikisi.put("depo/Ust.java", ust(duzey));
ikisi.put("dis/Yabanci.java", CAGIRAN);
System.out.printf("%-10s | Ust tek basina: %-9s | cagiranla birlikte: %s%n", duzey,
Derle.olur(Map.of("depo/Ust.java", ust(duzey))) ? "derlendi" : "derlenmedi",
Derle.olur(ikisi) ? "derlendi" : "derlenmedi");
}
}
}
public | Ust tek basina: derlendi | cagiranla birlikte: derlendi protected | Ust tek basina: derlendi | cagiranla birlikte: derlenmedi
Orta sütun iki satırda da derlendi diyor. Ust sınıfının kaynağı korumalı düzeyde de
kusursuzdur; kendi başına derlenir, sınıf dosyası üretilir, hiçbir uyarı çıkmaz. Sahibinin
tarafında görünen bir şey yoktur.
Sağ sütun ise ayrılıyor. Aynı Yabanci sınıfı, aynı satır, iki ayrı sonuç — ve Yabanci
sınıfının kaynağında hiçbir şey değişmemiştir. Kusur, değişikliği yapan sınıfın derlemesinde
değil, ona bakan sınıfın derlemesinde doğuyor.
Bu, kursun her derste sorduğu “bedel nerede görünür” sorusunun buradaki yanıtıdır.
Görünürlüğü genişletmek geriye dönük olarak sorunsuzdur: yeni bir kapı açar, açık olanı
kapatmaz. Daraltmak ise sahibinin göremeyeceği bir yerde kırılma üretir. Bir üyeyi public
bildirmenin maliyeti, o üyeyi sonradan geri almanın maliyetidir.
Özet
- Dört görünürlük düzeyi dört çağrı konumuyla çaprazlandığında on altı çiftin onu derlenir; tablo iç içe geçmiş dört halka biçimindedir, her düzey bir öncekinin üstüne konum ekler.
- Görünürlük üyeyi sahibinden saklamaz — ilk sütun dört düzeyde de derlenir — sınırlanan şey çağıran taraftır; belirteçsiz düzey de bir seçimsizlik değildir, aynı paketteki ilgisiz sınıfları içeri alır ve paketi bir erişim sınırına çevirir.
- Korumalı düzey paket içini de açar: alt sınıf olmayan bir komşu sınıf korumalı alanı okur, yani “yalnız alt sınıfa açık” okuması yanlıştır.
- Dış paketteki bir alt sınıf korumalı üyeye kendi tipinden bir referansla erişebilir, bildirilen tipi üst sınıf olan bir referansla erişemez: karar nesneye değil, referansın bildirilen tipine bakar.
- Sınıf dosyasında dört alanın dördü de durur; ayrımı yalnız bir bayrak taşır ve paket düzeyinin bayrağı yoktur. Görünürlük saklama değil, derleyiciye verilen bir kuraldır.
- Bir düzey daraltıldığında sahibinin kaynağı yine derlenir; kusur çağıranın derlemesinde doğar. Genişletmek geriye dönük olarak sorunsuzdur, daraltmak başka bir yerde kırılır.
Sonraki Adım
İki derstir aynı ikili kullanılıyor ve ikisinde de kararın bir tarafı ölçüldü: yapıcıdan
çağrılan yöntemi çalışma zamanı tipi seçti, erişilebilirliği bildirilen tip belirledi. İkisi
yan yana konmadı. Sıradaki ders bu kursun çekirdek ölçümüdür: tek bir referans üzerinde sekiz
ayrı karar okunur ve kaçının hangi taraftan geldiği sayılır. Aynı satırda d.ad() ile
d.etiket iki karşıt yanıt verir — alanların aynı adla yeniden bildirilmesi yeniden
tanımlama değil gizlemedir, ve gizlenen alan yok olmaz. Aynı derste @Override işaretinin
derleme zamanında neyi yakaladığı ve imza değiştiğinde kararın neden taraf değiştirdiği
ölçülür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.