← Kursa Dön
📄 Text · 15 min

Kalıtım (Inheritance) Temelleri

Kalıtım Nedir?

Kalıtım (inheritance), bir sınıfın başka bir sınıfın özelliklerini ve davranışlarını devralmasıdır. Java'da en temel OOP kavramlarından biri. Bir sınıf yazıyorsun, sonra "ya bunun biraz farklı bir versiyonu lazım" diyorsun — işte kalıtım tam burada devreye giriyor.

Düşün ki bir fabrikada araba üretiyorsun. Temel araba tasarımın var: motor, 4 tekerlek, direksiyon. Şimdi spor araba yapmak istiyorsun — sıfırdan başlamıyorsun. Temel tasarımı alıp üstüne turbo motor, spoiler ekliyorsun. İşte kalıtım da bu: mevcut bir sınıfı alıp, üstüne yeni şeyler eklemek ya da var olanları değiştirmek.

extends Anahtar Kelimesi

Java'da kalıtım extends keyword'ü ile yapılır. Bir sınıf sadece tek bir sınıftan kalıtım alabilir — Java'da çoklu kalıtım (multiple inheritance) sınıflar için yoktur.

class Animal {
    String name;
    int age;

    void eat() {
        System.out.println(name + " is eating.");
    }

    void sleep() {
        System.out.println(name + " is sleeping.");
    }
}

class Dog extends Animal {
    String breed;

    void bark() {
        System.out.println(name + " says: Woof!");
    }
}

Burada Dog sınıfı Animal sınıfından türetildi. Dog artık name, age, eat() ve sleep() özelliklerinin hepsine sahip — üstüne bir de kendi breed alanı ve bark() metodu var.

Dog myDog = new Dog();
myDog.name = "Karabaş";
myDog.age = 3;
myDog.breed = "Golden Retriever";

myDog.eat();   // Karabaş is eating.
myDog.sleep(); // Karabaş is sleeping.
myDog.bark();  // Karabaş says: Woof!

Gördüğün gibi Dog nesnesi hem kendi metotlarını hem de Animal'dan devraldığı metotları kullanabiliyor.

Superclass ve Subclass

Terminolojiyi netleştirelim:

  • Superclass (üst sınıf, parent class, base class): Kalıtım veren sınıf. Yukarıdaki örnekte Animal.

  • Subclass (alt sınıf, child class, derived class): Kalıtım alan sınıf. Yukarıdaki örnekte Dog.

Bir subclass, superclass'ın tüm public ve protected üyelerine erişebilir. Private üyeler devralınır ama doğrudan erişilemez — getter/setter ile ulaşılır.

class Vehicle {
    private String engine;     // subclass erişemez (doğrudan)
    protected int speed;       // subclass erişebilir
    public String color;       // herkes erişebilir

    public String getEngine() {
        return engine;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }
}

class Car extends Vehicle {
    int numberOfDoors;

    void displayInfo() {
        // System.out.println(engine);  // HATA! private
        System.out.println(getEngine()); // OK, getter ile
        System.out.println(speed);       // OK, protected
        System.out.println(color);       // OK, public
    }
}

💡 İpucu: Private alanlar subclass'a "geçer" ama görünmez. Yani bellekte vardır, ama subclass kodu doğrudan erişemez. Bunu bir kilitli çekmece gibi düşün — çekmece sende ama anahtarı yok, getter/setter o anahtar.

IS-A İlişkisi

Kalıtım bir IS-A (bir türüdür) ilişkisi kurar. Dog extends Animal dediğinde, "Her Dog bir Animal'dır" demiş oluyorsun. Bu sadece sözdizimsel değil, mantıksal bir bağ.

Dog dog = new Dog();
System.out.println(dog instanceof Dog);    // true
System.out.println(dog instanceof Animal); // true
System.out.println(dog instanceof Object); // true

IS-A ilişkisi geçişli (transitive) bir ilişkidir. Eğer Dog bir Animal ise ve Animal bir Object ise, o zaman Dog da bir Object'tir.

Bu ilişki kodda şu anlama gelir: bir Animal referansı beklenen her yerde Dog nesnesi kullanabilirsin.

Animal myAnimal = new Dog(); // Dog IS-A Animal, bu geçerli
// Dog myDog = new Animal(); // HATA! Her Animal bir Dog değil

