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

Java ile Nesne Yönelimli Programlama
Çok biçimlilik kavramıReferans kuralı ve is-aYukarı ve aşağı dönüşüminstanceof Dinamik bağlamaAşırı yüklemeŞekil ve tanksavar örnekleritoString ve equals
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; is-a ilişkisini kullanmak.

Tür 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 ezme

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

Tasarım

Şekil dizisi, tanksavar oyunu ve toString/equals örnekleriyle üst sınıf türüyle genel kod yazmak.

Ç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 türündeki 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 türüyle 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 otomobili sürersiniz: "otomobil" arayüzüyle konuşursunuz, 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 ezme (overriding) + dinamik bağlama
Seçimi yapanDerleyici (javac)JVM, program çalışırken
Neye bakılır?Argümanların sayısı ve bildirilen türleriNesnenin 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!

Bu bölümün odağı çalışma zamanı çok biçimliliğidir; "çok biçimlilik" denince çoğunlukla bu kastedilir.

Çok Biçimlilik Kavramı

Bölüm Boyunca Kullanılan Hiyerarşi

Object+toString(): String+equals(Object): boolean+getClass(): ClassHayvan+sesCikar(): void+kos(): void+uyu(): voidKopek+sesCikar(): void+kos(): void+havla(): voidKedi+sesCikar(): void+tirmala(): void
  • Boş üçgenli ok kalıtımı (extends) gösterir: ok alt sınıftan üst sınıfa yönelir.
  • Her sınıf, yazmasak da Object'ten türer; toString() ve equals() herkeste vardır.
  • Kopek, sesCikar() ve kos() metotlarını ezer (override), havla() metodunu ekler.
  • Kedi yalnızca sesCikar()'ı ezer ve tirmala()'yı ekler; kos() ve uyu() Hayvan'dan gelir.
Kopek bir Hayvan'dır (is-a); Kedi de bir Hayvan'dır. Bu yüzden ikisi de Hayvan türündeki bir değişkende tutulabilir.

Çok Biçimlilik Kavramı

Sınıfların Kodu

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 Kedi extends Hayvan {
    @Override
    public void sesCikar() {
        System.out.println("Miyav!");
    }
    public void tirmala() {
        System.out.println("Kedi tırmalıyor");
    }
}
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");
    }
}

@Override notu (annotation), derleyiciye "bu metot üst sınıftaki bir metodu ezer" der; imza yanlış yazılırsa derleme hatası alınır.

Referanslar ve Tür 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şkenleriÖbekteki (heap) nesnekopektür: KopekKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

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

Referans değişkenleriÖbekteki (heap) nesnekopektür: Kopekhayvantür: 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şkenleriÖbekteki (heap) nesnekopektür: Kopekhayvantür: Hayvannesnetür: 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şkenleriÖbekteki (heap) nesnekopektür: Kopekhayvantür: Hayvannesnetür: 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şkenleriÖbekteki (heap) nesnekopektür: Kopekhayvantür: Hayvannesnetür: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

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

Referans değişkenleriÖbekteki (heap) nesnekopektür: Kopekhayvantür: Hayvannesnetür: ObjectKopek nesnesiObject kısmıHayvan kısmıKopek kısmı

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

Referanslar ve Tür Uyumu

Nesne Oluşturulurken Yapıcılar Zinciri

class Hayvan {
    public Hayvan() {
        System.out.println("Hayvan yapıcısı");
    }
}

class Kopek extends Hayvan {
    public Kopek() {
        super(); // derleyici ekler
        System.out.println("Kopek yapıcısı");
    }
}

// main içinde:
Kopek kopek = new Kopek();
new Kopek()Object kısmıHayvan kısmıKopek kısmı

new öbekte yer ayırır ve Kopek() yapıcısını çağırır.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

Kopek() başlar. İlk satır her zaman super(…) ya da this(…) çağrısıdır.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

super(): önce üst sınıfın parçası kurulmalıdır.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

Hayvan() çalışır; o da gizli super() ile Object()'i çağırmış ve bitirmiştir.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

Hayvan kısmının gövdesi çalışır.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

Hayvan() döner; şimdi Kopek()'in kalan gövdesi çalışır.

new Kopek()Object kısmıHayvan kısmıKopek kısmı

Nesne tamamlandı; adresi kopek'e atanır. Yapıcılar yukarıdan aşağıya çalıştı.

Referanslar ve Tür Uyumu

Referans Türü ve Nesne Türü

