SEN2001 · 11. hafta · Object-oriented Programming

Nesne yönelimli programlama: kalıtım, dynamic binding ve vtable

OOP'nin üç özelliği, kalıtım ve alt tipler, dynamic binding ile hangi metodun çalışacağını bulmak, override/overload/hiding farkı, çoklu kalıtım ve dynamic binding'in vtable ile uygulanması.

object-oriented programmingclassobjectinstance variableclass variablemethodmessageinheritancesubclasssuperclassoverridingpolymorphismdynamic bindingstatic bindingabstract methodabstract classsubtypemultiple inheritancediamond probleminterfaceclass instance recordvirtual method tablevtable

Nesne yönelimli programlama (OOP) üç fikrin birleşimidir: abstract data type (geçen hafta), kalıtım (inheritance) ve dynamic binding. Bu sayfanın sınav karşılığı iki tür soru: “Hangi metot çalışır, ne basılır?” (override, overload, hiding, yapıcı sırası) ve “Dynamic binding nasıl uygulanır?” (vtable). İlk türde kural setini bilmek yeter.

Altın kurallar:

  1. Override edilen instance metot nesnenin gerçek sınıfına göre seçilir.
  2. Overload seçimi derlemede, argümanın bildirilen tipine göre yapılır.
  3. Alanlar ve static metotlar dinamik bağlanmaz, değişkenin tipine bakılır.
  4. Üst sınıfın yapıcısı önce çalışır.
  5. vtable: üst sınıfınkini kopyala, override edileni değiştir, yeniyi sona ekle.

Neden kalıtım?

ADT’ler yeniden kullanılabilir ama neredeyse her zaman küçük değişiklik gerekir; kodu kopyalayıp değiştirmek bakım kâbusu. Kalıtım, var olan bir sınıftan (superclass, parent) yeni bir sınıf (subclass, derived) türetir: alt sınıf üst sınıfın alanlarını ve metotlarını alır, yenilerini ekler, bazılarını override eder.

Terimler:

  • Class: ADT. Object: sınıfın bir örneği (instance).
  • Method: sınıfta tanımlı alt program. Message: bir nesneye metot çağrısı göndermek.
  • Instance variable: her nesnenin kendi kopyası. Class variable (Java static): sınıf başına bir tane.
  • Alt sınıfın nesnesi üst sınıfın beklendiği her yerde kullanılabiliyorsa alt sınıf bir subtype’tır. Java’da public kalıtım bunu sağlar.

Kalıtımın dezavantajı: sınıflar arasında bağımlılık yaratır. Üst sınıftaki bir değişiklik tüm alt sınıfları etkileyebilir.

Dynamic binding

Üst sınıf tipinde bir referans, alt sınıflardan birinin nesnesini gösterebilir. Çağrılan metot override edilmişse hangi sürüm çalışır? Nesnenin gerçek sınıfınınki. Bu karar çalışma zamanında verilir: dynamic binding (polimorfizm).

Animal a = new Dog();
System.out.println(a.speak() + " " + a.name + " " + a.kind() + " " + a.intro());
Woof animal Animal I say Woof
İfadeSeçim kuralıSonuç
a.speak()Override, dinamik: nesne DogWoof
a.nameAlan, bildirilen tip Animalanimal
a.kind()Static metot, bildirilen tipAnimal
a.intro()Animal’da yazılı, içinde speak() dinamikI say Woof

Faydası: for (Animal an : zoo) an.speak(); kodu, ileride eklenecek Cat sınıfıyla da değişmeden çalışır.

Override ile overload karışınca

class P { void m(Object o) {...P.Object} void m(String s) {...P.String} }
class Q extends P { @Override void m(Object o) {...Q.Object} }
P p = new Q(); Object o = "text";
p.m(o); p.m("text");
Q.Object P.String

Tarif: (1) derleme zamanı: p’nin tipi P, argümanın bildirilen tipine göre P’deki imzalardan biri seçilir. o Object tipinde, içinde String olsa bile m(Object) seçilir. (2) çalışma zamanı: seçilen imzanın nesnenin sınıfındaki (Q) sürümü çalışır.

Yapıcı sırası

class A { A() { System.out.print("A "); show(); } void show() { ... "A.show" } }
class B extends A { int v = 7; B() { super(); System.out.print("B "); }
                    @Override void show() { ... "B.show v=" + v } }
new B();
A B.show v=0 B

Üst sınıf yapıcısı önce çalışır, alt sınıfın alan başlatıcıları ondan sonra. A’nın yapıcısındaki show() dinamik bağlanır ve henüz başlatılmamış v’yi (0) görür.

super ve private