Bu kavram ileride polimorfizmi anlamamız için kritik olacak.

Object Sınıfı — Herkesin Atası

Java'da her sınıf doğrudan ya da dolaylı olarak java.lang.Object sınıfından türer. extends yazmasanız bile Java bunu otomatik yapar.

// Bu ikisi tamamen aynı anlama gelir:
class MyClass { }
class MyClass extends Object { }

Object sınıfı bazı temel metotlar sağlar ve her sınıf bunları miras alır:

MetotNe Yapar
toString()Nesnenin String temsilini döner
equals(Object o)İki nesneyi karşılaştırır
hashCode()Nesnenin hash değerini döner
getClass()Nesnenin sınıf bilgisini döner
clone()Nesnenin kopyasını oluşturur
finalize()GC temizlemeden önce çağrılır (deprecated)
class Student {
    String name;
    int id;

    Student(String name, int id) {
        this.name = name;
        this.id = id;
    }
}

Student s = new Student("Ayşe", 101);
System.out.println(s.toString());  // Student@1b6d3586 (varsayılan)
System.out.println(s.getClass()); // class Student

Varsayılan toString() pek kullanışlı değil — sınıf adı ve hash kodu veriyor. İleride bunu override etmeyi öğreneceğiz.

⚠️ Dikkat: finalize() metodu Java 9'dan itibaren deprecated. Kaynak temizliği için try-with-resources veya Cleaner API kullanılmalı.

Kalıtım Hiyerarşisi

Kalıtım zincirleme olabilir. Bir sınıf başka bir sınıftan, o da başka birinden türeyebilir.

class LivingThing {
    boolean isAlive = true;

    void breathe() {
        System.out.println("Breathing...");
    }
}

class Animal extends LivingThing {
    String species;

    void move() {
        System.out.println("Moving...");
    }
}

class Cat extends Animal {
    void purr() {
        System.out.println("Purrrr...");
    }
}

CatAnimalLivingThingObject. Cat nesnesi breathe(), move() ve purr() metotlarının hepsini kullanabilir.

Cat cat = new Cat();
cat.isAlive;    // true (LivingThing'den)
cat.breathe();  // Breathing... (LivingThing'den)
cat.move();     // Moving... (Animal'dan)
cat.purr();     // Purrrr... (kendi metodu)

Ama bu hiyerarşiyi çok derinleştirmek kötü bir pratik. 3-4 seviyeden fazla kalıtım zinciri genellikle tasarım sorununa işaret eder.

Constructor ve Kalıtım

Burada çok kritik bir konu var: constructor'lar miras alınmaz! Ama superclass'ın constructor'ı her zaman çağrılır.

Bir subclass nesnesi oluşturulduğunda, önce superclass'ın constructor'ı çalışır, sonra subclass'ınki. Bu mantıklı — önce temel parçalar inşa edilmeli.

class Animal {
    String name;

    Animal() {
        System.out.println("Animal constructor");
    }

    Animal(String name) {
        this.name = name;
        System.out.println("Animal constructor: " + name);
    }
}

class Dog extends Animal {
    String breed;

    Dog() {
        // Burada gizli bir super() çağrısı var!
        System.out.println("Dog constructor");
    }

    Dog(String name, String breed) {
        super(name); // Animal(String name) constructor'ını çağır
        this.breed = breed;
        System.out.println("Dog constructor: " + breed);
    }
}
Dog dog = new Dog("Boncuk", "Terrier");
// Çıktı:
// Animal constructor: Boncuk
// Dog constructor: Terrier

super() Çağrısı

super() ile superclass'ın constructor'ını açıkça çağırabilirsin. Kurallar:

  1. super() çağrısı constructor'ın ilk satırı olmalı.

  2. Eğer hiç super() yazmazsan, Java otomatik olarak parametresiz super() ekler.

  3. Eğer superclass'ta parametresiz constructor yoksa, super(...) ile uygun olanı açıkça çağırmalısın.

class Shape {
    String color;

    // Parametresiz constructor YOK!
    Shape(String color) {
        this.color = color;
    }
}

class Circle extends Shape {
    double radius;

    Circle(double radius) {
        super("Red"); // ZORUNLU! Shape'te parametresiz constructor yok
        this.radius = radius;
    }

