SEN1002 · 3. hafta · Polymorphism

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

polymorphismdynamic bindinglate bindingstatic typedynamic typeupcastingdowncastinginstanceofClassCastExceptionpattern matching

Ç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:

  1. Derleyici değişkenin tipine bakar (static type). Değişkenin tipinde olmayan metodu çağıramazsın.
  2. JVM nesnenin tipine bakar (dynamic type). Override edilmiş metotlarda nesnenin sürümü çalışır.
  3. Upcasting otomatik, downcasting cast ister. Cast yanlışsa çalışırken ClassCastException.
  4. 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

UpcastingDowncasting
YönAlt → üst (Cat → Animal)Üst → alt (Animal → Cat)
YazımAnimal a = cat;Cat c = (Cat) a;
Cast gerekir mi?Hayır, otomatikEvet
RiskYokNesne 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:

  1. Derleme zamanı: Tipler aynı kalıtım çizgisinde mi? (Cat) animalRef derlenir. Kardeş sınıflar arasında, örneğin (Dog) catRef, derlenmez: incompatible types: Cat cannot be converted to Dog.
  2. Ç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:

  1. Derleme zamanı, imza seçimi: argümanların static tiplerine bakarak hangi parametre listesinin çağrılacağı belirlenir (overload).
  2. Ç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

ÜyeKim belirler?
Override edilmiş instance metotNesnenin gerçek tipi
Alan (ref.x)Değişkenin tipi
static metotDeğişkenin tipi (gizleme, override değil)
private metotOverride edilemez, hep tanımlandığı sınıfınki
Overload seçimiArgü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üşünceDoğrusu
Animal a = new Cat(); a.purr(); çalışırDerlenmez; derleyici Animal’a bakar
Değişken Animal ise Animal’ın speak’i çalışırOverride edilmişse nesnenin sürümü çalışır
Cast nesneyi dönüştürürNesne değişmez, sadece referansın tipi değişir
Derlenen cast güvenlidirNesne uygun değilse ClassCastException
(Dog) cat çalışmada patlarDaha derlenmez: kardeş sınıflar
null instanceof X hata fırlatırfalse döner
super.a() içindeki her çağrı üst sınıfa giderSadece o çağrı; içerdeki b() yine alt sınıfınki
Static metot da nesneye göre seçilirDeğişkenin tipine göre
Overload seçiminde nesnenin tipi önemliArgümanın static tipi önemli

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

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
Roar

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.

Soru 2kolay

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
a.purr(): Animal tipinde purr metodu yok

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();.

Soru 3orta

Find the compile error.

Animal a = new Animal();
Cat c = a;
Cevabı göster
Animal → Cat otomatik dönüşmez, cast gerekir

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.

Soru 4orta

What happens?

Animal an = new Animal();
Cat ct = (Cat) an;
ct.purr();
Cevabı göster
Derlenir, çalışırken ClassCastException

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.

Soru 5orta

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
Cat ile Dog kardeş sınıf, birbirine cast edilemez

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

Soru 6orta

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
true, false, false

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

Soru 7zor

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
Second.a / First.a / Second.b

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.

Soru 8zor

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
X.m(String) ve Y.m(Object)

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

Soru 9zor

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
Animal info

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.

Soru 10orta

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 0
Cevabı göster
Circle area=3.141592653589793 / Square area=4.0 / Shape area=0.0

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.

Soru 11orta

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;

  1. (a)
  2. (b)
  3. (c)
Cevabı göster
(b)

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

Soru 12orta

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
for-each döngüsünde s.area() topla

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

Deftere not kartı

Ust ref = new Alt(); → upcasting, OTOMATİK, her zaman güvenli. Static tip (değişkenin tipi): DERLEYİCİ ne çağırabileceğine buna bakar. Dynamic tip (nesnenin tipi): ÇALIŞIRKEN override edilmiş hangi sürüm, buna bakar. ref.altaOzelMetot() → derleme hatası (cannot find symbol). Önce cast. Alt a = (Alt) ref; → downcasting, cast ŞART. Nesne gerçekten Alt değilse ClassCastException. Kardeş sınıflar arası cast (Cat → Dog): derleme hatası. instanceof: gerçek tip + kalıtım. null instanceof X → false. Güvenli kalıp: if (s instanceof Circle c) { c.diameter(); } super.m() sadece o çağrıyı yukarı atar; içerdeki this.b() yine alt sınıfın b'si. Overload seçimi derleme zamanı (argüman static tipi), override seçimi çalışma zamanı. static metot, alan, private metot: dynamic binding YOK.