Java - Polymorphism

Java - Polymorphism

Arkadaşlar merhaba,

Bu yazımızda Polymorphism konusundan bahsedeceğim.

Polymorphism Nedir ?

Java, bir nesnenin birçok farklı form alma özelliği olan polimorfizmi destekler. Bunu daha kesin olarak ifade etmek gerekirse, bir Java nesnesine, nesneyle aynı türde bir referans, nesnenin bir üst sınıfı olan bir referans veya nesnenin uyguladığı bir arabirimi tanımlayan bir referans kullanılarak doğrudan veya bir üst sınıf aracılığıyla erişilebilir.

Çok şekilli olan referanslardır.

Polymorphism bir referansın zamanın farklı anlarında kendi ya da alt tipinden olan nesneleri gösterebilmesine denir.

Daha güzel bir tanımla, iki referansın birbiriyle haberleşip,birbirlerinin gerçek tiplerini bilmemeleri demektir.

public interface HasTail {

    public abstract boolean isTailStriped();
}

public class Primate {

      public boolean hasHair() {
        return true;
    }
}

 public class Lemur extends Primate implements HasTail {

    public boolean isTailStriped() {
        return false;
    }

    public int age = 10;

    public static void main(String[] args) {
        Lemur lemur = new Lemur();
        System.out.println(lemur.age);
        HasTail hasTail = lemur;
        System.out.println(hasTail.isTailStriped());
        Primate primate = lemur;
        System.out.println(primate.hasHair());

    }
}

Bu örnekle ilgili dikkat edilmesi gereken en önemli şey, yalnızca bir nesnenin, Lemur'un oluşturulması ve başvurulmasıdır. Polimorfizm, bir Lemur instance'sinin Primate veya HasTail gibi süper tiplerinden biri kullanılarak bir methoda yeniden atanmasını veya geçirilmesini sağlar.

Nesne yeni bir referans tipine atandıktan sonra, sadece o referans tipi için mevcut olan metotlar ve değişkenler, açık bir cast olmaksızın nesne üzerinde çağrılabilir. Örneğin, aşağıdaki kod parçacıkları derlenmeyecektir:

HasTail hasTail = lemur;
System.out.println(hasTail.age); // DOES NOT COMPILE

Primate primate = lemur; 
System.out.println(primate.isTailStriped()); // DOES NOT COMPILE

Bu örnekte, hasTail referansının yalnızca HasTail interface'in de tanımlanan methodlara doğrudan erişimi vardır; bu nedenle, yaş değişkeninin nesnenin bir parçası olduğunu bilmez. Benzer şekilde, referans primate yalnızca Primate sınıfında tanımlanan methodlara erişimi vardır ve isTailStriped() methoduna doğrudan erişimi yoktur.

Object vs Referans

Java'da tüm nesnelere referansla erişilir, bu nedenle bir geliştirici olarak nesnenin kendisine asla doğrudan erişiminiz olmaz. Ancak kavramsal olarak, nesneyi, java çalışma zamanı ortamı tarafından tahsis edilen bellekte bulunan varlık olarak düşünmelisiniz. Bellekteki nesne için sahip olduğunuz referansın türü ne olursa olsun, nesnenin kendisi değişmez.

Örneğin, tüm nesneler Java.lang.Object öğesini devraldığından, aşağıdaki örnekte gösterildiği gibi tümü Java.lang.Object öğesine yeniden atanabilir:

Lemur lemur = new Lemur();
Object lemurAsObject = lemur;

Lemur nesnesi farklı bir türe sahip bir referansa atanmış olsa da, nesnenin kendisi değişmedi ve bellekte bir Lemur nesnesi olarak var olmaya devam ediyor.O halde değişen, lemurAsObject referansıyla Lemur sınıfı içindeki methodlara erişme yeteneğimizdir.

Nesnenin türü, bellekte nesnenin içinde hangi özelliklerin bulunduğunu belirler.

Nesneye yapılan referans türü, java programı için hangi method ve değişkenlere erişilebileceğini belirler.

Nesneyi Cast Etme

public class Example {

    Primate primate = new Lemur(); // implicit casting => kapalı casting;

}

