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); // trueIS-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ğilBu 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:
| Metot | Ne 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 StudentVarsayı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çintry-with-resourcesveyaCleanerAPI 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...");
}
}Cat → Animal → LivingThing → Object. 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: Terriersuper() Çağrısı
super() ile superclass'ın constructor'ını açıkça çağırabilirsin. Kurallar:
super()çağrısı constructor'ın ilk satırı olmalı.Eğer hiç
super()yazmazsan, Java otomatik olarak parametresizsuper()ekler.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 constructorBunu 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 Belirteci | Aynı Sınıf | Aynı Paket | Subclass (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 classfinal 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ırSavingsAccount extends BankAccount— Tasarruf hesabı gerçekten bir banka hesabıdırButton 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ırEmployee 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 mirasBu örnekte Director → Manager → Employee 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ı
Kalıtım derinliğini 3-4 seviyede tut. Daha fazlası anlaşılmaz hale gelir.
"IS-A" testini uygula. "Bir X gerçekten bir Y midir?" sorusunun cevabı evet değilse kalıtım kullanma.
Superclass'ı genel tut. Çok spesifik superclass'lar esnekliği azaltır.
Protected kullan, public değil. Subclass'lara açmak istediğin üyeleri
protectedyap.final ile kilitle. Değiştirilmemesi gereken metotlara
finalkoy.
Özet
Kalıtım, bir sınıfın başka bir sınıfın özellik ve davranışlarını
extendsile 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).
finalsınıftan kalıtım yapılamaz,finalmetot override edilemez.Kalıtım IS-A ilişkisi kurar; sadece gerçek bir "türü" ifade ettiğinde kullanılmalıdır.
AI Asistan
Sorularını yanıtlamaya hazır