Bölüm 4.4: Çok Biçimlilik

Java ile Nesne Yönelimli Programlama
Çok biçimlilik kavramıReferans kuralıYukarı ve aşağı dönüşüminstanceof Dinamik bağlamaMetot tablosuAşırı yüklemeŞekil ve tanksavar örnekleriequals ve simetri
Sercan KÜLCÜ · Giresun Üniversitesi · Elektrik-Elektronik Mühendisliği

Giriş

Öğrenme Çıktıları

Kavram

Çok biçimliliği (polymorphism) tanımlamak; derleme zamanı ve çalışma zamanı çok biçimliliğini ayırt etmek.

Referans kuralı

Üst sınıf referansına alt sınıf nesnesi atanabildiğini, tersinin neden derleme hatası verdiğini açıklamak.

Tip dönüşümü

Yukarı ve aşağı dönüşümü (upcasting, downcasting) yazmak; ClassCastException'ı instanceof ile önlemek.

Dinamik bağlama

Çağrılan metodun nesnenin gerçek sınıfına göre seçildiğini adım adım izlemek; metot aramasını hiyerarşide göstermek.

Aşırı yükleme ve geçersiz kılma

Overloading ile overriding'i karşılaştırmak; hangisinin derleyici, hangisinin JVM tarafından seçildiğini bilmek.

Tasarım

Şekil dizisi ve tanksavar oyunu örnekleriyle üst sınıf tipiyle genel kod yazmak; equals'ın simetri tuzağını tanımak.

Çok Biçimlilik Kavramı

Çok Biçimlilik (Polymorphism)

  • Yunanca poly (çok) + morph (biçim): aynı ifadenin, farklı nesnelerde farklı davranışa yol açmasıdır.
  • Bir referans, kendi tipindeki veya bir alt sınıfa ait bir nesneye atıfta bulunabilir.
  • hayvan.sesCikar() çağrısı köpek nesnesinde Hav hav!, kedi nesnesinde Miyav! yazdırır.
  • Kodu üst sınıf tipiyle bir kez yazarız; her nesne çağrıya kendi sürümüyle cevap verir.
Nesne yönelimli programlamanın dört temel ilkesi: sarmalama, kalıtım, çok biçimlilik ve soyutlama. Çok biçimlilik, kalıtımın üzerine kurulur.
Uzaktan kumandaAynı "aç" düğmesi televizyonu, klimayı ve projektörü açar; her cihaz bunu kendi bildiği gibi yapar.
Sürücü belgesiB sınıfı belgeyle her marka otomobil sürülür: sürücü "otomobil" arayüzünü kullanır, motorun ayrıntısı markaya aittir.
Hayvanat bahçesiBakıcı "herkes ses çıkarsın" der; her hayvan kendi sesini çıkarır.

Çok Biçimlilik Kavramı

Çok Biçimliliğin İki Türü

Derleme zamanı (statik)Çalışma zamanı (dinamik)
MekanizmaAşırı yükleme (overloading)Metot geçersiz kılma (overriding) + dinamik bağlama
Seçimi yapanDerleyici (javac)JVM, program çalışırken
Neye bakılır?Argümanların sayısı ve bildirilen tipleriNesnenin gerçek sınıfı
Kalıtım gerekir mi?Hayır, tek sınıfta olurEvet, sınıf hiyerarşisi gerekir

Aynı ad, farklı parametreler

int topla(int a, int b) { ... }
double topla(double a, double b) { ... }

topla(5, 3);     // int sürümü
topla(3.5, 2.7); // double sürümü

Aynı imza, farklı sınıflar

Hayvan h1 = new Kopek();
Hayvan h2 = new Kedi();

h1.sesCikar(); // Hav hav!
h2.sesCikar(); // Miyav!

"Çok biçimlilik" denince çoğunlukla çalışma zamanı çok biçimliliği kastedilir.

Çok Biçimlilik Kavramı

Örnek Sınıf Hiyerarşisi

Object+toString(): String+equals(Object): boolean+getClass(): ClassHayvan+sesCikar(): void+kos(): void+uyu(): voidKopek+sesCikar(): void+kos(): void+havla(): voidKedi+sesCikar(): void+tirmala(): void
class Hayvan {
  public void sesCikar() { System.out.println("Hayvan sesi"); }
  public void kos()      { System.out.println("Hayvan koşuyor"); }
  public void uyu()      { System.out.println("Hayvan uyuyor"); }
}
class Kopek extends Hayvan {
  @Override public void sesCikar() { System.out.println("Hav hav!"); }
  @Override public void kos() { System.out.println("Köpek koşuyor"); }
  public void havla() { System.out.println("Köpek havlıyor"); }
}
class Kedi extends Hayvan {
  @Override public void sesCikar() { System.out.println("Miyav!"); }
  public void tirmala() { System.out.println("Kedi tırmalıyor"); }
}
  • Kopek, sesCikar() ve kos()'u geçersiz kılar, havla()'yı ekler; Kedi yalnızca sesCikar()'ı geçersiz kılar, tirmala()'yı ekler.
  • Kopek de Kedi de bir Hayvan'dır (is-a); ikisi de Hayvan tipindeki bir değişkende tutulabilir.

Referanslar ve Tip Uyumu

Referans, Alt Sınıf Nesnesini Gösterebilir

// Köpek nesnesine referans
Kopek kopek = new Kopek();
// Hayvan referansı Kopek'i gösterebilir
Hayvan hayvan = kopek;
// Object referansı da gösterebilir
Object nesne = hayvan;
System.out.println(kopek == hayvan);
System.out.println(hayvan == nesne);
System.out.println(nesne.getClass());
Referans değişkenleriHeap'teki nesnekopektip: KopekKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

new Kopek() heap'te bir nesne oluşturur; kopek referansı onu gösterir.

Referans değişkenleriHeap'teki nesnekopektip: Kopekhayvantip: HayvanKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

Yeni nesne oluşmaz: hayvan, aynı nesneyi gösteren ikinci referanstır. Kopek bir Hayvan olduğu için atama geçerlidir.

Referans değişkenleriHeap'teki nesnekopektip: Kopekhayvantip: Hayvannesnetip: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

Her sınıf Object'ten türediği için Object referansı da aynı nesneyi gösterebilir.

Referans değişkenleriHeap'teki nesnekopektip: Kopekhayvantip: Hayvannesnetip: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

== referansları karşılaştırır: ikisi de aynı nesneyi gösterdiği için true.

Referans değişkenleriHeap'teki nesnekopektip: Kopekhayvantip: Hayvannesnetip: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

Yine aynı nesne: true. Üç referans, tek nesne.

Referans değişkenleriHeap'teki nesnekopektip: Kopekhayvantip: Hayvannesnetip: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

Referansın tipi Object olsa da nesnenin gerçek sınıfı değişmez: Kopek.

Referanslar ve Tip Uyumu

Referans Tipi ve Nesne Tipi

Hayvan hreferansKopek nesnesiheap'tebildirilen tipderleyici bakargerçek tipJVM bakar
Hayvan h = new Kopek();
// bildirilen tür: Hayvan
// gerçek tür:     Kopek
SoruKim karar verir?
Bu metot çağrılabilir mi?Derleyici, referans tipine bakar
Hangi sürüm çalışır?JVM, nesne tipine bakar
Hangi atamalar geçerli?Derleyici, referans tipine bakar
Hayvan hayvan = new Kopek();
hayvan.havla(); // derleme hatası
error: cannot find symbol
  symbol:   method havla()
  location: variable hayvan of type Hayvan

Kumandada düğmesi olmayan özellik kullanılamaz: nesne bir köpek olsa da Hayvan "kumandasında" havla() düğmesi yoktur.

Referanslar ve Tip Uyumu

Referans Kuralı

  • Bir nesnenin alt tipini, üst sınıf tipindeki bir referansa atayabiliriz.
  • Ancak üst sınıfın nesnesini, alt sınıf tipindeki bir referansa atayamayız.

Geçerli

// Kopek, Hayvan'ın alt türüdür
Hayvan hayvan = new Kopek();
Object nesne = new Kedi();