Bu örnekte, önce bir Lemur nesnesi yaratıyoruz ve onu dolaylı olarak bir Primate referansına aktarıyoruz. Lemur, Primate'in bir alt sınıfı olduğundan,bu, bir cast operatörü olmadan yapılabilir. Nesnelerin geçerli referansı hedef türün bir alt türüyse, bir cast operatörüne ihtiyacınız yoktur.

Bu, implicit casting veya tür dönüşümü olarak adlandırılır.

Alternatif olarak, geçerli referans hedef türün bir alt türü değilse, uyumlu bir tip ile cast işlemi gerçekleştirmeniz gerekir. Temel alınan nesne türle uyumlu değilse, çalışma zamanında bir ClassCastException oluşturulur.

Lemur lemur2 = primate; // DOES NOT COMPILE

Lemur lemur3 = (Lemur) primate; // Explicit Cast => açık casting

Bir alt türden bir süper türe gönderme, açık bir cast (Lemur) primate gibi bir işlemi gerektirmez.

Bir supertype'dan bir subtype'a referans, bir casting gerektirir.

Derleyici, alakasız bir sınıfa yapılan cast'lere izin vermez.

Çalışma zamanında, ilgili bir türe ilişkin geçersiz bir casting, ClassCastException hatası oluşturur.

public class Bird {}

public class Fish {

    Fish fish = new Fish();
    Bird bird = (Bird) fish; // DOES NOT COMPILE

}

Bu örnekte, Fish ve Bird sınıfları, derleyicinin bildiği herhangi bir sınıf hiyerarşisi aracılığıyla ilişkili değildir; bu nedenle, kod derlenmeyecektir.

Her ikisi de Object'i gizli olarak genişletirken, biri diğerinin alt türü olamayacağı için ilgisiz türler olarak kabul edilirler.

Derleyici, bir nesnenin ilişkisiz türlere dönüştürülmesine izin vermediği gibi, instanceof'un ilişkisiz türlerle kullanılmasına da izin vermez. Bunu alakasız Kuş ve Balık sınıflarımızla gösterebiliriz:

if (fish instanceof Bird) { // DOES NOT COMPILE
            Bird bird = (Bird) fish;
}

İki sınıf ilgili bir hiyerarşiyi paylaşsa da, bu, birinin instance'sinin otomatik olarak diğerine aktarılabileceği anlamına gelmez.

public class Rodent {

    public String name = "Rodent";

    public void instanceOfexample() {
        name = "Rodent";
        System.out.println(name);
    }

    public static void main(String[] args) {

        Rodent rodent = new Capybara();
        rodent.method(rodent);

        System.out.println(rodent.name);
    }

    static void method(Rodent rodent) {
        if (rodent instanceof Capybara) {
            Capybara capybara1 = (Capybara) rodent;
             System.out.println(capybara1.name);
            System.out.println("ok downcasting performed");
        }
    }

}

public class Capybara extends Rodent {

    public String name = "Capybara";


    public static void main(String[] args) {

        Capybara capybara = (Capybara) rodent; // ClassCastException

    }

}

Bu kod, bir Rodent intance'ı oluşturur ve ardından onu Rodent, Capibara'nın bir alt sınıfına aktarmaya çalışır.

Bu kod derlenecek olsa da, referans alınan nesne Capybara sınıfının bir instance'si olmadığı için çalışma zamanında bir ClassCastException oluşturur.

Bu örnekte akılda tutulması gereken şey, oluşturulan Rodent nesnesinin Capibara sınıfını hiçbir şekilde miras almamasıdır.

InstanceOf Operatörü

Bir nesnenin belirli bir sınıfa veya interface'ye ait olup olmadığını kontrol etmek ve çalışma zamanında ClassCastExceptions hatasını önlemek için kullanılabilecek instanceof operatörü kullanılır.

Önceki örnekten farklı olarak, aşağıdaki kod parçacığı, çalışma zamanında bir istisna oluşturmaz ve yalnızca instanceof operatörü true değerini döndürürse cast gerçekleştirir.

public class Capybara extends Rodent {

    public String name = "Capybara";


    public static void main(String[] args) {
            Rodent rodent = new Capybara();
            Capybara.method(rodent);
    }