    // Bu HATA verir:
    // Circle() { } // super() otomatik eklenir ama Shape() yok!
}

⚠️ Dikkat: Superclass'ta parametresiz constructor yoksa ve subclass'ta super(...) çağrısı yapmazsan, derleme hatası alırsın. Bu çok sık yapılan bir hata.

Constructor Çağrı Zinciri

Birden fazla seviyeli kalıtımda constructor'lar yukarıdan aşağıya çağrılır. Yani önce en üstteki ata sınıfın constructor'ı çalışır.

class A {
    A() { System.out.println("A constructor"); }
}

class B extends A {
    B() { System.out.println("B constructor"); }
}

class C extends B {
    C() { System.out.println("C constructor"); }
}

C obj = new C();
// Çıktı:
// A constructor
// B constructor
// C constructor

Bunu bir bina inşaatı gibi düşün: önce temel atılır (Object), sonra zemin kat (A), sonra 1. kat (B), en son çatı (C). Temelsiz bina olmaz.

this() ve super() Birlikte

Bir constructor'da ya this() ya da super() kullanabilirsin — ikisini birden kullanamazsın. İkisi de ilk satırda olmalı, ama ilk satır bir tane.

class Animal {
    String name;
    int age;

    Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Dog extends Animal {
    String breed;

    Dog(String name, int age, String breed) {
        super(name, age); // Animal constructor'ını çağır
        this.breed = breed;
    }

    Dog(String name) {
        this(name, 0, "Unknown"); // Üstteki Dog constructor'ını çağır
        // super(...) burada KULLANILAMAZ
    }
}

Kalıtımda Erişim Kontrol Tablosu

Hangi üyelere nereden erişileceğini bilmek önemli:

Erişim BelirteciAynı SınıfAynı PaketSubclass (Farklı Paket)Diğer
private
(default)
protected
public

Subclass'lar için protected çok önemli. Dışarıya kapalı ama çocuklara açık — "aile içi erişim" gibi düşünebilirsin.

final Keyword ve Kalıtım

final keyword'ü kalıtımı engellemek için kullanılır.

final sınıf — Bu sınıftan kalıtım yapılamaz:

final class MathUtils {
    static double PI = 3.14159;

    static double square(double x) {
        return x * x;
    }
}

// class SuperMath extends MathUtils { } // HATA! final class

final metot — Bu metot override edilemez (ama sınıftan kalıtım yapılabilir):

class Payment {
    final void validate() {
        // Bu metot değiştirilemez, güvenlik açısından kritik
        System.out.println("Validating payment...");
    }

    void process() {
        // Bu override edilebilir
        System.out.println("Processing...");
    }
}

class CreditCardPayment extends Payment {
    // void validate() { } // HATA! final metot
    
    @Override
    void process() {
        System.out.println("Processing credit card...");
    }
}

String, Integer, Math gibi Java standart kütüphanesindeki birçok sınıf final'dır. Bunların davranışının değiştirilmesi istenmiyor.

Kalıtım Ne Zaman Kullanılır?

Kalıtım güçlü bir araç ama her durumda doğru çözüm değil. Şu soruyu sor: "B gerçekten bir A mıdır?"

✅ Kalıtım iyi:

  • Cat extends Animal — Kedi gerçekten bir hayvandır

  • SavingsAccount extends BankAccount — Tasarruf hesabı gerçekten bir banka hesabıdır

  • Button extends UIComponent — Buton gerçekten bir UI bileşenidir

❌ Kalıtım kötü:

  • Stack extends ArrayList — Stack bir ArrayList değildir, sadece bazı özelliklerini kullanır

  • Employee extends Person extends User — Aşırı derin hiyerarşi

Eğer sadece bazı işlevleri yeniden kullanmak istiyorsan, kalıtım yerine composition (bileşim) düşün. Bu konuyu ilerideki derslerde detaylıca işleyeceğiz.

Kalıtımda Yaygın Hatalar

1. Parametresiz constructor unutmak:

class Parent {
    Parent(int x) { }  // Parametresiz constructor yok!
}

class Child extends Parent {
    Child() { }  // HATA! super() çağrılır ama Parent() yok
}

2. Private üyelere doğrudan erişim:

class Parent {
    private int secret = 42;
}

class Child extends Parent {
    void show() {
        // System.out.println(secret); // HATA! private
    }
}

3. Constructor'da this() ve super() karışıklığı:

Her ikisi de ilk satırda olmalı. Aynı constructor'da ikisini kullanamazsın. Çözüm: constructor chain'i dikkatlice tasarla.

Çok Katmanlı Kalıtım Örneği

Gerçekçi bir örnek yapalım:

class Employee {
    String name;
    double baseSalary;

    Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    double calculatePay() {
        return baseSalary;
    }