X z = new Z();      // Z.hi → "Z+" + super.hi() → Y.hi → "Y+" + super.hi() → X.hi
System.out.println(z.hi());
System.out.println(new Derived().call());   // private who() override edilmez
Z+Y+X
Base

super.m() her zaman doğrudan üst sınıfın sürümüne gider. private metotlar miras alınmaz; aynı adlı metot alt sınıfta ayrı bir metottur.

Dil tasarım soruları

SoruJavaC++SmalltalkC#
Her şey nesne mi?Primitifler hariçHayır (hibrit)EvetDeğer tipleri hariç (boxing var)
Varsayılan bağlamaDinamik (private/static/final hariç)Statik; virtual ile dinamikHep dinamikStatik; virtual + override
Çoklu kalıtımSınıfta yok, interface ileVarYokSınıfta yok, interface ile
Nesne nerede?HeapStack ya da heapHeapHeap (class), stack (struct)
Bellek geri almaGarbage collectionProgramcı (delete, destructor)GCGC

C++‘ta stack’teki bir nesne üst sınıf tipindeki bir değişkene değerle atanırsa alt sınıfın alanları kesilir (object slicing). Java’da tüm nesneler heap’te ve referansla erişildiği için bu sorun yok.

Abstract sınıf ve çoklu kalıtım

abstract class Shape { abstract double area(); }
Shape s = new Shape();     // error: Shape is abstract; cannot be instantiated

Çoklu kalıtımda iki üst sınıf aynı imzalı iki metot getirirse (diamond problem) hangisi seçilir? C++ programcıya Walker::move ile nitelemeyi zorunlu tutar. Java sınıflarda çoklu kalıtıma izin vermez; interface’lerdeki default metotlar çakışırsa derleyici override ister:

interface Walker { default String move() { return "walk"; } }
interface Swimmer { default String move() { return "swim"; } }
class Duck implements Walker, Swimmer {
    public String move() { return Walker.super.move() + "&" + Swimmer.super.move(); }
}
walk&swim

Uygulama: CIR ve vtable

Class instance record (CIR), bir nesnenin bellekteki yapısıdır; record gibi derlemede belirlenir. Alt sınıfın CIR’i, üst sınıfın CIR’ini kopyalar ve kendi alanlarını sona ekler. Böylece üst sınıfın alanları her alt sınıf nesnesinde aynı ofsettedir.

Dinamik bağlanan metotlar için her sınıfın bir virtual method table’ı (vtable) vardır: sınıftaki dinamik bağlanan her metot için kodun adresi. Her CIR, sınıfının vtable’ını gösteren bir pointer taşır.

class Shape  { area(); draw(); name(); }
class Circle extends Shape  { override area(); new radius(); }
class Ring   extends Circle { override draw(); override area(); }

slot    Shape vtable    Circle vtable    Ring vtable
0       Shape.area      Circle.area      Ring.area
1       Shape.draw      Shape.draw       Ring.draw
2       Shape.name      Shape.name       Shape.name
3       -               Circle.radius    Circle.radius
for (Shape s : new Shape[]{ new Shape(), new Circle(), new Ring() })
    System.out.println(s.area() + " " + s.draw() + " " + s.name());
Shape.area Shape.draw Shape.name
Circle.area Shape.draw Shape.name
Ring.area Ring.draw Shape.name

Derleyici s.area() çağrısını “nesnenin vtable’ında slot 0’daki adrese git” koduna çevirir. Hangi sınıf olursa olsun slot 0 hep area’dır; çalışmada sadece hangi tablonun okunduğu değişir. Maliyet: her çağrıda bir dolaylı adresleme. Statik bağlanan metotlar (C++ non-virtual, Java private/final) bu maliyeti ödemez ve vtable’da yer almaz.

Çoklu kalıtımda iş karmaşıklaşır: nesnenin her üst sınıf için ayrı bir vtable göstergesi ve parçası olur.

Sık yapılan hatalar

YanlışDoğrusu
a.name nesnenin sınıfına göreAlanlar bildirilen tipe göre (hiding)
Static metot override edilirGizlenir (hiding), dinamik değil
Overload çalışmada seçilirDerlemede, bildirilen tiple
Yapıcıda alt sınıf alanları hazırHenüz varsayılan değerde (0, null)
super.hi() dinamikDoğrudan üst sınıfa statik
private metot override edilebilirMiras alınmaz; ayrı metot
C++‘ta tüm metotlar dinamikSadece virtual olanlar
vtable’da override edilmeyen metot yokÜst sınıfın sürümü aynı slotta kalır

Kendini test et

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

Soru 1orta

What is printed in Java?