Her köpek bir hayvandır: hayvandan beklenen her şeyi köpek de yapabilir.

Hatalı

// Hayvan, Kopek'in üst sınıfıdır
Kopek kopek = new Hayvan();
error: incompatible types:
  Hayvan cannot be converted to Kopek

Bir hayvan havla() bilmeyebilir; derleyici buna izin vermez.

Hayvan h = new Kopek(); Kopek k = h; de aynı hatayı verir. Derleyici h'nin gösterdiği nesneyi değil, bildirilen tipini (Hayvan) görür; çözüm açık dönüşümdür.
Hayvan hKopek nesnesi✓ geçerliKopek kHayvan nesnesi✗ derlenmez

Tip Dönüşümü

Yukarı ve Aşağı Dönüşüm

HayvanKopekyukarıotomatikaşağı(Kopek) ile
Yukarı (upcasting)Aşağı (downcasting)
YönAlt → üstÜst → alt
YazımOtomatik (örtük)Açık: (Kopek) h
Güvenli mi?Her zamanNesneye bağlı
Diğer adıGenişletme (widening)Daraltma (narrowing)
Kopek kopek = new Kopek();
Hayvan h = kopek;    // yukarı: otomatik
Hayvan h1 = (Hayvan) kopek; // gereksiz
Hayvan h = new Kopek();
Kopek k = (Kopek) h; // aşağı: açık
k.havla();           // Köpek havlıyor

Tip Dönüşümü

Aşağı Dönüşüm (Downcasting)

  • Bir üst sınıf referansı, alt sınıf tipine açıkça (explicitly) dönüştürülebilir: (AltSinif) referans.
  • Üst sınıf referansı, ancak bu dönüşümle bir alt sınıf değişkenine atanabilir.
  • Amaç: üst sınıfta bulunmayan (yalnızca alt sınıfa ait) metotları çağırmak.
  • Dönüşüm nesneyi değiştirmez; yalnızca derleyiciye "bu referansı Kopek olarak gör" der.
Hayvan h2 = new Kopek();
Kopek k = (Kopek) h2;
k.havla();
((Kopek) h2).havla(); // tek satırda
Köpek havlıyor
Köpek havlıyor

Öncelik tuzağı

Nokta (.) dönüşümden önce uygulanır. Parantezler unutulursa (Kopek), havla() çağrısının sonucuna uygulanmak istenir:

(Kopek) h2.havla();   // hatalı
error: not a statement
String s = (Kopek) h2.isim();
// h2.isim() önce aranır → Hayvan'da yok
error: cannot find symbol
  symbol:   method isim()
  location: variable h2 of type Hayvan

Doğrusu: ((Kopek) h2).havla();

Tip Dönüşümü

Hatalı Aşağı Dönüşüm: ClassCastException

Hayvan h4 = new Kedi();
Kopek k4 = (Kopek) h4; // derlenir!
k4.havla();
Exception in thread "main" java.lang.ClassCastException:
class Kedi cannot be cast to class Kopek
Kopek k5 = (Kopek) new Hayvan(); // derlenir!
java.lang.ClassCastException:
class Hayvan cannot be cast to class Kopek
  • Derleyici izin verir: Hayvan tipindeki bir referans pekâlâ bir Kopek'i gösteriyor olabilir.
  • JVM, dönüşüm anında nesnenin gerçek sınıfını denetler: nesne bir Kedi ya da düz bir Hayvan ise program istisnayla durur.
  • Yani üst sınıf nesnesi, alt sınıf referansına hiçbir zaman atanamaz; dönüşüm yalnızca nesne gerçekten o alt sınıftansa başarılı olur.
B objB = (B) objA; derlenir, ama yalnızca objA gerçekten bir B nesnesini gösteriyorsa çalışır. Emin olunamıyorsa önce instanceof ile sınanır.

İlgisiz tipler arasında dönüşümü derleyici hiç kabul etmez: (Kedi) kopek → incompatible types: Kopek cannot be converted to Kedi.

Tip Dönüşümü · Adım adım kod izleme

ClassCastException: Kod İzleme

YığınHeapmain()h1Kopek #1

Yukarı dönüşüm: Hayvan tipindeki h1 bir Kopek nesnesini gösterir.

YığınHeapmain()h1h2Kopek #1Kedi #2

h2 ise bir Kedi gösterir. Derleyici için ikisi de yalnızca Hayvan.

YığınHeapmain()h1h2k1Kopek #1Kedi #2JVM denetimi: nesnenin sınıfıKopek = Kopek ✓

Derleyici dönüşüme izin verir. Çalışırken JVM nesneye bakar: gerçekten Kopek. k1 aynı nesneyi gösterir.

YığınHeapmain()h1h2k1Kopek #1Kedi #2

Kopek tipli referansla havla() çağrılabilir.

YığınHeapmain()h1h2k1k2?Kopek #1Kedi #2JVM denetimi: Kedi bir Kopek mi?KediHayvanObject ✗

Bu satır da derlenir. Ama JVM, Kedi'den yukarı çıkan zincirde Kopek bulamaz.

YığınHeapmain()h1h2k1k2?Kopek #1Kedi #2ClassCastException → 6. ve 7. satır çalışmaz

ClassCastException fırlatılır, k2'ye hiçbir şey atanmaz; yakalayan olmadığı için program durur, Bitti yazılmaz.

Hayvan h1 = new Kopek();
Hayvan h2 = new Kedi();
Kopek k1 = (Kopek) h1;
k1.havla();
Kopek k2 = (Kopek) h2;
k2.havla();
System.out.println("Bitti");

Tip Dönüşümü

instanceof ile Güvenli Dönüşüm

hayvanlar = { new Kopek(), new Kedi(), new Hayvan() }

for (Hayvan x : hayvanlar) {
    if (x instanceof Kopek) {
        Kopek kk = (Kopek) x;
        kk.havla();
    } else if (x instanceof Kedi) {
        ((Kedi) x).tirmala();
    } else {
        System.out.println("Özel davranış yok");
    }
}
Köpek havlıyor
Kedi tırmalıyor
Özel davranış yok
  • referans instanceof Tur: nesne Tur'den ya da onun bir alt sınıfındansa true.
  • Önce sınama, sonra dönüşüm: ClassCastException oluşamaz. null instanceof Tur her zaman false.

Java 16+: örüntü eşleme (pattern matching)

if (x instanceof Kopek kk) {
    kk.havla(); // dönüşüm otomatik
}
Uzun instanceof zincirleri çoğunlukla bir tasarım uyarısıdır: davranışı alt sınıflarda geçersiz kılınan bir metoda taşımak daha iyidir (Alıştırma 5).

Tip Dönüşümü · Adım adım kod izleme

instanceof ile Güvenli Dönüşüm: Kod İzleme

dizi•••Kedi #1Hayvan #2Kopek #3

Dizinin üç öğesi de Hayvan tipinde referans; gösterdikleri nesneler farklı sınıflardan.

dizi•••Kedi #1Hayvan #2Kopek #3x→ Kedi #1x instanceof Kopekfalse

İlk tur: x bir Kedi. Kopek sınaması yanlış.

dizi•••Kedi #1Hayvan #2Kopek #3x→ Kedi #1x instanceof Kopekfalsex instanceof Keditrue

Kedi sınaması doğru; önce sınandığı için (Kedi) x dönüşümü güvenli: Kedi tırmalıyor.

dizi•••Kedi #1Hayvan #2Kopek #3x→ Hayvan #2x instanceof Kopekfalsex instanceof Kedifalse

İkinci tur: düz bir Hayvan. Hiçbir alt sınıfın örneği değil: iki sınama da yanlış.

dizi•••Kedi #1Hayvan #2Kopek #3x→ Hayvan #2x instanceof Kopekfalsex instanceof Kedifalse

else dalı çalışır; hiçbir dönüşüm denenmediği için hata riski yok.

dizi•••Kedi #1Hayvan #2Kopek #3x→ Kopek #3x instanceof Kopektrue

Üçüncü tur: Kopek sınaması doğru, havla() çalışır. Döngü biter; hiçbir turda ClassCastException oluşmadı.