    void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Base Salary: " + baseSalary);
    }
}

class Manager extends Employee {
    double bonus;

    Manager(String name, double baseSalary, double bonus) {
        super(name, baseSalary);
        this.bonus = bonus;
    }

    @Override
    double calculatePay() {
        return baseSalary + bonus;
    }
}

class Director extends Manager {
    double stockOptions;

    Director(String name, double salary, double bonus, double stock) {
        super(name, salary, bonus);
        this.stockOptions = stock;
    }

    @Override
    double calculatePay() {
        return super.calculatePay() + stockOptions;
    }
}
Director dir = new Director("Ahmet", 10000, 5000, 3000);
System.out.println(dir.calculatePay()); // 18000.0
dir.displayInfo(); // Employee'den miras

Bu örnekte DirectorManagerEmployee zinciri var. Her seviye kendi katkısını ekliyor.

Java'da Çoklu Kalıtım Neden Yok?

Java sınıflarda çoklu kalıtımı desteklemez. Yani bir sınıf sadece tek bir sınıftan extend edebilir.

// Bu YAPILAMAZ:
// class FlyingFish extends Fish, Bird { }

Neden? Diamond Problem yüzünden. Fish ve Bird ikisi de Animal'dan türüyorsa ve aynı metodu farklı şekilde override ediyorsa, FlyingFish hangisini kullanacak? Belirsizlik oluşuyor.

Java bu sorunu interface'ler ile çözer. Bir sınıf birden fazla interface'i implement edebilir. Bunu B08 bölümünde göreceğiz.

💡 İpucu: C++ çoklu kalıtıma izin verir ama bu birçok karmaşıklık getirir. Java'nın tasarımcıları bilinçli olarak bunu dışarıda bıraktı. Interface'ler aynı esnekliği çok daha güvenli şekilde sağlar.

Kalıtım ve Bellek Modeli

Bir subclass nesnesi oluşturulduğunda bellekte neler olur?

class Animal {
    String name = "Animal";
    int legs = 4;
}

class Dog extends Animal {
    String breed = "Unknown";
}

Dog d = new Dog();

Bellekte Dog nesnesi şöyle görünür:

[Dog nesnesi]
├── name  = "Animal"    (Animal'dan)
├── legs  = 4           (Animal'dan)
└── breed = "Unknown"   (Dog'un kendi)

Tek bir nesne, ama hem superclass hem subclass alanlarını içeriyor. new Dog() dediğinde hem Animal kısmı hem Dog kısmı aynı nesne içinde oluşturulur.

Kalıtım Hiyerarşisi Tasarım İpuçları

  1. Kalıtım derinliğini 3-4 seviyede tut. Daha fazlası anlaşılmaz hale gelir.

  2. "IS-A" testini uygula. "Bir X gerçekten bir Y midir?" sorusunun cevabı evet değilse kalıtım kullanma.

  3. Superclass'ı genel tut. Çok spesifik superclass'lar esnekliği azaltır.

  4. Protected kullan, public değil. Subclass'lara açmak istediğin üyeleri protected yap.

  5. final ile kilitle. Değiştirilmemesi gereken metotlara final koy.


Özet

  • Kalıtım, bir sınıfın başka bir sınıfın özellik ve davranışlarını extends ile devralmasıdır.

  • Her sınıf dolaylı olarak Object sınıfından türer; toString(), equals(), hashCode() gibi metotları miras alır.

  • Constructor'lar miras alınmaz ama super() ile superclass constructor çağrılır; çağrı zinciri her zaman yukarıdan aşağıya işler.

  • Java'da bir sınıf yalnızca tek bir sınıftan türeyebilir (çoklu kalıtım yoktur).

  • final sınıftan kalıtım yapılamaz, final metot override edilemez.

  • Kalıtım IS-A ilişkisi kurar; sadece gerçek bir "türü" ifade ettiğinde kullanılmalıdır.