class Animal {
    String name = "animal";
    String speak() { return "..."; }
    static String kind() { return "Animal"; }
    String intro() { return "I say " + speak(); }
}
class Dog extends Animal {
    String name = "dog";
    @Override String speak() { return "Woof"; }
    static String kind() { return "Dog"; }
}
// in main:
Animal a = new Dog();
System.out.println(a.speak() + " " + a.name + " " + a.kind() + " " + a.intro());
Cevabı göster
Woof animal Animal I say Woof

Instance metodu dynamic binding ile nesnenin gerçek sınıfına göre seçilir: Dog.speak → Woof. Alanlar ve static metotlar dinamik bağlanmaz, değişkenin bildirilen tipine (Animal) göre seçilir: a.name = "animal", a.kind() = "Animal" (hiding). intro Animal'da yazılı ama içindeki speak() çağrısı da dinamik: "I say Woof".

Soru 2zor

What is printed in Java?

class A { A() { System.out.print("A "); show(); }
          void show() { System.out.print("A.show "); } }
class B extends A { int v = 7;
          B() { super(); System.out.print("B "); }
          @Override void show() { System.out.print("B.show v=" + v + " "); } }
// in main:
new B();
Cevabı göster
A B.show v=0 B

Önce üst sınıfın yapıcısı çalışır: "A ". A'nın yapıcısı show() çağırır; nesne aslında B olduğu için B.show çalışır. Ama B'nin alan başlatıcıları (v = 7) henüz çalışmadı, v varsayılan 0. Sonra B'nin yapıcısı: "B ". Yapıcıdan override edilebilir metot çağırmak bu yüzden tehlikeli.

Soru 3zor

What is printed in Java?

class P { void m(Object o) { System.out.print("P.Object "); }
          void m(String s) { System.out.print("P.String "); } }
class Q extends P { @Override void m(Object o) { System.out.print("Q.Object "); } }
// in main:
P p = new Q();
Object o = "text";
p.m(o);
p.m("text");
Cevabı göster
Q.Object P.String

İki aşama: (1) Overload seçimi derleme zamanında, argümanın bildirilen tipine göre: o Object → m(Object) imzası; "text" String → m(String). (2) Override seçimi çalışma zamanında, nesnenin gerçek sınıfına göre: m(Object) Q'da override edilmiş → Q.Object; m(String) Q'da yok → P.String.

Soru 4orta

Fill in the vtable of class Ring: which implementation does each slot point to?

class Shape  { area(); draw(); name(); }
class Circle extends Shape  { override area(); new radius(); }
class Ring   extends Circle { override draw(); override area(); }
Cevabı göster
area: Ring.area, draw: Ring.draw, name: Shape.name, radius: Circle.radius

Alt sınıfın vtable'ı üst sınıfınkinin kopyasıyla başlar, slot sırası korunur: [area, draw, name] + Circle'ın eklediği [radius]. Override edilen slotlar yeni sürümle değiştirilir. Ring area ve draw'ı değiştirir; name Shape'ten, radius Circle'dan miras. Çağrı s.area() derlemede "slot 0" olarak çevrilir, çalışmada nesnenin vtable'ından okunur.

Soru 5zor

What is printed in Java?

class Base { private String who() { return "Base"; }
             String call() { return who(); } }
class Derived extends Base { private String who() { return "Derived"; } }
// in main:
System.out.println(new Derived().call());
Cevabı göster
Base

private metotlar miras alınmaz ve override edilemez; Derived'daki who tamamen ayrı bir metot. Base.call içindeki who() derleme zamanında Base.who'ya statik bağlanır. Java'da private, static ve final metotlar statik bağlanır; vtable'a girmeleri gerekmez.

Soru 6orta

What is printed in Java?

class X { String hi() { return "X"; } }
class Y extends X { @Override String hi() { return "Y+" + super.hi(); } }
class Z extends Y { @Override String hi() { return "Z+" + super.hi(); } }
// in main:
X z = new Z();
System.out.println(z.hi());
Cevabı göster
Z+Y+X

Dinamik bağlama Z.hi'yi seçer. super.hi() dinamik değildir: her zaman sınıfın doğrudan üst sınıfındaki sürümü çağırır. Z → Y.hi → X.hi zinciri: "Z+" + "Y+" + "X".

Soru 7kolay

Does this Java code compile? Why?

abstract class Shape { abstract double area(); }
// in main:
Shape s = new Shape();
Cevabı göster
No: an abstract class cannot be instantiated

Abstract metot gövdesiz bir protokoldür; en az bir abstract metodu olan sınıf abstract'tır ve nesnesi yaratılamaz. Amacı alt sınıflara ortak bir arayüz vermek. Shape s = new Circle(); gibi alt sınıf nesnesiyle kullanılır. C++'ta karşılığı pure virtual function: virtual double area() = 0;.

Soru 8orta

Two Java interfaces Walker and Swimmer both declare default String move(). A class Duck implements Walker, Swimmer does not override move. What happens, and how is it fixed?