Hayvan[] dizi = {new Kedi(), new Hayvan(), new Kopek()};
for (Hayvan x : dizi) {
    if (x instanceof Kopek) {
        ((Kopek) x).havla();
    } else if (x instanceof Kedi) {
        ((Kedi) x).tirmala();
    } else {
        System.out.println("Özel davranış yok");
    }
}

Tip Dönüşümü

Diziler ve ArrayStoreException

Kopek[] kopekler = new Kopek[2];
Hayvan[] hayvanlar = kopekler;   // derlenir
hayvanlar[0] = new Kopek();
System.out.println("ilk atama tamam");
hayvanlar[1] = new Kedi();       // derlenir ama...
System.out.println("buraya gelinmez");
ilk atama tamam
Exception in thread "main" java.lang.ArrayStoreException: Kedi

Aynı fikir jenerik listede

List<Hayvan> l = new ArrayList<Kopek>();
error: incompatible types: ArrayList<Kopek>
  cannot be converted to List<Hayvan>
  • Diziler kovaryanttır: Kopek bir Hayvan olduğundan Kopek[] de bir Hayvan[] sayılır ve yukarı dönüşüm derlenir.
  • Ama dizi nesnesi gerçekte Kopek[]'dir. Hayvan[] referansı üzerinden içine bir Kedi yazmak derleyicinin gözünde geçerlidir; JVM her yazmada öğenin tipini denetler ve ArrayStoreException fırlatır.
  • Jenerik koleksiyonlar bu açığı derleme zamanında kapatır: List<Kopek>, List<Hayvan> değildir.
  • Okumak için kovaryans güvenlidir (her öğe bir Hayvan'dır); sorun yalnızca yazarken çıkar.

Dinamik Bağlama

Dinamik Bağlama (Dynamic Binding)

  • Referans bir alt sınıf nesnesini gösteriyorsa, alt sınıfın metodu çağrılır.
  • Çağrılacak sürüm, değişkenin bildirilen tipine göre değil, nesnenin gerçek sınıfına göre belirlenir.
  • Bu seçim derleme sırasında değil, çalışma zamanında yapıldığı için dinamik (geç) bağlama denir.
Hayvan h = new Kopek();
h.kos();      // Kopek sınıfının metodu
h.sesCikar(); // Kopek sınıfının metodu
h.uyu();      // Kopek'te yok → Hayvan'ınki
Köpek koşuyor
Hav hav!
Hayvan uyuyor

h.kos() iki aşamada çözülür

1. Derleme zamanıDerleyici h'nin tipine (Hayvan) bakar: kos() var mı? Var → çağrı derlenir. Yoksa derleme hatası.
2. Çalışma zamanıJVM nesneye bakar: gerçek sınıf Kopek → Kopek.kos() çalışır.
Kural: derleyici "var mı?" diye, JVM "hangisi?" diye sorar.
h.kos()Derleyici: Hayvan'da kos() var mı?evet → derlenir (yoksa hata)JVM: nesne hangi sınıftan?Kopek → Kopek.kos() çalışır

Dinamik Bağlama

Metot Araması Hiyerarşinin En Altından Başlar

Hayvan h = new Kopek();
h.kos();
h.uyu();
System.out.println(h.toString());
// h.havla(); → derleme hatası
Object+toString(): String+equals(Object): booleanHayvan+sesCikar(): void+kos(): void+uyu(): voidKopek+sesCikar(): void+kos(): void+havla(): voidgerçek sınıf

Nesne bir Kopek: aramalar hep en alttan, gerçek sınıftan başlar.

Object+toString(): String+equals(Object): booleanHayvan+sesCikar(): void+kos(): void+uyu(): voidKopek+sesCikar(): void+kos(): void+havla(): void✓ bulundu

kos() Kopek'te var → arama biter, Kopek sürümü çalışır.

Object+toString(): String+equals(Object): booleanHayvan+sesCikar(): void+kos(): void+uyu(): void✓ bulunduKopek+sesCikar(): void+kos(): void+havla(): void✗ yok, yukarı

uyu() Kopek'te yok → bir üste çık: Hayvan'da bulundu.

Object+toString(): String+equals(Object): boolean✓ bulunduHayvan+sesCikar(): void+kos(): void+uyu(): void✗ yokKopek+sesCikar(): void+kos(): void+havla(): void✗ yok

toString() en tepede, Object'te bulunur: sınıf adı + @ + onaltılık hash kodu (çalıştırmaya göre değişir).

Object+toString(): String+equals(Object): booleanHayvan+sesCikar(): void+kos(): void+uyu(): void✗ havla() yokKopek+sesCikar(): void+kos(): void+havla(): void

h.havla() derlenmez: derleyici aramayı referans tipi Hayvan'dan yukarı yapar ve bulamaz.

Dinamik Bağlama

Çok Biçimli Dizi: Adım Adım Kod İzleme

Hayvan[] hayvanlar = new Hayvan[3];
hayvanlar[0] = new Kopek();
hayvanlar[1] = new Kedi();
hayvanlar[2] = new Hayvan();
for (int i = 0; i < hayvanlar.length; i++) {
    hayvanlar[i].sesCikar();
}
hayvanlarheap0null1null2null

Hayvan tipinde 3 öğeli dizi: öğeler referanstır, başlangıçta null.

hayvanlarheap0Kopek nesnesi1null2null

0. öğe bir Kopek'i gösterir (yukarı dönüşüm, otomatik).

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2null

1. öğe bir Kedi'yi gösterir.

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

2. öğe düz bir Hayvan nesnesidir. Dizi artık üç farklı biçim tutuyor.

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

i = 0, 0 < 3 doğru → döngü gövdesi.

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Kopek.sesCikar().

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

i = 1, 1 < 3 doğru → döngü gövdesi.

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Kedi.sesCikar().

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

i = 2, 2 < 3 doğru → döngü gövdesi.

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Hayvan.sesCikar().

hayvanlarheap0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

i = 3, 3 < 3 yanlış → döngü biter. Tek satır kod, üç farklı davranış.

Dinamik Bağlama

Perde Arkası: Metot Tablosu

  • JVM'ler dinamik bağlamayı genellikle sınıf başına bir metot tablosuyla (virtual method table) uygular: her geçersiz kılınabilir metodun sabit bir sıra numarası (yuvası) vardır.
  • Alt sınıfın tablosu üst sınıfınkinin kopyasıyla başlar; geçersiz kılınan yuvalar yeni gövdeyle değiştirilir, yeni metotlar sona eklenir.
  • Her nesne kendi sınıfını bilir. h.kos() için JVM: nesne → sınıfı → tablodaki kos yuvası → gövde. Arama tek bir dizi erişimidir, hiyerarşide yukarı yürümek gerekmez.

javap -c Ana çıktısından

47: invokevirtual #15  // Method Hayvan.kos:()V
52: invokevirtual #18  // Method Hayvan.sesCikar:()V
Hayvan tablosu[0] sesCikarHayvan[1] kosHayvan[2] uyuHayvanKopek tablosu[0] sesCikarKopek[1] kosKopek[2] uyuHayvan[3] havlaKopekKedi tablosu[0] sesCikarKedi[1] kosHayvan[2] uyuHayvan[3] tirmalaKedisağ sütun: metodungövdesinin bulunduğu sınıfvurgulu ad: gövde busınıfta yazıldısoluk ad: üstten kopya

Derleyici bayt koduna yalnızca Hayvan.kos yazar; gövdeyi invokevirtual, nesnenin tablosuna bakarak seçer. Yapıcılar ve super.m() invokespecial, static metotlar invokestatic ile çağrılır: tabloya bakılmaz. private metotlar Java 11'den beri invokevirtual kullanır, ama tek adayları vardır.

Dinamik Bağlama · Adım adım kod izleme

Metot Tablosu ile Çağrı: Kod İzleme

HeapKopek #1sınıf →Kedi #2sınıf →Kopek tablosuKedi tablosuher nesne kendisınıfına, sınıf datablosuna bağlıdırd[0] ve d[1]: ikisi de Hayvan referansı

Dizideki iki nesne farklı sınıflardandır; JVM için her biri kendi sınıfının tablosuna giden bir bağlantı taşır.

Yığınhmain()Kopek #1sınıf →Kopek tablosu[0] sesCikarKopek[1] kosKopek[2] uyuHayvan[3] havlaKopekinvokevirtual Hayvan.kos → yuva [1]Kopek tablosu [1] = Kopek.kos

Bayt kodu “Hayvan.kos, yuva 1” der. Nesne bir Kopek: onun tablosunun 1. yuvasında Kopek.kos vardır.

Yığınhmain()Kopek #1sınıf →Kopek tablosu[0] sesCikarKopek[1] kosKopek[2] uyuHayvan[3] havlaKopekinvokevirtual Hayvan.sesCikar → yuva [0]Kopek tablosu [0] = Kopek.sesCikar

Aynı yol, 0. yuva: Hav hav!

Yığınhmain()Kedi #2sınıf →Kedi tablosu[0] sesCikarKedi[1] kosHayvan[2] uyuHayvan[3] tirmalaKediinvokevirtual Hayvan.kos → yuva [1]Kedi tablosu [1] = Hayvan.kos (kalıtımla)

Aynı bayt kodu, başka nesne. Kedi kos'u geçersiz kılmadığı için tablosunun 1. yuvası Hayvan.kos'u gösterir.

Yığınhmain()Kedi #2sınıf →Kedi tablosu[0] sesCikarKedi[1] kosHayvan[2] uyuHayvan[3] tirmalaKediinvokevirtual Hayvan.sesCikar → yuva [0]Kedi tablosu [0] = Kedi.sesCikar

Miyav! Dört çağrının hiçbirinde if ya da tip sınaması yapılmadı: seçimi tablolar yaptı.

Hayvan[] d = {new Kopek(), new Kedi()};
for (Hayvan h : d) {
    h.kos();
    h.sesCikar();
}

Dinamik Bağlama

Neler Dinamik Bağlanmaz?

class Hayvan {
    String tur = "hayvan";

    static void bilgi() {
        System.out.println("Hayvan.bilgi()");
    }

    void tanit() {
        System.out.println("Ben bir " + tur);
    }
}

class Kopek extends Hayvan {
    String tur = "köpek"; // gizler, ezmez

    static void bilgi() {
        System.out.println("Kopek.bilgi()");
    }

    @Override
    void tanit() {
        System.out.println("Ben bir " + tur);
    }
}
Hayvan h = new Kopek();
System.out.println(h.tur);
h.bilgi();
h.tanit();
hayvan
Hayvan.bilgi()
Ben bir köpek
ÜyeNeye göre seçilir?
Örnek metoduNesne tipi (dinamik)
Alan (değişken)Referans tipi (statik)
static metotReferans tipi (statik)
private / finalGeçersiz kılınamaz, static

static metot sınıf adıyla çağrılır: Kopek.bilgi(). Alt sınıfta aynı adla alan tanımlamaktan kaçınılır.

Dinamik Bağlama · Adım adım kod izleme

Alan Gizleme ve static: Kod İzleme

YığınHeapmain()hKopek #1Hayvan.tur"hayvan"Kopek.tur"köpek"bir nesne, iki ayrı tur alanı

Alt sınıftaki tur üst sınıfınkini ezmez, gizler: nesnede iki ayrı alan vardır.

YığınHeapmain()hKopek #1Hayvan.tur"hayvan"Kopek.tur"köpek"alan → bildirilen tip (Hayvan)

Alanlar dinamik bağlanmaz: h'nin bildirilen tipi Hayvan olduğu için Hayvan.tur okunur: hayvan.

YığınHeapmain()hKopek #1Hayvan.tur"hayvan"Kopek.tur"köpek"static metot → bildirilen tip; nesneye bakılmaz

static metot sınıfa aittir; derleyici h.bilgi()'yi Hayvan.bilgi() olarak bağlar.

YığınHeapKopek.tanit()thismain()hKopek #1Hayvan.tur"hayvan"Kopek.tur"köpek"örnek metodu → gerçek tip (Kopek)

Örnek metodu dinamik bağlanır: Kopek.tanit() çalışır; onun içinde tur, Kopek'in alanıdır: Ben bir köpek.

YığınHeapmain()hKopek #1Hayvan.tur"hayvan"Kopek.tur"köpek"(Kopek) h → Kopek.tur okunur

Dönüşüm bildirilen tipi Kopek yapar; aynı nesnenin diğer alanı okunur: köpek. Bu karışıklık yüzünden alt sınıfta aynı adlı alan tanımlanmaz.

class Hayvan {
    String tur = "hayvan";
    static String bilgi() { return "Hayvan.bilgi"; }
    String tanit() { return "Ben bir " + tur; }
}
class Kopek extends Hayvan {
    String tur = "köpek";            // gizler
    static String bilgi() { return "Kopek.bilgi"; }
    String tanit() { return "Ben bir " + tur; }
}
Hayvan h = new Kopek();   // main
System.out.println(h.tur);
System.out.println(h.bilgi());
System.out.println(h.tanit());
System.out.println(((Kopek) h).tur);

Dinamik Bağlama

Yapıcıdan Geçersiz Kılınabilir Metot Çağırmak

  • Dinamik bağlama nesne kurulurken de çalışır: üst sınıfın yapıcısındaki ciz() çağrısı, nesne bir Etiket olduğu için Etiket.ciz()'i çalıştırır.
  • Ama o anda alt sınıfın alan ilk değerleri henüz atanmamıştır: metin hâlâ null. Alt sınıfın metodu, yarı kurulmuş bir nesne üzerinde çalışır.
  • Metot alana yalnızca yazdırmak için değil, kullanmak için dokunsaydı program çökerdi.
  • Kural: yapıcıdan yalnızca private, final ya da static metotlar çağrılmalıdır; bunlar geçersiz kılınamaz.

ciz() içinde metin.length() kullanılsaydı

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "this.metin" is null
	at Etiket.ciz(Ana.java:21)
	at Bilesen.<init>(Ana.java:16)
	at Etiket.<init>(Ana.java:19)
Yığın izindeki sıra durumu özetler: Etiket yapıcısı → Bilesen yapıcısı → yine Etiket'in metodu. <init>, bayt kodunda yapıcının adıdır.

Dinamik Bağlama · Adım adım kod izleme

Yapıcıdaki Çağrı: Kod İzleme

Yığın (stack)HeapEtiket()main()e?Etiket #1metinnullnesne ayrıldı; Etiket() → gizli super()

new nesneye yer ayırır, metin varsayılan null. Etiket()'in ilk işi gizli super() çağrısıdır.

Yığın (stack)HeapBilesen()Etiket()main()e?Etiket #1metinnullüst sınıf kısmı kuruluyor

Bilesen() gövdesi çalışır ve ilk satırı yazar.

Yığın (stack)HeapEtiket.ciz()Bilesen()Etiket()main()e?Etiket #1metinnullEtiket.ciz() seçildi, metin = null

ciz() = this.ciz(). Nesnenin sınıfı Etiket olduğu için geçersiz kılınmış sürüm seçilir; ama metin henüz atanmadı: Etiket: null.

Yığın (stack)HeapEtiket()main()e?Etiket #1metin"Merhaba"super() döndü → alan ilk değerleri

Üst sınıf yapıcısı döndükten sonra alt sınıfın alan ilk değeri çalışır: metin = "Merhaba". Çok geç: ciz() çoktan çağrıldı.

Yığın (stack)HeapEtiket()main()e?Etiket #1metin"Merhaba"Etiket() gövdesi

Etiket() gövdesi çalışır; nesne artık tamamen kuruldu.

Yığın (stack)HeapEtiket.ciz()main()eEtiket #1metin"Merhaba"aynı metot, kurulmuş nesne

Kurulmuş nesnede aynı metot doğru sonucu verir. Fark yalnızca çağrının ne zaman yapıldığıdır.

class Bilesen {
    Bilesen() {
        System.out.println("Bilesen kuruluyor");
        ciz();
    }
    void ciz() { System.out.println("boş bileşen"); }
}
class Etiket extends Bilesen {
    private String metin = "Merhaba";
    Etiket() { System.out.println("Etiket kuruldu"); }
    @Override void ciz() {
        System.out.println("Etiket: " + metin);
    }
}
Etiket e = new Etiket();   // main
e.ciz();

Aşırı Yükleme

Aşırı Yükleme Derleme Zamanında Seçilir

static void yazdir(Hayvan h) {
    System.out.println("yazdir(Hayvan)");
}

static void yazdir(Kopek k) {
    System.out.println("yazdir(Kopek)");
}

public static void main(String[] args) {
    Kopek kopek = new Kopek();
    Hayvan hayvan = kopek;
    yazdir(kopek);
    yazdir(hayvan);
    yazdir((Kopek) hayvan);
}
yazdir(Kopek)
yazdir(Hayvan)
yazdir(Kopek)
  • hayvan bir Kopek'i gösterse de yazdir(hayvan) → yazdir(Hayvan).
  • Aşırı yüklenmiş sürümler arasındaki seçim argümanın bildirilen tipine göre, derleyici tarafından yapılır.
  • Dönüşüm bildirilen tipi değiştirir: (Kopek) hayvan → yazdir(Kopek).
Aşırı yüklemeGeçersiz kılma
Nerede?Aynı sınıfAlt sınıf
İmzaFarklı parametreAynı imza
SeçimDerleyiciJVM
ÖlçütBildirilen tipGerçek tip

Aşırı Yükleme

Aşırı Yükleme: En Özel Sürüm ve Belirsizlik

static void goster(Object o) { System.out.println("goster(Object)"); }
static void goster(Hayvan h) { System.out.println("goster(Hayvan)"); }
static void goster(String s) { System.out.println("goster(String)"); }
// main içinde:
goster(new Kedi());
goster("a");
Object o = "a";
goster(o);
goster((Hayvan) null);
goster(Hayvan)
goster(String)
goster(Object)
goster(Hayvan)

goster(null);

error: reference to goster is ambiguous
  both method goster(Hayvan) in Ana and
  method goster(String) in Ana match
  • Birden çok sürüm uyarsa derleyici en özel olanı seçer: Kedi hem Hayvan'a hem Object'e uyar; Hayvan daha özeldir.
  • null her başvuru tipine uyar. Hayvan ile String arasında kalıtım ilişkisi olmadığı için hiçbiri ötekinden özel değildir: belirsiz çağrı, derleme hatası.
  • Uyan sürümler tek bir zincir üzerindeyse (Object, Hayvan, Kedi) belirsizlik olmaz: goster(null) en alttakini, goster(Kedi)'yi seçer.
  • Çözüm, ifadenin bildirilen tipini açık dönüşümle belirlemektir: goster((Hayvan) null).

Aşırı Yükleme · Adım adım kod izleme

Derleyici mi, JVM mi? Kod İzleme

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakaryazdir(Hayvan)yazdir(Kopek)Kopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

İki değişkenin bildirilen tipi aynı (Hayvan); gösterdikleri nesneler farklı: Kopek ve Kedi.

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Hayvanyazdir(Hayvan)yazdir(Kopek)Kopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

hayvan bir Kopek'i gösterse de derleyici yalnızca bildirilen tipi bilir: yazdir(Hayvan).

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Hayvanyazdir(Hayvan)yazdir(Kopek)nesne: KopekKopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

Metodun içinde h.sesCikar() geçersiz kılınmış bir metot: JVM nesneye bakar, Hav hav!

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Hayvanyazdir(Hayvan)yazdir(Kopek)Kopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

diger da Hayvan tipinde: aynı sürüm seçilir.

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Hayvanyazdir(Hayvan)yazdir(Kopek)nesne: KediKopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

Aynı satır, farklı nesne: bu kez Kedi.sesCikar(), Miyav!

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Kopekyazdir(Hayvan)yazdir(Kopek)Kopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

Açık dönüşüm ifadenin bildirilen tipini Kopek yapar: derleyici bu kez yazdir(Kopek)'u seçer.

Derleyicibildirilen tipe bakarJVMnesnenin sınıfına bakarargüman tipi: Kopekyazdir(Hayvan)yazdir(Kopek)nesne: KopekKopek.sesCikar()Kedi.sesCikar()derleme zamanıçalışma zamanı

Aşırı yükleme seçimi bildirilen tiple derlemede, geçersiz kılma seçimi gerçek tiple çalışırken yapıldı.

static void yazdir(Hayvan h) {
    System.out.print("yazdir(Hayvan): ");
    h.sesCikar();
}
static void yazdir(Kopek k) {
    System.out.print("yazdir(Kopek): ");
    k.sesCikar();
}
// main içinde:
Hayvan hayvan = new Kopek();
Hayvan diger = new Kedi();
yazdir(hayvan);
yazdir(diger);
yazdir((Kopek) hayvan);

Aşırı Yükleme

Aşırı Yükleme Seçiminin Üç Aşaması

1. Kutulamasız

Yalnızca tam eşleşme, ilkel genişletme (int → long) ve başvuru genişletmesi (String → Object) denenir.

2. Kutulama

1. aşamada aday yoksa kutulama ve kutudan çıkarma da denenir: double → Double → Object.

3. Değişken sayıda

Hâlâ aday yoksa int... gibi değişken sayıda parametreli sürümler devreye girer.

  • Bir aşamada en az bir aday bulunursa sonraki aşamalara geçilmez; o aşamanın adayları arasından en özel olan seçilir.
  • Sonuç: f(long) ve f(Integer) varken f(5) çağrısı f(long)'u seçer. Genişletme kutulamadan önce gelir, çünkü kutulama Java 5'te eklendi ve eski programların davranışı değişmesin istendi.
  • Daraltma (long → int) hiçbir aşamada denenmez; üç aşamada da aday yoksa: no suitable method found for yazdir(boolean).
  • Bu sıra derleme zamanında uygulanır; seçilen imza bayt koduna yazılır. Seçilen imzanın hangi sınıftaki gövdesinin çalışacağı ise çalışma zamanında belirlenir.

Aşırı Yükleme · Adım adım kod izleme

Aşırı Yükleme Aşamaları: Kod İzleme

Argüman: 5 (int)1. kutulamasız2. kutulama3. değişken sayıdaf(long)✓ int → long genişletmef(Integer)✗ kutulama gerekirf(Object)✗ kutulama gerekirf(int...)1. aşamada tek aday: f(long)

5 bir int. 1. aşamada yalnızca genişletme ile long uyar; Integer ve Object kutulama ister, bu aşamada denenmez.

Argüman: b (byte)1. kutulamasız2. kutulama3. değişken sayıdaf(long)✓ byte → long genişletmef(Integer)✗ Byte ≠ Integerf(Object)✗ kutulama gerekirf(int...)yine f(long)

byte da long'a genişler. Kutulansaydı bile Byte olurdu, Integer değil.

Argüman: Integer1. kutulamasız2. kutulama3. değişken sayıdaf(long)✗ kutudan çıkarma gerekirf(Integer)✓ tam eşleşmef(Object)✓ Integer → Objectf(int...)iki aday: en özel olan Integer

Argüman zaten bir Integer: 1. aşamada iki aday var. Integer, Object'ten özel olduğu için seçilir.

Argüman: "x" (String)1. kutulamasız2. kutulama3. değişken sayıdaf(long)✗f(Integer)✗f(Object)✓ String → Objectf(int...)başvuru genişletmesi: f(Object)

String yalnızca Object'e uyar.

Argüman: 2.5 (double)1. kutulamasız2. kutulama3. değişken sayıdaf(long)✗ double → long daraltmaf(Integer)✗ Double ≠ Integerf(Object)✓ double → Double → Objectf(int...)1. aşama boş → 2. aşamada kutulama

double, long'a daraltılamaz; 1. aşamada aday yok. 2. aşamada Double'a kutulanır ve Object'e uyar.

Argüman: () ve (1, 2)1. kutulamasız2. kutulama3. değişken sayıdaf(long)✗ parametre sayısıf(Integer)✗ parametre sayısıf(Object)✗ parametre sayısıf(int...)✓ 0 ya da 2 intyalnızca 3. aşamada aday var

Sıfır ya da iki argümanı yalnızca int... karşılar; ona en son, 3. aşamada başvurulur.

static void f(long x)    { p("f(long)"); }
static void f(Integer x) { p("f(Integer)"); }
static void f(Object x)  { p("f(Object)"); }
static void f(int... x)  { p("f(int...)"); }
// main içinde (p: System.out.println):
byte b = 1;
f(5);
f(b);
f(Integer.valueOf(5));
f("x");
f(2.5);
f();
f(1, 2);

Kalıtım ve Çok Biçimlilik

Kalıtım ve Çok Biçimlilik

  • Kalıtım, sınıf hiyerarşileri oluşturmak için gereklidir.
  • Çok biçimlilik için kalıtım gereklidir: bir sınıf hiyerarşisine sahip olmalıyız.
  • Kalıtımı kullanmak için çok biçimlilik gerekli değildir: yalnızca kod yeniden kullanımı için de kalıtım yapılabilir.
  • B sınıfı A'nın alt sınıfıysa, B'nin nesnesi A'nın nesnesi gibi davranabilir (tersi değil).
  • A referansını B referansına atamak için daraltma dönüşümü (casting) gerekir; parantezle yapılır.
A objA = new B(); // B, A gibi davranır
B objB = (B) objA; // geçerli, ancak
                   // dikkatli kullanılmalı
Son satır derlenir; çalışırken objA gerçekten bir B nesnesini göstermiyorsa ClassCastException fırlatılır.

Java'da arayüzler (interface) de çok biçimlilik sağlar: implements eden her sınıf, arayüz tipindeki referansa atanabilir.

A objAB nesnesi(B) objA → ✓ başarılıA objAA nesnesi(B) objA → ✗ ClassCastException

Kalıtım ve Çok Biçimlilik

Örnek: Şekil Hiyerarşisi

Sekil-isim: String+getIsim(): String+alanHesapla(): double+cevreHesapla(): double+ciz(): voidKare-kenar: double+alanHesapla()+cevreHesapla()+ciz()Dikdortgen-genislik: double-yukseklik: double+alanHesapla()+cevreHesapla()Cember-yaricap: double+alanHesapla()+cevreHesapla()+ciz()
abstract class Sekil {
    private String isim;

    public Sekil(String isim) {
        this.isim = isim;
    }

    public String getIsim() {
        return isim;
    }

    public abstract double alanHesapla();

    public abstract double cevreHesapla();

    public void ciz() {
        System.out.println(isim + " çiziliyor.");
    }
}

abstract: gövdesiz metot; her alt sınıf yazmak zorundadır, new Sekil(…) yapılamaz.

Kalıtım ve Çok Biçimlilik

Alt Sınıflar Metotları Geçersiz Kılar

class Kare extends Sekil {
    private double kenar;
    public Kare(double kenar) {
        super("Kare");
        this.kenar = kenar;
    }
    @Override
    public double alanHesapla() {
        return kenar * kenar;
    }
    @Override
    public double cevreHesapla() {
        return 4 * kenar;
    }
    @Override
    public void ciz() {
        System.out.println("Kare çiziliyor: kenar " + kenar);
    }
}

Kare: yapıcı super("Kare") ile ismi üst sınıfa verir; üç metodu da geçersiz kılar.

class Dikdortgen extends Sekil {
    private double genislik;
    private double yukseklik;
    public Dikdortgen(double genislik, double yukseklik) {
        super("Dikdörtgen");
        this.genislik = genislik;
        this.yukseklik = yukseklik;
    }
    @Override
    public double alanHesapla() {
        return genislik * yukseklik;
    }
    @Override
    public double cevreHesapla() {
        return 2 * (genislik + yukseklik);
    }
    // ciz() ezilmedi → Sekil'inki kullanılır
}

Dikdortgen: alan ve çevreyi geçersiz kılar, ciz()'i geçersiz kılmaz → Sekil.ciz() kullanılır.

class Cember extends Sekil {
    private double yaricap;
    public Cember(double yaricap) {
        super("Çember");
        this.yaricap = yaricap;
    }
    @Override
    public double alanHesapla() {
        return Math.PI * yaricap * yaricap;
    }
    @Override
    public double cevreHesapla() {
        return 2 * Math.PI * yaricap;
    }
    @Override
    public void ciz() {
        System.out.println("Çember çiziliyor: yarıçap " + yaricap);
    }
}

Cember: Math.PI ile hesaplar; ciz()'i kendi bilgisiyle geçersiz kılar.

Kalıtım ve Çok Biçimlilik

Şekiller Dizisi: Adım Adım Kod İzleme

Sekil[] sekiller = new Sekil[3];
sekiller[0] = new Kare(5);
sekiller[1] = new Dikdortgen(4, 6);
sekiller[2] = new Cember(7);

for (Sekil sekil : sekiller) {
    System.out.println(sekil.getIsim()
        + " alanı: " + sekil.alanHesapla());
    sekil.ciz();
}
sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

Dizi Sekil tipinde; öğeler üç farklı alt sınıf nesnesini gösterir.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

sekil.alanHesapla() → Kare.alanHesapla() çalışır.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

ciz() geçersiz kılınmış → Kare.ciz() çalışır.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

sekil.alanHesapla() → Dikdortgen.alanHesapla() çalışır.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

Dikdortgen ciz()'i geçersiz kılmadı → arama yukarı çıkar, Sekil.ciz() çalışır.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

sekil.alanHesapla() → Cember.alanHesapla() çalışır.

sekillerheap0Kare (5)1Dikdortgen (4, 6)2Cember (7)

ciz() geçersiz kılınmış → Cember.ciz() çalışır.

Kalıtım ve Çok Biçimlilik

Neden Çok Biçimlilik?

Tip sınayarak

static double alanKotu(Sekil s) {
    double alan;
    if (s instanceof Kare) {
        alan = ((Kare) s).alanHesapla();
    } else if (s instanceof Dikdortgen) {
        alan = ((Dikdortgen) s).alanHesapla();
    } else if (s instanceof Cember) {
        alan = ((Cember) s).alanHesapla();
    } else {
        alan = 0; // yeni tür unutuldu!
    }
    return alan;
}

Yeni bir şekil eklenince bu metot da değişmeli; unutulursa alanKotu(new DikUcgen(3, 4)) sessizce 0.0 döner.

Çok biçimli

static double toplamAlan(Sekil[] dizi) {
    double toplam = 0;
    for (Sekil sekil : dizi) {
        // her nesne kendi alanını
        toplam += sekil.alanHesapla();
    }
    return toplam;
}
  • Yeni DikUcgen extends Sekil sınıfı eklemek yeter; bu kod hiç değişmez.
  • { Kare(5), Dikdortgen(4, 6), DikUcgen(3, 4) } → 55.0
  • Kod kısalır, ClassCastException riski ortadan kalkar.

İlke: genişletmeye açık, değiştirmeye kapalı kod (open/closed principle).

Kalıtım ve Çok Biçimlilik

Çok Biçimli Parametre ve Dönüş Değeri

class Veteriner {
    void muayene(Hayvan h) {     // her alt sınıf
        System.out.print("Muayene: ");
        h.sesCikar();
    }
    static Hayvan sahiplen(String tur) { // gizli
        if (tur.equals("kedi")) return new Kedi();
        return new Kopek();
    }
}
// main içinde:
Veteriner v = new Veteriner();
v.muayene(new Kedi());
Hayvan h = Veteriner.sahiplen("köpek");
v.muayene(h);
System.out.println(h.getClass().getSimpleName());
Muayene: Miyav!
Muayene: Hav hav!
Kopek
  • Parametre üst sınıf tipinde tanımlanırsa metot, bugün var olan ve ileride yazılacak her alt sınıfın nesnesini kabul eder. muayene yeni bir Kus sınıfı için değişmez.
  • Dönüş tipi üst sınıf olursa metot hangi alt sınıfı ürettiğini gizler. Çağıran yalnızca Hayvan sözleşmesine güvenir; sahiplen başka bir alt sınıf döndürmeye başlasa kullanan kod etkilenmez.
  • Nesneleri üreten bu tür metotlara fabrika metodu (factory method) denir.
  • Genel ilke: parametre ve değişken tipleri, işi görmeye yeten en genel tip olarak seçilir.

Kalıtım ve Çok Biçimlilik

Örnek: Tanksavar Oyunu

Tanksavarx, y: intrenk: Color+topAt(): TopTS Jawelin+topAt(): TopTS Tow2A+topAt(): TopToptopKonum: Rectanglerenk: ColorTop37mmTop57mm

TS Jawelin: TanksavarJawelin, TS Tow2A: TanksavarTow2A. Tank sınıfının da TankT90MS ve TankLeopard2A4 alt sınıfları vardır.

  • Swing ile yazılmış oyunda düşen tanklar vurulur; skor 10'un katı olunca tanksavar türü değişir.
  • Oyun döngüsü yalnızca Tanksavar ve Tank tiplerini bilir.
Tanksavar tanksavar = new TanksavarJawelin(x, y);
...
// hangi top? nesneye bağlı (dinamik bağlama)
toplar.add(tanksavar.topAt());
...
if (tanksavar instanceof TanksavarJawelin) {
    tanksavar = new TanksavarTow2A(tanksavar.x, tanksavar.y);
}

Sınama çıktısı

Top t1 = tanksavar.topAt(); // Jawelin
// → Top37mm, yükseklik 30
tanksavar = new TanksavarTow2A(100, 540);
Top t2 = tanksavar.topAt();
// → Top57mm, yükseklik 40
Aynı topAt() çağrısı, tanksavar değişince farklı bir top üretir; oyun kodunda hiçbir if gerekmez.

Kalıtım ve Çok Biçimlilik

equals ve Kalıtım: Simetri Tuzağı

Object metotları da dinamik bağlanır: Object o = k1; System.out.println(o); nesnenin kendi toString'ini, o.equals(x) kendi equals'ını çalıştırır.

class Nokta {
    final int x, y;
    Nokta(int x, int y) { this.x = x; this.y = y; }
    @Override public boolean equals(Object o) {
        if (!(o instanceof Nokta)) return false;
        Nokta n = (Nokta) o;
        return x == n.x && y == n.y;
    }
    @Override public int hashCode() { return 31 * x + y; }
}
class RenkliNokta extends Nokta {
    final String renk;
    RenkliNokta(int x, int y, String renk) {
        super(x, y);
        this.renk = renk;
    }
    @Override public boolean equals(Object o) {
        if (!(o instanceof RenkliNokta)) return false;
        RenkliNokta r = (RenkliNokta) o;
        return super.equals(r) && renk.equals(r.renk);
    }
}
Nokta p = new Nokta(1, 2);
RenkliNokta k =
    new RenkliNokta(1, 2, "kırmızı");
System.out.println(p.equals(k)
    + " " + k.equals(p));
true false
  • Hangi equals'ın çalışacağını alıcı nesne belirler: p.equals(k) Nokta'nınkini, k.equals(p) RenkliNokta'nınkini çalıştırır.
  • Sonuçlar farklı: simetri bozuldu. Bir HashSet ya da contains, elemanların sırasına göre farklı cevap verebilir.
  • Çözümler: iki sınıfta da getClass() karşılaştırması; ya da kalıtım yerine oluşum (RenkliNokta bir Nokta alanı tutar).

Özet

Özet

KavramKuralÖrnek
Referans kuralıÜst sınıf referansı alt sınıf nesnesini gösterebilir; tersi derlenmezHayvan h = new Kopek();
is-aAlt sınıf bir üst sınıftır; tersi değilh instanceof Hayvan → true
Yukarı dönüşümOtomatik ve her zaman güvenliObject o = kopek;
Aşağı dönüşümAçık yazılır; nesne uygun değilse ClassCastExceptionKopek k = (Kopek) h;
ErişimHangi metotların çağrılabileceğini referans tipi belirlerh.havla() → derleme hatası
Dinamik bağlamaHangi sürümün çalışacağını nesne tipi belirler; arama alttan yukarıh.kos() → Kopek sürümü
Statik bağlamaAlanlar, static/private/final metotlar ve aşırı yükleme seçimih.tur → "hayvan"
FaydaÜst sınıf tipiyle bir kez yazılan kod, yeni alt sınıflarla değişmeden çalışırtoplamAlan(sekiller)

Özet

İlgili Sayfalar

Dinamik Bağlama

Simülatör: üst sınıf tipindeki referansla yapılan çağrıda metot aramasının nesnenin gerçek sınıfından başlayıp yukarı çıkması.

Soyut Sınıflar ve Arayüzler

Arayüz tanımlama ve implements, arayüz tipindeki referanslarla çok biçimlilik, çoklu arayüz, default/static/private arayüz metotları, jenerikler.

Arayüzlerle bağlantısı

Bir sınıf tek bir üst sınıftan türeyebilir, ama birçok arayüzü uygulayabilir: Hayvan h yerine SesCikarabilen s gibi arayüz tipleriyle de aynı dinamik bağlama çalışır.

Özet

Sık Yapılan Hatalar (1): Derleme Hataları

KodDerleyici mesajıNeden / çözüm
Kopek k = new Hayvan();incompatible types: Hayvan cannot be converted to KopekHer hayvan köpek değildir; ters atama yapılamaz.
Kopek k = hayvan;incompatible types: Hayvan cannot be converted to KopekNesne köpek olsa bile açık dönüşüm gerekir: (Kopek) hayvan.
hayvan.havla();cannot find symbol … variable hayvan of type HayvanReferans tipinde metot yok; önce aşağı dönüşüm gerekir.
(Kopek) h.havla();not a statementParantez eksik: ((Kopek) h).havla();
(Kedi) kopekincompatible types: Kopek cannot be converted to KediKardeş sınıflar arasında dönüşüm yoktur.
@Override void sescikar()method does not override or implement a method from a supertypeAd yanlış yazıldı (büyük/küçük harf).
void sesCikar() (üstte public)attempting to assign weaker access privileges; was publicGeçersiz kılan metot erişimi daraltamaz.

Özet

Sık Yapılan Hatalar (2): Çalışma Zamanı ve Mantık

Denetimsiz aşağı dönüşüm

(Kopek) h yazıp h'nin bir Kedi gösterebileceğini unutmak → ClassCastException. Önce instanceof.

"Dönüşüm nesneyi değiştirir" sanmak

(Hayvan) kopek köpeği hayvana çevirmez; sesCikar() yine "Hav hav!" yazar.

Alanların da geçersiz kılındığını sanmak

h.tur referans tipine göre okunur ("hayvan"). Çözüm: alanlar private yapılır, getter metodu geçersiz kılınır.

Aşırı yüklemeyi geçersiz kılma sanmak

Alt sınıfta equals(Kisi k) yazmak equals(Object)'i geçersiz kılmaz, aşırı yükler; koleksiyonlar bu metodu çağırmaz. @Override bu hatayı yakalar.

== ile nesne karşılaştırmak

== referansları karşılaştırır; içerik için geçersiz kılınmış equals kullanılır.

Her yerde instanceof zinciri

Yeni alt sınıf eklendikçe kırılır. Davranış, geçersiz kılınan bir metoda taşınmalıdır.

Özet

Sık Yapılan Hatalar (3)

Yapıcıdan geçersiz kılınabilir metot çağırmak

Alt sınıfın metodu, alanları henüz atanmamış nesnede çalışır: null çıktısı ya da NullPointerException.

Dizi kovaryansına güvenmek

Hayvan[] d = new Kopek[2]; d[1] = new Kedi(); derlenir, çalışırken ArrayStoreException: Kedi.

null ile belirsiz çağrı

goster(null), ilişkisiz iki aşırı yükleme varken: reference to goster is ambiguous. Seçim açık dönüşümle yapılır.

Kutulamanın önce geldiğini sanmak

f(long) ve f(Integer) varken f(5) → f(long): genişletme 1. aşamadadır.

equals'ı alt sınıfta tek yönlü genişletmek

p.equals(k) true, k.equals(p) false: simetri bozulur, koleksiyonlar tutarsızlaşır.

Jenerik listeyi dizi gibi sanmak

List<Hayvan> l = new ArrayList<Kopek>(); → incompatible types. Jenerikler kovaryant değildir.

Özet

Örnek Problem 1: Çıktı Nedir?

class A {
    void yaz() {
        System.out.println("A.yaz");
    }

    void cagir() {
        yaz(); // this.yaz()
    }
}

class B extends A {
    @Override
    void yaz() {
        System.out.println("B.yaz");
    }
}

// main içinde:
A a = new B();
a.cagir();
new A().cagir();

cagir() yalnızca A'da tanımlı ve içinden yaz()'ı çağırıyor. B ise yalnızca yaz()'ı eziyor. Program ne yazdırır?

Cevap
B.yaz
A.yaz
  • a.cagir(): B'de cagir yok → A.cagir() çalışır.
  • İçerideki yaz() aslında this.yaz()'dır; this bir B nesnesi → dinamik bağlama ile B.yaz().
  • new A().cagir(): nesne A → A.yaz().
  • Ders: üst sınıfın kodu, alt sınıfın geçersiz kıldığı metodu farkında olmadan çağırabilir.

Özet

Örnek Problem 2: Derlenir mi, Çalışır mı?

Hayvan h = new Kedi();
Kedi k1 = h;              // (1)
Kedi k2 = (Kedi) h;       // (2)
Kopek k3 = (Kopek) h;     // (3)
h.tirmala();              // (4)
((Kedi) h).tirmala();     // (5)
Object o = new Kopek();
o.sesCikar();             // (6)
((Hayvan) o).sesCikar();  // (7)

Her satır için karar verin: derleme hatası, çalışma zamanı hatası ya da sorunsuz (çıktısıyla).

Cevap
#SonuçNeden
1DerlemeHayvan → Kedi için açık dönüşüm gerekir
2SorunsuzNesne gerçekten Kedi
3ÇalışmaClassCastException: Kedi → Kopek olmaz
4DerlemeHayvan'da tirmala() yok
5Sorunsuz"Kedi tırmalıyor"
6DerlemeObject'te sesCikar() yok
7Sorunsuz"Hav hav!" (dinamik bağlama)

Özet

Örnek Problem 3: Aşırı Yükleme + Geçersiz Kılma

class Ust {
    void f(Object o) {
        System.out.println("Ust.f(Object)");
    }

    void f(String s) {
        System.out.println("Ust.f(String)");
    }
}

class Alt extends Ust {
    @Override
    void f(Object o) {
        System.out.println("Alt.f(Object)");
    }
}

// main içinde:
Ust u = new Alt();
Object o = "merhaba";
u.f(o);
u.f("merhaba");

Ust'te iki aşırı yüklenmiş f var; Alt yalnızca f(Object)'i eziyor. İki çağrı ne yazdırır?

Cevap
Alt.f(Object)
Ust.f(String)
  1. Derleyici sürümü seçer: o'nun bildirilen tipi Object → f(Object). Çalışırken nesne Alt → geçersiz kılınmış sürüm: Alt.f(Object).
  2. Argüman String → en uygun sürüm f(String). Alt bunu geçersiz kılmadığı için Ust.f(String).

Önce aşırı yükleme (derleme zamanı, bildirilen tip), sonra geçersiz kılma (çalışma zamanı, nesne tipi).

Özet

Örnek Problem 4: Kurulum, Aşama, Dizi

class Ust {
    Ust() { goster(); }
    void goster() { System.out.println("Ust"); }
}
class Alt extends Ust {
    private int[] dizi = {1, 2, 3};
    @Override void goster() {
        System.out.println(dizi == null
            ? "dizi yok" : "uzunluk " + dizi.length);
    }
}
  1. new Alt(); ve ardından new Alt().goster(); ne yazdırır?
  2. f(long), f(Integer), f(Object) ve f(int...) varken short s = 3; f(s);, f('A'); ve f(Long.valueOf(4)); hangi sürümleri seçer?
  3. Object[] d = new String[1]; d[0] = 1; derlenir mi, çalışır mı?
Cevapları göster
  1. dizi yok, sonra dizi yok ve uzunluk 3: her new Alt() sırasında Ust(), Alt.goster()'i dizi atanmadan çağırır; kurulumdan sonraki çağrıda dizi hazırdır.
  2. f(long), f(long), f(Object): short ve char, long'a genişler. Long için f(long) kutudan çıkarma ister; 1. aşamada tek aday Object'tir.
  3. Derlenir, çalışırken ArrayStoreException: java.lang.Integer: 1, Integer'a kutulanır ve gerçekte String[] olan diziye yazılamaz.

Özet

Alıştırmalar (1)

  1. Calisan (maas() = temel maaş), Mudur (temel × 1,5) ve Stajyer (sabit 5000) sınıflarını yazın. Calisan[] = { Ayşe 20000, Müdür Mehmet 30000, Stajyer Can } için toplam maaş?
  2. X: ad() → "X", yaz() → println(ad()). Y extends X: ad() → "Y". Z extends Y: yaz() → println("Z:" + ad()). { new X(), new Y(), new Z() } için her birinde yaz() çağrılırsa çıktı?
  3. Object o = new Kopek(); ardından o.sesCikar(); neden derlenmez? Nasıl düzeltilir, düzeltilince ne yazar?
Cevaplar / ipuçları
  1. Mudur.maas(), return temelMaas * 1.5; ile geçersiz kılınır; döngüde toplam += c.maas(); → 20000 + 45000 + 5000 = 70000.0.
  2. X, Y, Z:Y. Y'de yaz() X'ten gelir ama içindeki ad() Y'ninkidir; Z'de ad() Y'den gelir.
  3. Object'te sesCikar() yok (cannot find symbol). ((Hayvan) o).sesCikar(); → Hav hav!

Özet

Alıştırmalar (2)

  1. Şekil hiyerarşisine DikUcgen(a, b) ekleyin (alan = a·b/2, çevre = a + b + hipotenüs). DikUcgen(3, 4) için alan, çevre ve ciz() çıktıları nedir? Şekil döngüsünde ne değişir?
  2. instanceof zincirli döngüyü (Kopek → havla, Kedi → tırmala, diğer → "Özel davranış yok") hiç instanceof kullanmadan yazın.
  3. yazdir(Hayvan) ve yazdir(Kopek) aşırı yüklemeleri varken Object o = new Kopek(); yazdir(o); derlenir mi?
Cevaplar / ipuçları
  1. Dik üçgen alanı: 6.0, Dik üçgen çevresi: 12.0, Dik üçgen çiziliyor. (ciz() geçersiz kılınmadıysa). Döngü hiç değişmez; yalnızca diziye yeni nesne eklenir.
  2. Hayvan'a void ozelDavranis() ("Özel davranış yok") ekleyin; Kopek ve Kedi bunu ezsin. Döngü: x.ozelDavranis(); → aynı üç satır.
  3. Hayır: bildirilen tip Object; iki sürüm de Object kabul etmez (no suitable method found for yazdir(Object)). yazdir((Kopek) o) ile derlenir.

Özet

Kaynaklar

  • Oracle, The Java Tutorials: Polymorphism; Overriding and Hiding Methods; Inheritance.
  • J. Gosling ve diğ., The Java Language Specification, Java SE 21 Edition (5.5 Casting Contexts, 8.4.8 Inheritance, Overriding, and Hiding, 15.12 Method Invocation Expressions).
  • P. Deitel ve H. Deitel, Java: How to Program, 11. baskı, Pearson (Bölüm 10: Object-Oriented Programming: Polymorphism and Interfaces).
  • K. Sierra ve B. Bates, Head First Java, 3. baskı, O'Reilly (Inheritance and Polymorphism).
  • C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson (Bölüm 5: Inheritance).
  • A. Downey ve C. Mayfield, Think Java, 2. baskı, O'Reilly.

Son

Bölüm 4.4: Çok Biçimlilik
Sonraki bölüm: Bölüm 4.5: Soyut Sınıflar ve Arayüzler · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.