Hayvan hreferansKopek nesnesiöbektebildirilen türderleyici bakargerçek türJVM 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 türüne bakar
Hangi sürüm çalışır?JVM, nesne türüne bakar
Hangi atamalar geçerli?Derleyici, referans türüne 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 özelliği kullanamazsınız: nesne bir köpek olsa da Hayvan "kumandasında" havla() düğmesi yoktur.

Referanslar ve Tür Uyumu

Referans Kuralı

  • Bir nesnenin alt türünü, üst sınıf türündeki bir referansa atayabiliriz.
  • Ancak üst sınıfın nesnesini, alt sınıf türündeki 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.

Dikkat: Hayvan h = new Kopek(); Kopek k = h; de aynı hatayı verir. Derleyici h'nin gösterdiği nesneyi değil, bildirilen türünü (Hayvan) görür; çözüm açık dönüşümdür.

Referanslar ve Tür Uyumu

Üst Sınıf ve Alt Sınıf: is-a İlişkisi

ObjectHayvanKopekKedi
  • is-a ilişkisi yalnızca alt sınıftan üst sınıfa doğru geçerlidir.
  • Köpek is a Hayvan: her köpek bir hayvandır.
  • Hayvan is not only a Köpek: her hayvan bir köpek değildir.
  • Bir üst sınıf nesnesi, alt sınıflardan herhangi birinin nesnesi değildir.

instanceof ile sınama

Hayvan h2 = new Kopek();
Hayvan h3 = new Hayvan();
Hayvan bos = null;
İfadeSonuç
h2 instanceof Kopektrue
h2 instanceof Hayvantrue
h2 instanceof Objecttrue
h2 instanceof Kedifalse
h3 instanceof Kopekfalse
bos instanceof Hayvanfalse

null hiçbir türün örneği değildir.

Tür 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

Tür Dönüşümü

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

  • Bir üst sınıf referansı, alt sınıf türüne 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();

Tür Dönüşümü

Dikkatli Kullanın: 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 türündeki 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 değilseniz önce instanceof.

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

Tür 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ına, sonra dönüştür: ClassCastException oluşamaz.

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 ezilen bir metoda taşımak daha iyidir (Alıştırma 5).

Dinamik Bağlama

Hatırlatma: Metot Ezme (Overriding)

  • Alt sınıf, üst sınıftan aldığı bir metodu aynı imzayla (ad + parametre listesi) yeniden yazar.
  • Dönüş türü aynı olmalıdır (ya da bir alt türü: kovaryant dönüş).
  • Erişim belirleyici daraltılamaz: public metot public kalmalıdır.
  • private, static ve final metotlar ezilemez.
  • @Override yazmak zorunlu değildir, ama yazım hatalarını derleme anında yakalar.
  • Ezilen sürüme alt sınıfın içinden super.metot() ile ulaşılabilir.
class Kopek extends Hayvan {
    @Override
    public void kos() {
        super.kos(); // Hayvan sürümü
        System.out.println("...ve havlıyor");
    }
}
Ezme ≠ aşırı yüklemeEzme: aynı imza, farklı sınıf. Aşırı yükleme: aynı ad, farklı parametreler.

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 türüne 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 türüne (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.

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 karma kod (ç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 türü Hayvan'dan yukarı yapar ve bulamaz.

Dinamik Bağlama

Çok Biçimli Dizi: Kodu Adım Adım İzle

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();
}
hayvanlaröbek0null1null2null

Hayvan türünde 3 elemanlı dizi: elemanlar referanstır, başlangıçta null.

hayvanlaröbek0Kopek nesnesi1null2null

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2null

1. eleman bir Kedi'yi gösterir.

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

hayvanlaröbek0Kopek nesnesi1Kedi nesnesi2Hayvan nesnesi

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

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 türü (dinamik)
Alan (değişken)Referans türü (statik)
static metotReferans türü (statik)
private / finalEzilemez, statik

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

Aşırı Yükleme

Aşırı Yükleme (Overloading)

public class HesapMakinesi {
    public int topla(int sayi1, int sayi2) {
        return sayi1 + sayi2;
    }
    public double topla(double sayi1, double sayi2) {
        return sayi1 + sayi2;
    }
    public int topla(int sayi1, int sayi2, int sayi3) {
        return sayi1 + sayi2 + sayi3;
    }
    // dizinin elemanlarını topla
    public int topla(int[] sayilar) {
        int toplam = 0;
        for (int sayi : sayilar) {
            toplam += sayi;
        }
        return toplam;
    }
}
  • Aynı sınıfta aynı adlı, parametre listesi farklı (sayı, tür, sıra) metotlar.
  • Derleyici argümanlara bakarak derleme zamanında seçer.
  • Yalnızca dönüş türü farklı olan iki metot aşırı yükleme değildir.
HesapMakinesi hm =
        new HesapMakinesi();
int[] sayilar = {1, 2, 3, 4, 5};
int s1 = hm.topla(5, 3);
double s2 = hm.topla(3.5, 2.7);
int s3 = hm.topla(4, 6, 8);
int s4 = hm.topla(sayilar);

Değerler

s1 = 8, s2 = 6.2,
s3 = 18, s4 = 15

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 türüne göre, derleyici tarafından yapılır.
  • Dönüşüm bildirilen türü değiştirir: (Kopek) hayvan → yazdir(Kopek).
Aşırı yüklemeEzme
Nerede?Aynı sınıfAlt sınıf
İmzaFarklı parametreAynı imza
SeçimDerleyiciJVM
ÖlçütBildirilen türGerçek tür

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 türündeki referansa atanabilir.

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. Ayrıntılar bir sonraki bölümde.

Kalıtım ve Çok Biçimlilik

Alt Sınıflar Metotları Ezer

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 ezer.

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 ezer, ciz()'i ezmez → 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 ezer.

Kalıtım ve Çok Biçimlilik

Şekiller Dizisi: Kodu Adım Adım İzle

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();
}
sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

