Bölüm 4: Çok Biçimlilik
Giriş
Öğrenme Çıktıları
Çok biçimliliği (polymorphism) tanımlamak; derleme zamanı ve çalışma zamanı çok biçimliliğini ayırt etmek.
Ü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.
Yukarı ve aşağı dönüşümü (upcasting, downcasting) yazmak; ClassCastException'ı instanceof ile önlemek.
Ç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.
Overloading ile overriding'i karşılaştırmak; hangisinin derleyici, hangisinin JVM tarafından seçildiğini bilmek.
Ş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 nesnesindeHav hav!, kedi nesnesindeMiyav!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.
Çok Biçimlilik Kavramı
Çok Biçimliliğin İki Türü
| Derleme zamanı (statik) | Çalışma zamanı (dinamik) | |
|---|---|---|
| Mekanizma | Aşırı yükleme (overloading) | Metot ezme (overriding) + dinamik bağlama |
| Seçimi yapan | Derleyici (javac) | JVM, program çalışırken |
| Neye bakılır? | Argümanların sayısı ve bildirilen türleri | Nesnenin gerçek sınıfı |
| Kalıtım gerekir mi? | Hayır, tek sınıfta olur | Evet, 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
- 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()veequals()herkeste vardır. Kopek,sesCikar()vekos()metotlarını ezer (override),havla()metodunu ekler.KediyalnızcasesCikar()'ı ezer vetirmala()'yı ekler;kos()veuyu()Hayvan'dan gelir.
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());
new Kopek() öbekte bir nesne oluşturur; kopek referansı onu gösterir.
Yeni nesne oluşmaz: hayvan, aynı nesneyi gösteren ikinci referanstır. Kopek bir Hayvan olduğu için atama geçerlidir.
Her sınıf Object'ten türediği için Object referansı da aynı nesneyi gösterebilir.
== referansları karşılaştırır: ikisi de aynı nesneyi gösterdiği için true.
Yine aynı nesne: true. Üç referans, tek nesne.
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 öbekte yer ayırır ve Kopek() yapıcısını çağırır.
Kopek() başlar. İlk satır her zaman super(…) ya da this(…) çağrısıdır.
super(): önce üst sınıfın parçası kurulmalıdır.
Hayvan() çalışır; o da gizli super() ile Object()'i çağırmış ve bitirmiştir.
Hayvan kısmının gövdesi çalışır.
Hayvan() döner; şimdi Kopek()'in kalan gövdesi çalışır.
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 h = new Kopek(); // bildirilen tür: Hayvan // gerçek tür: Kopek
| Soru | Kim 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.
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
- 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;
| İfade | Sonuç |
|---|---|
h2 instanceof Kopek | true |
h2 instanceof Hayvan | true |
h2 instanceof Object | true |
h2 instanceof Kedi | false |
h3 instanceof Kopek | false |
bos instanceof Hayvan | false |
null hiçbir türün örneği değildir.
Tür Dönüşümü
Yukarı ve Aşağı Dönüşüm
| Yukarı (upcasting) | Aşağı (downcasting) | |
|---|---|---|
| Yön | Alt → üst | Üst → alt |
| Yazım | Otomatik (örtük) | Açık: (Kopek) h |
| Güvenli mi? | Her zaman | Nesneye 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 yokerror: 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:
Hayvantüründeki bir referans pekâlâ birKopek'i gösteriyor olabilir. - JVM, dönüşüm anında nesnenin gerçek sınıfını denetler: nesne bir
Kediya da düz birHayvanise 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: nesneTur'den ya da onun bir alt sınıfındansatrue.- Önce sına, sonra dönüştür:
ClassCastExceptionoluşamaz.
Java 16+: örüntü eşleme (pattern matching)
if (x instanceof Kopek kk) { kk.havla(); // dönüşüm otomatik }
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:
publicmetotpublickalmalıdır. private,staticvefinalmetotlar ezilemez.@Overrideyazmak 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"); } }
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
h'nin türüne (Hayvan) bakar: kos() var mı? Var → çağrı derlenir. Yoksa derleme hatası.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ı
Nesne bir Kopek: aramalar hep en alttan, gerçek sınıftan başlar.
kos() Kopek'te var → arama biter, Kopek sürümü çalışır.
uyu() Kopek'te yok → bir üste çık: Hayvan'da bulundu.
toString() en tepede, Object'te bulunur: sınıf adı + @ + onaltılık karma kod (çalıştırmaya göre değişir).
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(); }
Hayvan türünde 3 elemanlı dizi: elemanlar referanstır, başlangıçta null.
0. eleman bir Kopek'i gösterir (yukarı dönüşüm, otomatik).
1. eleman bir Kedi'yi gösterir.
2. eleman düz bir Hayvan nesnesidir. Dizi artık üç farklı biçim tutuyor.
i = 0, 0 < 3 doğru → döngü gövdesi.
Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Kopek.sesCikar().
i = 1, 1 < 3 doğru → döngü gövdesi.
Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Kedi.sesCikar().
i = 2, 2 < 3 doğru → döngü gövdesi.
Aynı satır, farklı nesne: JVM nesnenin sınıfına bakar → Hayvan.sesCikar().
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
| Üye | Neye göre seçilir? |
|---|---|
| Örnek metodu | Nesne türü (dinamik) |
| Alan (değişken) | Referans türü (statik) |
static metot | Referans türü (statik) |
private / final | Ezilemez, 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)
hayvanbirKopek'i gösterse deyazdir(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ükleme | Ezme | |
|---|---|---|
| Nerede? | Aynı sınıf | Alt sınıf |
| İmza | Farklı parametre | Aynı imza |
| Seçim | Derleyici | JVM |
| Ölçüt | Bildirilen tür | Gerç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ı
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
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(); }
Dizi Sekil türünde; elemanlar üç farklı alt sınıf nesnesini gösterir.
sekil.alanHesapla() → Kare.alanHesapla() çalışır.
ciz() ezilmiş → Kare.ciz() çalışır.
sekil.alanHesapla() → Dikdortgen.alanHesapla() çalışır.
Dikdortgen ciz()'i ezmedi → arama yukarı çıkar, Sekil.ciz() çalışır.
sekil.alanHesapla() → Cember.alanHesapla() çalışır.
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 Sekilsınıfı eklemek yeter; bu kod hiç değişmez. - { Kare(5), Dikdortgen(4, 6), DikUcgen(3, 4) } → 55.0
- Kod kısalır,
ClassCastExceptionriski 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
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
TanksavarveTanktü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
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ğlamaylaKisisürümü.equals(Object): önceinstanceof, sonra aşağı dönüşüm.equalsezilincehashCode()da ezilmelidir.
Özet
Özet
| Kavram | Kural | Örnek |
|---|---|---|
| Referans kuralı | Üst sınıf referansı alt sınıf nesnesini gösterebilir; tersi derlenmez | Hayvan h = new Kopek(); |
| is-a | Alt sınıf bir üst sınıftır; tersi değil | h instanceof Hayvan → true |
| Yukarı dönüşüm | Otomatik ve her zaman güvenli | Object o = kopek; |
| Aşağı dönüşüm | Açık yazılır; nesne uygun değilse ClassCastException | Kopek k = (Kopek) h; |
| Erişim | Hangi metotların çağrılabileceğini referans türü belirler | h.havla() → derleme hatası |
| Dinamik bağlama | Hangi sürümün çalışacağını nesne türü belirler; arama alttan yukarı | h.kos() → Kopek sürümü |
| Statik bağlama | Alanlar, static/private/final metotlar ve aşırı yükleme seçimi | h.tur → "hayvan" |
| Fayda | Üst sınıf türüyle bir kez yazılan kod, yeni alt sınıflarla değişmeden çalışır | toplamAlan(sekiller) |
Özet
İlgili Sayfalar
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.
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ı
| Kod | Derleyici mesajı | Neden / çözüm |
|---|---|---|
Kopek k = new Hayvan(); | incompatible types: Hayvan cannot be converted to Kopek | Her hayvan köpek değildir; ters atama yapılamaz. |
Kopek k = hayvan; | incompatible types: Hayvan cannot be converted to Kopek | Nesne köpek olsa bile açık dönüşüm gerekir: (Kopek) hayvan. |
hayvan.havla(); | cannot find symbol … variable hayvan of type Hayvan | Referans türünde metot yok; önce dönüştürün. |
(Kopek) h.havla(); | not a statement | Parantez eksik: ((Kopek) h).havla(); |
(Kedi) kopek | incompatible types: Kopek cannot be converted to Kedi | Kardeş sınıflar arasında dönüşüm yoktur. |
@Override void sescikar() | method does not override or implement a method from a supertype | Ad yanlış yazıldı (büyük/küçük harf). |
void sesCikar() (üstte public) | attempting to assign weaker access privileges; was public | Ezen metot erişimi daraltamaz. |
Özet
Sık Yapılan Hatalar (2): Çalışma Zamanı ve Mantık
(Kopek) h yazıp h'nin bir Kedi gösterebileceğini unutmak → ClassCastException. Önce instanceof.
(Hayvan) kopek köpeği hayvana çevirmez; sesCikar() yine "Hav hav!" yazar.
h.tur referans türüne göre okunur ("hayvan"). Alanları private yapıp getter metodu ezin.
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.
instanceof zinciriYeni 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'decagiryok →A.cagir()çalışır.- İçerideki
yaz()aslındathis.yaz()'dır;thisbirBnesnesi → dinamik bağlama ileB.yaz(). new A().cagir(): nesneA→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 |
|---|---|---|
| 1 | Derleme | Hayvan → Kedi için açık dönüşüm gerekir |
| 2 | Sorunsuz | Nesne gerçekten Kedi |
| 3 | Çalışma | ClassCastException: Kedi → Kopek olmaz |
| 4 | Derleme | Hayvan'da tirmala() yok |
| 5 | Sorunsuz | "Kedi tırmalıyor" |
| 6 | Derleme | Object'te sesCikar() yok |
| 7 | Sorunsuz | "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)
- Derleyici sürümü seçer:
o'nun bildirilen türüObject→f(Object). Çalışırken nesneAlt→ ezilmiş sürüm: Alt.f(Object). - Argüman
String→ en uygun sürümf(String).Altbunu 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)
Calisan(maas()= temel maaş),Mudur(temel × 1,5) veStajyer(sabit 5000) sınıflarını yazın.Calisan[]= { Ayşe 20000, Müdür Mehmet 30000, Stajyer Can } için toplam maaş?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 birindeyaz()çağrılırsa çıktı?Object o = new Kopek();ardındano.sesCikar();neden derlenmez? Nasıl düzeltilir, düzeltilince ne yazar?
Cevaplar / ipuçları
Mudur.maas(),return temelMaas * 1.5;ile ezilir; döngüdetoplam += c.maas();→ 20000 + 45000 + 5000 = 70000.0.- X, Y, Z:Y. Y'de
yaz()X'ten gelir ama içindekiad()Y'ninkidir; Z'dead()Y'den gelir. Object'tesesCikar()yok (cannot find symbol).((Hayvan) o).sesCikar();→ Hav hav!
Özet
Alıştırmalar (2)
- Şekil hiyerarşisine
DikUcgen(a, b)ekleyin (alan = a·b/2, çevre = a + b + hipotenüs).DikUcgen(3, 4)için alan, çevre veciz()çıktıları nedir? Şekil döngüsünde ne değişir? instanceofzincirli döngüyü (Kopek → havla, Kedi → tırmala, diğer → "Özel davranış yok") hiçinstanceofkullanmadan yazın.yazdir(Hayvan)veyazdir(Kopek)aşırı yüklemeleri varkenObject o = new Kopek(); yazdir(o);derlenir mi?
Cevaplar / ipuçları
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.Hayvan'avoid ozelDavranis()("Özel davranış yok") ekleyin;KopekveKedibunu ezsin. Döngü:x.ozelDavranis();→ aynı üç satır.- Hayır: bildirilen tür
Object; iki sürüm deObjectkabul 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.