Çok biçimlilik: dynamic binding, upcasting, downcasting
Üst sınıf tipindeki değişken alt sınıf nesnesini tutunca hangi metot çalışır, neyi derleyici kabul eder, cast ne zaman patlar.
Çok biçimlilik (polymorphism), “aynı çağrı, farklı nesnede farklı davranış” demek. Bir Shape dizisine daire, kare, üçgen koyarsın; hepsine area() dersin, her biri kendi alanını hesaplar. Sınavın en ağır çıktı soruları bu konudan gelir: üst sınıf tipindeki değişken, alt sınıf nesnesi, araya super ve cast serpiştirilmiş kod. Hepsini iki soru sorarak çözersin: derleyici neye izin verir ve çalışırken hangi sürüm çalışır.
Altın kurallar:
- Derleyici değişkenin tipine bakar (static type). Değişkenin tipinde olmayan metodu çağıramazsın.
- JVM nesnenin tipine bakar (dynamic type). Override edilmiş metotlarda nesnenin sürümü çalışır.
- Upcasting otomatik, downcasting cast ister. Cast yanlışsa çalışırken
ClassCastException. - Dynamic binding sadece override edilmiş instance metotlarda. Alanlar, static ve private metotlar değişkenin tipine göre seçilir.
Static tip ve dynamic tip
Animal a = new Lion();
Bu satırda iki tip var. a değişkeninin tipi Animal (static tip, derleme zamanında belli). Gösterdiği nesnenin tipi Lion (dynamic tip, çalışma zamanında belli). Bu atama geçerli çünkü her Lion bir Animal’dır (is-a). Tersine Lion l = new Animal(); derlenmez.
Örneklerde kullanacağımız hiyerarşi:
class Animal {
void speak() { System.out.println("Animal speaks"); }
}
class Cat extends Animal {
@Override void speak() { System.out.println("Meow"); }
void purr() { System.out.println("Purr"); }
}
class Lion extends Cat {
@Override void speak() { System.out.println("Roar"); }
}
Dynamic binding
Override edilmiş bir metodu çağırdığında hangi sürümün çalışacağı programın çalıştığı anda, nesneye bakılarak belirlenir. Buna dynamic binding (ya da late binding) denir.
Animal a = new Lion();
a.speak();
Animal b = new Cat();
b.speak();
((Cat) b).purr();
Roar
Meow
Purr
Gerçek gücü dizilerde ve metot parametrelerinde görürsün. Aşağıda Shape.toString() sadece bir kez yazıldı ama içindeki name() ve area() her nesnede farklı çalışıyor:
class Shape {
String name() { return "Shape"; }
double area() { return 0; }
public String toString() { return name() + " area=" + area(); }
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override String name() { return "Circle"; }
@Override double area() { return Math.PI * r * r; }
double diameter() { return 2 * r; }
}
class Square extends Shape {
double s;
Square(double s) { this.s = s; }
@Override String name() { return "Square"; }
@Override double area() { return s * s; }
}
Shape[] shapes = { new Circle(1), new Square(2), new Shape() };
for (Shape s : shapes) System.out.println(s);
Circle area=3.141592653589793
Square area=4.0
Shape area=0.0
Yeni bir Triangle sınıfı eklersen bu döngüye dokunmana gerek yok. Polimorfizmin asıl faydası bu: kod üst tiple yazılır, yeni alt tipler eklendikçe çalışmaya devam eder.
super ile karışık çağrılar
super.a() sadece o tek çağrıyı üst sınıfın sürümüne yönlendirir. Üst sınıfın metodu içinde başka bir metot çağrılırsa (b() aslında this.b()), this hâlâ alt sınıf nesnesidir ve yine dynamic binding devreye girer.
class First {
void a() { System.out.println("First.a"); b(); }
void b() { System.out.println("First.b"); }
}
class Second extends First {
@Override void a() { System.out.println("Second.a"); super.a(); }
@Override void b() { System.out.println("Second.b"); }
}
First f = new Second();
f.a();
Second.a
First.a
Second.b
f.a() nesne Second → Second.a "Second.a"
super.a() açıkça First.a "First.a"
this.b() this = Second nesnesi → Second.b "Second.b"
Upcasting ve downcasting
| Upcasting | Downcasting | |
|---|---|---|
| Yön | Alt → üst (Cat → Animal) | Üst → alt (Animal → Cat) |
| Yazım | Animal a = cat; | Cat c = (Cat) a; |
| Cast gerekir mi? | Hayır, otomatik | Evet |
| Risk | Yok | Nesne o tip değilse ClassCastException |
| Neden yapılır? | Ortak tiple çalışmak (dizi, parametre) | Alt sınıfa özel metodu çağırmak |
Downcasting’de iki ayrı kontrol olur:
- Derleme zamanı: Tipler aynı kalıtım çizgisinde mi?
(Cat) animalRefderlenir. Kardeş sınıflar arasında, örneğin(Dog) catRef, derlenmez:incompatible types: Cat cannot be converted to Dog. - Çalışma zamanı: Nesne gerçekten o tipte mi (ya da alt tipinde mi)? Değilse
ClassCastException.
try {
Animal an = new Animal();
Cat ct = (Cat) an; // derlenir, ama nesne Cat değil
ct.purr();
} catch (ClassCastException e) {
System.out.println("ClassCastException");
}
ClassCastException
TUZAK · derleniyor diye çalışacak sanma. “Find the error” sorularında önce derleme hatası mı çalışma hatası mı olduğunu ayır. Animal’dan Cat’e cast her zaman derlenir; patlayıp patlamayacağı nesneye bağlı.
instanceof
x instanceof T, x’in gösterdiği nesne T tipinde ya da T’nin bir alt tipinde ise true döner. Downcast’ten önce kontrol için kullanılır.
Animal l = new Lion();
System.out.println(l instanceof Animal);
System.out.println(l instanceof Cat);
System.out.println(l instanceof Lion);
Animal plain = new Animal();
System.out.println(plain instanceof Cat);
Animal nul = null;
System.out.println(nul instanceof Animal);
true
true
true
false
false
Klasik kalıp ve Java 16’dan beri gelen kısa yazımı (pattern matching):
static void describe(Shape s) {
System.out.print(s.name());
if (s instanceof Circle) { // klasik
Circle c = (Circle) s;
System.out.print(" d=" + c.diameter());
}
System.out.println();
}
describe(new Circle(1.5));
describe(new Square(3));
Shape sh = new Circle(2);
if (sh instanceof Circle cc) // kontrol + cast tek satırda
System.out.println("r=" + cc.r);
Circle d=3.0
Square
r=2.0
İPUCU · instanceof zinciri kötü kokar.
if (s instanceof Circle) ... else if (s instanceof Square) ...yazıyorsan genelde o işi Shape’e bir metot olarak koyup override etmen gerekir. Sınavdaki “use polymorphism” ifadesi tam olarak bunu ister.
Overload ve override bir arada
En zor çıktı sorusu tipi. İki adımda çöz:
- Derleme zamanı, imza seçimi: argümanların static tiplerine bakarak hangi parametre listesinin çağrılacağı belirlenir (overload).
- Çalışma zamanı, sürüm seçimi: o imzanın, nesnenin gerçek sınıfındaki override edilmiş sürümü çalışır.
class X {
void m(Object o) { System.out.println("X.m(Object)"); }
void m(String s) { System.out.println("X.m(String)"); }
}
class Y extends X {
@Override void m(Object o) { System.out.println("Y.m(Object)"); }
}
X obj = new Y();
obj.m("hi"); // imza m(String); Y onu override etmedi
Object o = "hi";
obj.m(o); // imza m(Object); Y override etti
new X().m(o);
X.m(String)
Y.m(Object)
X.m(Object)
o gerçekte bir String tutsa da, imza seçiminde değişkenin tipi (Object) kullanılır.
Dynamic binding olmayan şeyler
| Üye | Kim belirler? |
|---|---|
| Override edilmiş instance metot | Nesnenin gerçek tipi |
Alan (ref.x) | Değişkenin tipi |
| static metot | Değişkenin tipi (gizleme, override değil) |
| private metot | Override edilemez, hep tanımlandığı sınıfınki |
| Overload seçimi | Argümanların static tipi |
Animal.info(); // static
Animal c2 = new Cat();
c2.info(); // nesne Cat ama static: Animal'ınki
Animal info
Animal info
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
Animal a = new Cat(); a.purr(); çalışır | Derlenmez; derleyici Animal’a bakar |
| Değişken Animal ise Animal’ın speak’i çalışır | Override edilmişse nesnenin sürümü çalışır |
| Cast nesneyi dönüştürür | Nesne değişmez, sadece referansın tipi değişir |
| Derlenen cast güvenlidir | Nesne uygun değilse ClassCastException |
(Dog) cat çalışmada patlar | Daha derlenmez: kardeş sınıflar |
null instanceof X hata fırlatır | false döner |
| super.a() içindeki her çağrı üst sınıfa gider | Sadece o çağrı; içerdeki b() yine alt sınıfınki |
| Static metot da nesneye göre seçilir | Değişkenin tipine göre |
| Overload seçiminde nesnenin tipi önemli | Argümanın static tipi önemli |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed?
class Animal { void speak() { System.out.println("Animal speaks"); } }
class Cat extends Animal { @Override void speak() { System.out.println("Meow"); } }
class Lion extends Cat { @Override void speak() { System.out.println("Roar"); } }
// in main:
Animal a = new Lion();
a.speak();Cevabı göster
Değişkenin tipi Animal ama nesne Lion. Override edilmiş metot çalışma zamanında nesnenin gerçek tipine göre seçilir (dynamic binding). Lion'un speak'i çalışır.
Find the compile error.
class Animal { void speak() { } }
class Cat extends Animal { void purr() { } }
// in main:
Animal a = new Cat();
a.purr();Cevabı göster
Derleyici sadece değişkenin tipine (static type) bakar. Animal'da purr yok, nesnenin Cat olduğunu derleyici bilmez. javac: cannot find symbol ... method purr() ... variable a of type Animal. Çözüm: ((Cat) a).purr();.
Find the compile error.
Animal a = new Animal();
Cat c = a;Cevabı göster
Yukarı dönüşüm (Cat → Animal) otomatiktir, aşağı dönüşüm değildir. javac: incompatible types: Animal cannot be converted to Cat. (Cat) a yazarsan derlenir ama bu örnekte nesne gerçekten Animal olduğu için çalışırken ClassCastException fırlar.
What happens?
Animal an = new Animal();
Cat ct = (Cat) an;
ct.purr();Cevabı göster
Cast ile derleyiciye "bana güven, bu bir Cat" dersin; Cat, Animal'ın alt sınıfı olduğu için derleyici kabul eder. Çalışma zamanında JVM nesneye bakar: düz Animal, Cat değil. ClassCastException fırlar, purr hiç çalışmaz.
Find the compile error.
class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }
// in main:
Cat c = new Cat();
Dog d = (Dog) c;Cevabı göster
Cast ancak tipler aynı kalıtım çizgisindeyse derlenir. Bir Cat değişkeni hiçbir zaman Dog nesnesi tutamaz, derleyici bunu bilir. javac: incompatible types: Cat cannot be converted to Dog. (Animal değişkeninden Dog'a cast derlenirdi.)
What is printed?
Animal l = new Lion(); // Lion extends Cat extends Animal
System.out.println(l instanceof Cat);
Animal plain = new Animal();
System.out.println(plain instanceof Cat);
Animal nul = null;
System.out.println(nul instanceof Animal);Cevabı göster
instanceof nesnenin gerçek tipine bakar ve kalıtımı sayar: Lion bir Cat'tir, true. Düz Animal nesnesi Cat değildir, false. null hiçbir tipin örneği değildir, instanceof null için her zaman false döner (hata fırlatmaz).
What is printed?
class First {
void a() { System.out.println("First.a"); b(); }
void b() { System.out.println("First.b"); }
}
class Second extends First {
@Override void a() { System.out.println("Second.a"); super.a(); }
@Override void b() { System.out.println("Second.b"); }
}
// in main:
First f = new Second();
f.a();Cevabı göster
Nesne Second: f.a() Second'ın a'sını çalıştırır. super.a() First'ün a'sına gider ve First.a basar. First.a içindeki b() aslında this.b(); this hâlâ Second nesnesi, bu yüzden Second'ın b'si çalışır. super sadece o tek çağrıyı yukarı yönlendirir, nesnenin tipini değiştirmez.
What is printed?
class X {
void m(Object o) { System.out.println("X.m(Object)"); }
void m(String s) { System.out.println("X.m(String)"); }
}
class Y extends X {
@Override void m(Object o) { System.out.println("Y.m(Object)"); }
}
// in main:
X obj = new Y();
obj.m("hi");
Object o = "hi";
obj.m(o);Cevabı göster
İki aşama var. (1) Derleme zamanında hangi imza: argümanın static tipine bakılır. "hi" String, en uygun imza m(String). o Object, imza m(Object). (2) Çalışma zamanında o imzanın hangi sürümü: nesne Y. Y sadece m(Object)'i override etti. Birinci çağrı X'in m(String)'ine, ikincisi Y'nin m(Object)'ine gider.
What is printed?
class Animal { static void info() { System.out.println("Animal info"); } }
class Cat extends Animal { static void info() { System.out.println("Cat info"); } }
// in main:
Animal c = new Cat();
c.info();Cevabı göster
static metotlar override edilmez, gizlenir. Nesneye bakılmaz; değişkenin tipi (Animal) belirler. Bu yüzden dynamic binding sadece instance metotlar için geçerli. Static metodu sınıf adıyla çağırmak (Cat.info()) karışıklığı önler.
What is printed?
Shape[] shapes = { new Circle(1), new Square(2), new Shape() };
for (Shape s : shapes) System.out.println(s);
// Shape.toString() returns name() + " area=" + area();
// Circle and Square override name() and area(), Shape returns "Shape" and 0Cevabı göster
toString sadece Shape'te yazılı ama içindeki name() ve area() her nesnenin kendi sürümünü çalıştırır. Circle(1): π · 1² = 3.141592653589793. Square(2): 2 · 2 = 4.0. Shape: 0 değeri double döndüğü için 0.0.
Given Animal a = new Cat(); which line compiles AND runs without exception? (a) Cat c = a; (b) Cat c = (Cat) a; (c) Lion l = (Lion) a;
- (a)
- (b)
- (c)
Cevabı göster
(a) cast olmadan aşağı dönüşüm derlenmez. (b) derlenir ve nesne gerçekten Cat olduğu için çalışır. (c) derlenir (Lion, Animal'ın alt sınıfı) ama nesne Lion değil, çalışırken ClassCastException.
Write a static method totalArea(Shape[] shapes) that returns the sum of the areas of all shapes, using polymorphism (no instanceof). For a Square(2), a Square(3) and a Circle(1) it should return about 16.14.
Cevabı göster
Her nesne kendi area() sürümünü getirir; metot tiplerle hiç uğraşmaz. Yeni bir şekil sınıfı eklense bile bu metot değişmez, polimorfizmin asıl faydası bu:
public static double totalArea(Shape[] shapes) {
double sum = 0;
for (Shape s : shapes) {
sum += s.area(); // dynamic binding
}
return sum;
}4 + 9 + π = 16.14159..., printf("%.2f", ...) ile 16.14.Doğrulama: Tüm kod örnekleri ve cevaplar JDK 26 ile çalıştırıldı (java verify/SEN1002/W03.java); derleme hataları javac ile denendi (verify/SEN1002/err/E03a-c.java).