Cevabı göster
compile error (incompatible defaults); Duck must override move, e.g. calling Walker.super.move()

Çoklu kalıtımın klasik sorunu (diamond problem): iki üst tipten aynı imzalı iki uygulama geliyor, hangisi? Java derleyici seçim yapmaz, sınıfın açıkça override etmesini ister. Walker.super.move() + "&" + Swimmer.super.move() → "walk&swim". C++ çoklu sınıf kalıtımına izin verir ve belirsizliği Walker::move gibi nitelemeyle çözdürür.

Soru 9kolay

Name the three characteristic features of an object-oriented language.

Cevabı göster
abstract data types (encapsulation), inheritance, dynamic binding of method calls

ADT verinin temsilini gizler. Kalıtım (inheritance) var olan bir sınıfı yeniden kullanıp değiştirmeyi sağlar; ADT'lerin yeniden kullanım sorununu çözer. Dynamic binding (polimorfizm) bir üst sınıf referansıyla alt sınıfların kendi metotlarını çağırmayı sağlar; yeni alt sınıf eklemek mevcut kodu değiştirmez.

Soru 10orta

In C++, method draw is declared in class Shape without the virtual keyword and redefined in Circle. Which version runs for Shape* p = new Circle(); p->draw();?

Cevabı göster
Shape::draw (static binding)

C++'ta varsayılan statik bağlamadır; sadece virtual bildirilen metotlar dinamik bağlanır ve vtable'a girer. virtual olmadan çağrı pointer'ın tipine (Shape*) göre derlemede çözülür. Java'da tersi: private/static/final değilse her metot dinamik bağlanır. C++ bu tercihi verimlilik için yapar.

Soru 11kolay

What is printed in Java?

Animal[] zoo = { new Animal(), new Dog(), new Dog() };
int woofs = 0;
for (Animal an : zoo) if (an.speak().equals("Woof")) woofs++;
System.out.println(woofs + " " + (zoo[1] instanceof Animal) + " " + (zoo[0] instanceof Dog));
Cevabı göster
2 true false

Aynı an.speak() çağrısı her elemanda nesnenin sınıfına göre farklı metodu çalıştırır: polimorfizm. İki Dog → 2. Dog bir Animal'dır (alt tip, subtype): true. Animal nesnesi Dog değildir: false. Alt tip ilişkisi tek yönlü.

Soru 12orta

What does a class instance record (CIR) contain in a typical implementation, and where are the methods themselves stored?

Cevabı göster
the instance variables (including inherited ones) plus a pointer to the class's vtable; method code is stored once per class

CIR bir nesnenin yapısıdır, record gibi derlemede belirlenir. Alt sınıfın CIR'i üst sınıfınkini kopyalar ve kendi alanlarını sona ekler; bu yüzden alt sınıf nesnesi üst sınıf referansıyla güvenle kullanılabilir. Dinamik bağlanan metotlar için CIR'de vtable göstergesi bulunur. Statik bağlananların CIR'de yeri yoktur.

Doğrulama: Tüm Java çıktıları JDK ile çalıştırıldı (java verify/SEN2001/W11.java); abstract sınıf örnekleme ve çakışan default metot örnekleri javac ile derlenmeye çalışıldı ve beklenen derleme hatalarını verdi (verify/SEN2001/w11b/). vtable tablosu, her sınıfta her metodun hangi sürümünün çalıştığı Java'da gerçekten çağrılarak dolduruldu. C++ virtual/non-virtual ve Smalltalk bilgileri kavramsal; derleyicileri kurulu değil.

Deftere not kartı

OOP = ADT + inheritance + dynamic binding class, object (instance), method, message. instance variable vs class (static) variable override: alt sınıf aynı imzayı yeniden tanımlar → DİNAMİK seçilir overload: aynı ad farklı parametre → DERLEMEDE argümanın bildirilen tipine göre alan ve static metot: dinamik DEĞİL, bildirilen tipe göre (hiding) Java: private/static/final statik bağlı, gerisi dinamik. C++: sadece virtual dinamik super.m(): hep doğrudan üst sınıfın sürümü (dinamik değil) yapıcı sırası: üst → alt. yapıcıdan override metot çağrısı alt sınıf alanlarını 0 görür abstract class örneklenemez. C++: pure virtual = 0 subtype: alt sınıf nesnesi üst tip yerine geçebilir (tek yönlü) çoklu kalıtım: C++ var; Java sınıfta yok, interface ile. diamond → açık override CIR = alanlar (miras + yeni) + vtable pointer vtable: üst sınıfınki kopyalanır, override slotu değişir, yeni metot sona eklenir çağrı: derlemede slot numarası, çalışmada nesnenin vtable'ından adres