Dizi Sekil türünde; elemanlar üç farklı alt sınıf nesnesini gösterir.

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

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

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

ciz() ezilmiş → Kare.ciz() çalışır.

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

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

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

Dikdortgen ciz()'i ezmedi → arama yukarı çıkar, Sekil.ciz() çalışır.

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

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

sekilleröbek0Kare (5)1Dikdortgen (4, 6)2Cember (7)

ciz() ezilmiş → Cember.ciz() çalışır.

Kalıtım ve Çok Biçimlilik

Neden Çok Biçimlilik?

Tür 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

Ö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 türlerini 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

Object Metotlarını Ezmek: toString ve equals

public class Kisi {
    private String ad;
    private int yas;
    public Kisi(String ad, int yas) {
        this.ad = ad;
        this.yas = yas;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Kisi)) return false;
        Kisi kisi = (Kisi) obj; // aşağı dönüşüm
        return yas == kisi.yas
            && ad.equals(kisi.ad);
    }

    @Override
    public String toString() {
        return "Kisi[Ad=" + ad
            + ", Yas=" + yas + "]";
    }
}
Kisi k1 = new Kisi("Ali", 25);
Kisi k2 = new Kisi("Ali", 25);
System.out.println(k1 == k2);
System.out.println(k1.equals(k2));
Object nesne = k1;
System.out.println(nesne);
System.out.println(nesne.equals(k2));
false
true
Kisi[Ad=Ali, Yas=25]
true
  • println(nesne), toString()'i çağırır: dinamik bağlamayla Kisi sürümü.
  • equals(Object): önce instanceof, sonra aşağı dönüşüm.
  • equals ezilince hashCode() da ezilmelidir.

Ö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 türü belirlerh.havla() → derleme hatası
Dinamik bağlamaHangi sürümün çalışacağını nesne türü 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 türüyle bir kez yazılan kod, yeni alt sınıflarla değişmeden çalışırtoplamAlan(sekiller)

Özet

İlgili Sayfalar

Java Interfaces (Arayüzler)

Arayüz tanımlama ve implements, arayüz türündeki referanslarla çok biçimlilik, çoklu arayüz kalıtımı, default/static/private arayüz metotları. Sayfa İngilizcedir.

Bu bölümle 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 türleriyle 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 türünde metot yok; önce dönüştürün.
(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 publicEzen 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 ezildiğini sanmak

h.tur referans türüne göre okunur ("hayvan"). Alanları private yapıp getter metodu ezin.

Aşırı yüklemeyi ezme sanmak

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

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

== referansları karşılaştırır; içerik için ezilmiş equals kullanın.

Her yerde instanceof zinciri

Yeni alt sınıf eklendikçe kırılır. Davranışı ezilen bir metoda taşıyın.

Ö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 ezdiği 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 + Ezme

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 türü Object → f(Object). Çalışırken nesne Alt → ezilmiş sürüm: Alt.f(Object).
  2. Argüman String → en uygun sürüm f(String). Alt bunu ezmediği için Ust.f(String).

Önce aşırı yükleme (derleme zamanı, bildirilen tür), sonra ezme (çalışma zamanı, nesne türü).

Ö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 ezilir; 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() ezilmediyse). 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 tür 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: Çok Biçimlilik
Sonraki bölüm: Bölüm 4: Soyut Sınıflar ve Arayüzler
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.