 static void method(Rodent rodent){
        if (rodent instanceof Capybara) {
            Capybara capybara1 = (Capybara) rodent;

            System.out.println("ok downcasting performed");

          capybara1.instanceOfexample();
        }
    }

// ok downcasting performed
// Umut

}

Polymorphism ve Method Overriding

Bir methodu geçersiz kılmak(override) için bir dizi kurala uymalısınız. Bir method override edildiğinde derleyici aşağıdaki kontrolleri gerçekleştirir:

- Alt sınıftaki method, üst sınıftaki method ile aynı imzaya sahip olmalıdır.

- Alt sınıftaki method, en az üst sınıftaki method kadar erişilebilir olmalıdır.

- Alt sınıf methodu, üst sınıf methodunda bildirilen herhangi bir exception sınıfından yeni veya daha geniş bir checked exception ilan etmeyebilir.

- Method bir değer döndürürse, aynı veya üst sınıftaki method kovaryant dönüş türleri olarak bilinen bir alt türü olmalıdır.
class Penguin {
    public int getHeight() {
        return 3;
    }

    public void printInfo() {
        System.out.print(this.getHeight());
        // System.out.print(super.getHeight()); // DOES NOT COMPILE
    }
}

public class EmperorPenguin extends Penguin {
    public int getHeight() {
        return 8;
    }

    public void printInfo() {
        System.out.print(super.getHeight()); // 3 super kullanmaz isek 8 yazdırır.
    }

    public static void main(String[] fish) {
        new EmperorPenguin().printInfo();
    }

}

Bu örnekte bellekte çalıştırılan nesne EmperorPenguin'dir. getHeight() methodu alt sınıfta override edilir, yani ona yapılan tüm çağrılar çalışma zamanında değiştirilir.

Penguin sınıfında printInfo() tanımlanmış olmasına rağmen, nesne üzerinde getHeight() öğesinin çağrılması, çağrıldığı geçerli referans ürünü değil, bellekteki kesin nesneyle ilişkili methodu çağırır.

Override edilen metodların kendi özel uygulamalarına sahip alt sınıflarla karmaşık miras modelleri oluşturmanıza olanak tanır.

Ayrıca, private veya override edilen methodu kullanmak için üst sınıfın güncellenmesi gerekmediği anlamına gelir. Method düzgün bir şekilde override kılındıysa, override edilen method, çağrıldığı tüm yerlerde kullanılacaktır.

Bir alt sınıf tarafından override edilmesini istemediğimiz methodları final olarak işaretleyerek polimorfik davranışı sınırlamayı seçebiliriz.

class Penguin1 {

    public static int getHeight() {
        return 3;
    }

    public void printInfo() {
        System.out.print(this.getHeight());

    }
}

public class CrestedPenguin extends Penguin1 {

    public static int getHeight() {
        return 8;
    }

    public static void main(String... fish) {
        new CrestedPenguin().printInfo();
    }
}

CrestedPenguin örneği önceki EmporerPenguin örneğimizle neredeyse aynıdır, ancak 8 yerine 3 yazdırır. getHeight() methodu statiktir ve bu nedenle gizlidir, override edilmez.

class Marsupial {

    protected int age = 2;

    public static boolean isBiped() {
        return false;
    }
}

public class Kangaroo extends Marsupial {

    protected int age = 6;

    public static boolean isBiped() {
        return true;
    }

    public static void main(String[] args) {
        Kangaroo joey = new Kangaroo();
        Marsupial moey = joey;
        System.out.println(joey.isBiped());
        System.out.println(moey.isBiped());
        System.out.println(joey.age);
        System.out.println(moey.age);

    }

}

Bu örnekte, Kangaroo türünden yalnızca bir nesne oluşturulur ve bellekte saklanır. **Statik methodlar override **edilemediğinden yalnızca gizlenebildiğinden, Java, isBiped()'in hangi sürümünün çağrılacağını belirlemek için referans türünü kullanır, bu da joey.isBiped()'in true ve moey.isBiped()'in false yazdırılmasıyla sonuçlanır.

Benzer şekilde, yaş değişkeni override edilmez gizlenir, bu nedenle hangi değerin döndürüleceğini belirlemek için referans türü kullanılır. Bu,joey.age'in 6 dönmesine ve moey.age'nin 2 dönmesine neden olur.

Umarım faydalı olmuştur.