Polimorfizm
Polimorfizm Nedir?
Polimorfizm, Yunanca "çok biçimlilik" demek. Java'da bir nesnenin birden fazla tipte davranabilmesidir. Aynı metot çağrısının, nesnenin gerçek tipine göre farklı davranışlar sergilemesi — işte polimorfizmin özü bu.
Bir uzaktan kumanda düşün. "Aç" düğmesine basıyorsun — TV'ye yöneltirsen TV açılır, klimaya yöneltirsen klima açılır. Aynı düğme, farklı cihazlar, farklı davranışlar. Polimorfizm de tam bu: aynı metot çağrısı, farklı nesnelerde farklı sonuçlar.
Polimorfizmin Temeli: Upcasting
Polimorfizmi anlamak için önce upcasting kavramını bilmelisin. Upcasting, bir subclass nesnesini superclass referansına atamaktır.
class Animal {
void makeSound() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Hav hav!");
}
void fetch() {
System.out.println("Fetching the ball!");
}
}Animal myAnimal = new Dog(); // Upcasting — Dog'u Animal referansına atadık
myAnimal.makeSound(); // "Hav hav!" — Dog'un versiyonu çalışır
// myAnimal.fetch(); // HATA! Animal referansı fetch() bilmezBurada çok önemli iki şey oluyor:
myAnimalreferansıAnimaltipinde ama gerçek nesneDog.makeSound()çağrılınca Dog'un override ettiği versiyon çalışır — runtime'da gerçek nesneye bakılır.fetch()çağrılamaz çünküAnimaltipinde böyle bir metot yok — compile-time'da referans tipine bakılır.
Bu "referans tipi vs nesne tipi" ayrımı polimorfizmin kalbidir.
Polymorphic Method Calls
Polimorfizmin gerçek gücü, aynı superclass referansıyla farklı subclass nesnelerini kullanabilmende ortaya çıkar.
class Shape {
String name;
Shape(String name) {
this.name = name;
}
double area() {
return 0;
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
super("Circle");
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width, height;
Rectangle(double width, double height) {
super("Rectangle");
this.width = width;
this.height = height;
}
@Override
double area() {
return width * height;
}
}Shape[] shapes = {
new Circle(5),
new Rectangle(4, 6),
new Circle(3)
};
for (Shape s : shapes) {
System.out.printf("%s area: %.2f%n", s.name, s.area());
}
// Circle area: 78.54
// Rectangle area: 24.00
// Circle area: 28.27Aynı s.area() çağrısı, nesnenin gerçek tipine göre farklı hesaplama yapıyor. Bu kodda bir Triangle sınıfı da eklesen, for döngüsüne tek satır bile dokunmazsın — açık/kapalı prensibi (Open/Closed Principle).
Polimorfizm ve Metot Parametreleri
Polimorfizm en çok metot parametrelerinde işe yarar. Superclass tipinde parametre alırsan, tüm subclass'ları kabul eder.
class Printer {
void printArea(Shape shape) {
System.out.println(shape.name + " area: " + shape.area());
}
}Printer printer = new Printer();
printer.printArea(new Circle(5)); // Circle area: 78.53...
printer.printArea(new Rectangle(4, 6)); // Rectangle area: 24.0printArea() metodu Shape alıyor ama Circle, Rectangle veya gelecekte eklenecek herhangi bir Shape alt sınıfı ile çalışır. Bu genişletilebilirlik polimorfizmin en değerli yanı.
Upcasting Detaylı
Upcasting otomatik (implicit) gerçekleşir. Cast operatörüne gerek yoktur.
Dog dog = new Dog();
Animal animal = dog; // Otomatik upcasting
Object obj = dog; // Bu da upcasting — Dog IS-A Object
Animal animal2 = new Dog(); // Doğrudan upcasting
Animal animal3 = (Animal) dog; // Açık cast — gereksiz ama hata değilUpcasting güvenlidir çünkü her Dog bir Animal'dır. Ama upcasting yaptığında subclass'a özgü metotlara erişimi kaybedersin. Referans tipi Animal olduğu için compiler sadece Animal'da tanımlı metotları görür.
Downcasting
Downcasting, upcasting'in tersi — superclass referansını subclass tipine çevirmek. Bu tehlikeli olabilir çünkü her Animal bir Dog değildir.
Animal animal = new Dog(); // Upcasting
// Downcasting — gerçek nesne Dog olduğu için OK
Dog dog = (Dog) animal;
dog.fetch(); // Artık fetch() erişilebilir
// Tehlikeli downcasting
Animal animal2 = new Cat();
// Dog dog2 = (Dog) animal2; // ClassCastException! Cat, Dog değil!Downcasting açık cast gerektirir — (Dog) animal şeklinde yazmalısın. Compiler buna izin verir ama runtime'da nesne uyumsuzsa ClassCastException fırlatılır.
⚠️ Dikkat: Downcasting yapmadan önce her zaman
instanceofkontrolü yap. Aksi halde runtime'da patlarsın.
instanceof Operatörü
instanceof bir nesnenin belirli bir tipte olup olmadığını kontrol eder. Downcasting öncesi güvenlik ağın.
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}
if (animal instanceof Cat) {
// Buraya girmez çünkü animal aslında Dog
Cat cat = (Cat) animal;
}instanceof kalıtım zincirine de bakar:
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
System.out.println(dog instanceof Cat); // DERLEME HATASI — alakasız tiplerJava 16+ Pattern Matching for instanceof
Java 16'dan itibaren instanceof ile doğrudan cast yapabilirsin. Daha temiz kod:
// Eski yol
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}
// Java 16+ yol
if (animal instanceof Dog dog) {
dog.fetch(); // Otomatik cast edildi!
}Bu özellik kodu hem kısaltır hem de güvenliği artırır. dog değişkeni sadece if bloğu içinde geçerli.
void processAnimal(Animal animal) {
if (animal instanceof Dog dog) {
dog.fetch();
} else if (animal instanceof Cat cat) {
cat.purr();
} else {
System.out.println("Unknown animal");
}
}Runtime Type vs Compile-Time Type
Bu ayrımı kesinlikle anlamalısın. Her referansın iki tipi var:
Compile-time type (derleme zamanı): Referans değişkeninin tanımlanan tipi. Compiler bu tipe bakarak hangi metotlara erişilebileceğine karar verir.
Runtime type (çalışma zamanı): Referansın işaret ettiği gerçek nesnenin tipi. JVM bu tipe bakarak hangi override'ın çalışacağına karar verir.
Animal a = new Dog(); // compile-time: Animal, runtime: Dog
a.makeSound(); // Compiler: "Animal'da makeSound var mı?" → Evet ✅
// JVM: "Gerçek nesne Dog, Dog'un versiyonunu çalıştır"
// a.fetch(); // Compiler: "Animal'da fetch var mı?" → Hayır ❌ DERLEME HATASI| Compile-Time Type | Runtime Type | |
|---|---|---|
| Belirleme | Referans değişkeninin tipi | new ile oluşturulan nesne |
| Ne zaman? | Derleme zamanında | Çalışma zamanında |
| Ne belirler? | Hangi metotlar çağrılabilir | Hangi override çalışır |
| Örnek | Animal a = ... → Animal | ... = new Dog() → Dog |
Polimorfik Koleksiyonlar
Polimorfizm koleksiyonlarla birleştiğinde çok güçlü olur.
List<Animal> zoo = new ArrayList<>();
zoo.add(new Dog());
zoo.add(new Cat());
zoo.add(new Bird());
for (Animal animal : zoo) {
animal.makeSound(); // Her hayvan kendi sesini çıkarır
}Aynı liste farklı tipleri tutuyor ama hepsini Animal olarak ele alıyor. Yeni bir hayvan eklediğinde listeyi veya döngüyü değiştirmene gerek yok.
Polimorfizm ve Method Dispatch
Peki JVM çalışma zamanında doğru metodu nasıl buluyor? Buna dynamic method dispatch denir.
Animal a = new Dog();
a.makeSound(); // Hangisi çalışacak?JVM şu adımları izler:
areferansının işaret ettiği gerçek nesneye bak →DogDogsınıfındamakeSound()var mı? → Evet → ÇalıştırYoksa → Superclass'a (
Animal) bakOrada da yoksa → Daha yukarı git (
Object'e kadar)
Bu "aşağıdan yukarı arama" mekanizması her polymorphic çağrıda tekrarlanır. JVM bunu optimize eder ama konsept bu.
Gerçekçi Örnek: Bildirim Sistemi
class Notification {
String message;
Notification(String message) {
this.message = message;
}
void send() {
System.out.println("Sending: " + message);
}
}
class EmailNotification extends Notification {
String email;
EmailNotification(String message, String email) {
super(message);
this.email = email;
}
@Override
void send() {
System.out.println("Email to " + email + ": " + message);
}
}
class SmsNotification extends Notification {
String phone;
SmsNotification(String message, String phone) {
super(message);
this.phone = phone;
}
@Override
void send() {
System.out.println("SMS to " + phone + ": " + message);
}
}
class PushNotification extends Notification {
String deviceId;
PushNotification(String message, String deviceId) {
super(message);
this.deviceId = deviceId;
}
@Override
void send() {
System.out.println("Push to " + deviceId + ": " + message);
}
}class NotificationService {
void sendAll(List<Notification> notifications) {
for (Notification n : notifications) {
n.send(); // Polimorfik çağrı
}
}
}
List<Notification> queue = List.of(
new EmailNotification("Hoşgeldin!", "user@mail.com"),
new SmsNotification("Kodunuz: 1234", "+905551234567"),
new PushNotification("Yeni mesaj!", "device-abc")
);
new NotificationService().sendAll(queue);
// Email to user@mail.com: Hoşgeldin!
// SMS to +905551234567: Kodunuz: 1234
// Push to device-abc: Yeni mesaj!sendAll() metodu Notification tipinde parametre alıyor. Hangi tür bildirim olduğunu bilmiyor ve bilmesine gerek yok. Her nesne kendi send() implementasyonunu çağırıyor. Yarın bir WhatsAppNotification eklesen, sendAll() metoduna dokunmazsın.
Polimorfizm ve Alanlar (Fields)
Çok önemli bir nokta: polimorfizm sadece metotlarda çalışır, alanlarda çalışmaz!
class Parent {
String type = "Parent";
}
class Child extends Parent {
String type = "Child"; // Bu override değil, shadowing!
}Parent p = new Child();
System.out.println(p.type); // "Parent" — referans tipine bakılır!
Child c = new Child();
System.out.println(c.type); // "Child"Alanlar compile-time type'a göre çözülür. Bu yüzden alanlara doğrudan erişmek yerine getter metotları kullan — getter'lar override edilebilir.
💡 İpucu: Alanlara dışarıdan erişimi
privateyapıp getter ile sun. Böylece polimorfizm düzgün çalışır ve encapsulation da korunur.
instanceof Kötü Kullanımı
instanceof bazen polimorfizmi bozar. Eğer sürekli instanceof kontrolü yapıyorsan, muhtemelen polimorfizmi düzgün kullanmıyorsun.
// KÖTÜ — polimorfizm kullanmıyor
void handleAnimal(Animal a) {
if (a instanceof Dog) {
System.out.println("Woof");
} else if (a instanceof Cat) {
System.out.println("Meow");
} else if (a instanceof Bird) {
System.out.println("Tweet");
}
}
// İYİ — polimorfizm kullanıyor
void handleAnimal(Animal a) {
a.makeSound(); // Her sınıf kendi sesini bilir
}Birinci versiyona her yeni hayvan eklediğinde if-else eklemen gerekir. İkinci versiyon ise yeni sınıf ekledin mi otomatik çalışır.
⚠️ Dikkat: Çok sayıda
instanceofkontrolü genellikle bir tasarım kokusudur (code smell). Polimorfizmi doğru kullan,instanceof'a nadiren ihtiyaç duyarsın.
Polimorfizm Neden Önemli?
Polimorfizmin sağladığı avantajları özetleyelim:
Genişletilebilirlik: Yeni alt sınıf eklemek mevcut kodu bozmaz.
Soyutlama: Kullanıcı kod detayları bilmek zorunda değil.
Esneklik: Aynı referansla farklı davranışlar elde edebilirsin.
Bakım kolaylığı: Değişiklik gerektiren yer azalır.
Bu kavram OOP'un belkemiğidir. Interface'ler, design pattern'lar, framework'ler — hepsi polimorfizm üzerine inşa edilir.
Özet
Polimorfizm, aynı metot çağrısının nesnenin gerçek tipine göre farklı davranışlar sergilemesidir — "çok biçimlilik".
Upcasting (subclass → superclass) otomatik ve güvenlidir; downcasting (superclass → subclass) açık cast gerektirir ve
instanceofile kontrol edilmelidir.Compile-time type hangi metotların çağrılabileceğini, runtime type ise hangi override'ın çalışacağını belirler.
Polimorfizm sadece metotlarda çalışır; alanlar (fields) her zaman referans tipine göre çözülür.
instanceof+ pattern matching (Java 16+) downcasting'i güvenli ve temiz hale getirir.Çok sayıda
instanceofkontrolü genellikle polimorfizmin düzgün kullanılmadığının işaretidir — metot override'ını tercih et.
AI Asistan
Sorularını yanıtlamaya hazır