Welcome to My Blog 👋

Java, Spring Framework, Microservices, Docker, Kubernetes, AWS and Others 🚀
Follow Me

Polymorphism çok şekillilik demektir. Bu konu kalıtım konusu ile iç içe geçmiş bir konudur. Kalıtım yolu ile oluşturduğumuz sınıfların birbiri yerine kullanılabilmesini sağlar. Örneğin Canli adında bir sınıfımızın olduğunu düşünelim. Daha sonra Bitki, Hayvan ve Insan adındaki sınıflarımızı Canli sınıfından extends ettiğimizi kabul edelim. Polymorphism sayesinde Canli sınıfından bir nesne referansı ile bitki, hayvan ve insan sınıfından oluşturduğumuz nesneleri gösterebiliriz. Bu ne işimize yarayacak derseniz, örneğin bizim bitki, hayvan ve insan için aynı işi yapmasını istediğimiz bir metot olsun. Normal şartlarda bu iş için 3 ayrı metot yazıp kod tekrarı yapmamız gerekecekti ancak polymorphism sayesinde tek bir metot yazıp bu 3 ayrı nesne için sadece canli sınıfından bir nesne alabiliriz. Daha sonra bu metoda insan, hayvan veya bitki sınıfından bir nesne gönderildiğinde hangi nesne gönderilmiş olursa olsun o nesne gibi davranış sergileyecektir.
Üst sınıf nesnesinin bir alt sınıf nesnesini göstermesine upcasting nedir. Bunun tam tersi de mümkündür. Yani insan sınıfından bir nesne canli sınıfından bir nesneyi gösterebilir. Bu işleme de downcasting denir. Ancak bu işlemde dikkat edilmesi gereken yer bu işlemi gerçekleştirirken alt sınıfın tipinin belirtilmesi gerektiğidir.

class Canli {

    public void sesCikar() {

        System.out.println("ses çıkarma");
    }

}

class Insan extends Canli {
    
    @Override
    public void sesCikar() {

        System.out.println("konuşma");
    }
}

class Hayvan extends Canli {

    @Override
    public void sesCikar() {

        System.out.println("bağırma");
    }
}

public class Example {

    public static void main(String[] args) {

        Insan insan1 = new Insan();
        Hayvan hayvan1 = new Hayvan();
        Canli canli2 = new Canli();

        // Upcasting
        Canli canli1 = insan1;
        canli1.sesCikar();
        // Downcasting
        Insan insan2 = (Insan) canli1;
        insan2.sesCikar();

        // Polymorphism
        polymorphism(canli1);
        polymorphism(insan2);
        polymorphism(hayvan1);
        polymorphism(canli2);

    }

    public static void polymorphism(Canli c) {
        
        c.sesCikar();
    }
}

Java diline generic tipler kod tekrarını azaltmak için eklenmiştir. Generic tipler ile yazdığımız class'ların, metotların veya interface'lerin veri tiplerini çalışma anında belirtebiliriz. Örneğin bir metot yazdınız ve bu metot bir değişken alıyor. Bu değişkenin her veri tipinde olabilmesini isterseniz generic tipleri kullanabilirsiniz. Bu işlem generic tiplerden önce object sınıfı ile yapılabiliyordu ancak tip denetimi ve tip dönüşümü gibi ekstra işlemler gerektirdiği için generic tipler ile yapılması çok daha mantıklıdır.
Generic tiplerin, kod sadeliği sağlaması, kodu hızlı geliştirmemize olanak vermesi ve tip güvenliği gibi bir çok avantajı vardır.

Generic tipler class'lar ile kullanıldığında aşağıdaki şekilde kullanılabilir. Genel tanımlaması class ClassAdi<T,K,L,...> şeklinde olabilir. T,K,L gibi istediğimiz kadar farklı tip belirtebiliriz hepsi nesne oluştururken belirtiğimiz tipi temsil eder.

class GenericTipler<T> {
    
    T obj;
    
    public GenericTipler(T obj) {
        
        this.obj = obj;
    }

    public T getObj() {
        return obj;
    }

}

public class Example {

    public static void main(String[] args) {

        GenericTipler exampleString = new GenericTipler("Berkay");
        GenericTipler exampleInteger = new GenericTipler(5);
        
        System.out.println(exampleString.getObj().getClass().getName());
        System.out.println(exampleInteger.getObj().getClass().getName());

    }
}

Generic tipler metotlar ile kullanılabilir. Genel tanımlama ön_ekler <T,K> dönüş_tipi metot_adi(T obj, K obj2, K obj3, ...) gibi olur.


public class Example {

    public static void main(String[] args) {

        goster("berkay");
    }

    public static <T> void goster(T obj) {
        
        System.out.println(obj.getClass().getName());
    }
}

Generic tipler interface ve kalıtım ile de kullanılabilir. Bu kullanım class'lardaki kullanım ile aynıdır.

Java'da kullanılan bu iki yapı birbirine çok sık karıştırılmaktadır. Ancak bu iki yapı da çok basittir.

Override

Override bir metodun tekrardan yazılması anlamına gelir. Bildiğiniz gibi Java'da kalıtım vardır. Aynı şekilde interface yapısıda Java dilinde kullanılır. Yazdığımız bir sınıfa kalıtım yolu ile veya interface yolu ile birden çok metot dahil edebiliriz ve bu metotları kullanabiliriz. Ancak kalıtım ile aldığımız bir metodu değiştirmek istersek o zaman override etmemiz yani yeniden yazmamız gerekir. Interface yapısı ile aldığımız metotları da zorunlu olarak tekrardan yazmamız zaten gereklidir. İşte bu işleme override denir.

public class Ornek extends Ornek2 {

    // Overload

    public void selamVer() {
        System.out.println("Selam");
    }

    public void selamVer(String isim) {
        System.out.println("Selam " + isim);
    }

    public void selamVer(String isim, String soyisim) {
        System.out.println("Selam " + isim + " " + soyisim);
    }

    // Override

    @Override
    public void hareketEt() {

        System.out.println("hareketEt metodu override edildi");
    }

}

class Ornek2 {

    public void hareketEt() {

        System.out.println("hareket edildi");
    }

}

Overload

Overload bir metodun aşırı yüklenmesi anlamına gelir. Java'da overload ile aynı isimde birden fazla metot yazabiliriz. Bu metotları birbirinden ayıran fark aldıkları parametreler olur. Örneğin programımızda selamVer adında bir metot olsun. Bu metodu selamVer() şeklinde yazarak ekrana sadece selam yazdırabiliriz. Aynı şekilde başka bir metot olarak selamVer(String isim) şeklinde tanımlayabiliriz. Programımızda selamVer metodunu bir string değer ile çağırırsak ikinci metot çağırılır eğer parametresiz çağırırsak birinci metot çağırılır. Java burada metodun aldığı parametrelere bakarak hangi metodun çağırılacağına karar verir. Bu işleme overload denir. Burada dikkat edilmesi gereken konular ise parametrelerin sayısı, tipi ve sırası farklı ise kodumuz sorunsuz çalışacaktır. Ancak aynı parametreler ile aynı isimde iki metot yazarsa burada hata alırız. Bir diğer konu ise Java dönüş değerine göre metotları ayırt edemez. Yani void selamVer() metodu ve String selamVer() metodunu programımıza yazarsa Java bu ikisi arasındaki farkı anlayamaz ve hata verir. Çünkü selamVer() metodunu çağırdığımız yerde hangi dönüş değerini beklediğimizi belirtmek zorunda değilizdir.




Abstract sınıflar genellikle Java'da oluşturduğumuz sınıfların ortak özelliklerini bir arada toplamak için kullanılır. Bu yüzden abstract sınıflardan bir nesne yaratılamaz. Abstract sınıflarımızın içerisinde içi dolu veya boş metotlar ve değişkenler tanımlayabiliriz. İçi boş metottan kastettiğimiz şey sadece metodun var olduğunu ve extend edilen sınıfta kullanılması gerektiğini ama ne yapacağını belirtmediğimiz metotlardır. Abstract sınıflar bu yönleri ile interface'lere çok benzemektedir. Interface ile de aynı şekilde bir sınıfın alması gereken metotları belirtebiliyorduk. Interface ile belirttiğimiz metotlarında sadece ismini, dönüş değerini ve aldığı parametreleri belirtiyorduk. Abstract sınıflarda da gövdesiz metotlar ile aynı işi yapabiliyoruz. Abstract sınıflarda oluşturduğumuz gövdesiz metotların başına abstract anahtar kelimesini koymamız gerekiyor. Aynı şekilde bir sınıfın abstract olduğunu belirtmek için de sınıfın başına abstract anahtar kelimesini koyuyoruz. Bir diğer dikkat edilmesi gereken konu da eğer bir sınıfta abstract bir metot varsa o sınıf abstract bir sınıf olmak zorundadır.

package deneme;

public abstract class Canli {

    int yas;

    public abstract void yaslan();

    public void yemekYe() {
        System.out.println("yemek yendi");
    }

}

class Insan extends Canli {

    public void yaslan() {

        yas++;
    }

}

Abstract sınıfların interface'ler ile benzerliklerini anlattık. Peki hangi durumda hangi yapıyı kullanacağımıza nasıl karar vereceğiz diye merak ediyorsanız bunun cevabı çok basittir. Interface yapısında has a ilişkisi vardır. Yani türkçe olarak sahiptir anlamına gelir. Örnek olarak insan sınıfı kıyafet adında bir interface'i implements ederek bu sınıfın metotlarını kullanabilir. Örneğin bu metotlar kıyafetiGiy veya kiyafetiTemizle olabilir. Abstract yapıda ise is a ilişkisi vardır. Örneğin insan bir canlıdır ilişkisi is a ilişkisidir. Eğer bir sınıf diğer sınıfın alt bir türü ise örneğin araç ve araba veya bisiklet sınıfları gibi o zaman abstract sınıflar kullanılır. Burada dikkat edilmesi gereken bir diğer konu da Java'da çoklu kalıtım yoktur ancak birden fazla interface bir sınıfa implements edilebilir.

Java nesne yönelimli programlama dilidir. Java'da oluşturulan nesneler belleğin heap bölgesinde oluşturulur. Java'da bir sınıftan üretilen her nesne için heap bölgesinde ayrı bir alana veri yazılır. Yani Java'da aynı class'tan üretilen tüm objeler birbirinden farklı veriler ve metotlar içerir. Ancak tüm nesnelerde aynı üye kullanılmasını istersek o üyeyi static tanımlayabiliriz. Örneğin bir sınıfın kaç adet nesnesinin oluşturulduğunu tutmak istersek bu durumda static değişken kullanmalıyız.
Eğer bir sınıf içerisindeki bir üyeyi static tanımlamak istersek o sınıfıntan üretilen her nesne için o üye sadece bir tane olur. Örneğin Urün sınıfında stok değişkenini static tanımlarsak o stok değişkeni sadece 1 tane olur ve tüm nesneler o değişkeni kullanır. Peki hangi durumlarda sınıf üyelerimizi static tanımlamalıyız diye soracak olursanız eğer bir değişkenin değeri her üretilen nesne için farklı olmayacaksa tüm nesneler için aynı olacaksa o değişken static olmalıdır. Aynı şekilde bir metot tüm nesneler için aynı çıktıyı verecekse o değişken de static tanımlanmalıdır. Ayrıca static değişken ve metotları kullanmak için sınıftan bir nesne yaratmamıza gerek yoktur sadece sınıf adı ile erişim gerçekleştirebiliriz. Ancak dikkat edilmesi gereken bir nokta da eğer bir metot static tanımlanmış ise o metot içerisinde bir nesne yaratmadığımız herhangi bir class'ın static olmayan üyesine erişemeyiz. Ayrıca kendi class'ı içerisindeki static olmayan metot ve değişkenlere de erişemeyiz. Çünkü sınıf ve nesne kavramı ayrı şeylerdir. Static olmayan sınıf üyeleri aslında var olmayan şeylerdir ve var olmayan bir şeye var olan yani static olan bir üyeden erişimin yapılamaz.

public static int stok;
public static void stoktaVarMi(){}




Java'da bulunan final anahtar kelimesi bir çok yerde kullanılmaktadır ve kullanılan yere göre anlamı değişmektedir.

Eğer final anahtar kelimesi bir class ile kullanılırsa o class'tan bir alt class üretilemeyeceği anlamına gelir.
Eğer final anahtar kelimesi bir metot ile kullanılırsa o metodun override edilemeyeceği yani alt class'larda değiştirilemeyeceği anlamına gelir.
Eğer final anahtar kelimesi bir değişken ile kullanılırsa o değişkene oluşturulurken değer atanabileceği ve daha sonra bu değerin değiştirilemeyeceği anlamına gelir.
Eğer final anahtar kelimesi bir metot parametresi ile kullanılırsa o metot parametresinin metot içerisinde değiştirilemeyeceği anlamına gelir.

Örnek Kullanım;
  • public final class Canli{}
  • final void hareketEt(){}
  • final int yas = 5
  • void hareketEt(final int hiz){}



Java dilinde oluşturduğumuz sınıflara, metotlara veya değişkenlere nereden erişilebileceğini sınırlayabiliriz. Bu işlemi yapmak için özel erişim belirleyicileri kullanırız. Bu belirteçler sınıflarımızı kapsüller yani gizler. Bir sınıfın içerisinden o sınıfın tüm üyelerine erişebiliriz ancak bazı durumlarda bu üyelere dışarıdan erişilmesini, bu üyelerin değiştirilmesini veya kullanılmasını kısıtlamamız gerekebilir. Bu durumlarda erişim belirteçlerini kullanmalıyız.

  • private (Sadece o sınıfın elemanları erişebilir)
  • public (Her yerden erişilebilir)
  • protected (Paket içerisinden veya alt sınıflardan erişilebilir)
  • boş (Paket içerisinden erişilebilir)

Örnek Kullanım;

  • public class Canli 
  • private int yas 
  • protected void hareketEt(){} 
  • class Hayvan 

Object sınıfı Java'da bulunan özel bir sınıftır. Bu sınıfı özel yapan ise Java'da bulunan tüm class'ların bu sınıftan türetilmiş olmasıdır. Java'da kendimiz bir class yazıp object oluşturduğumuzda veya var olan bir sınıftan da object oluşturduğumuzda bazı ortak metotların olduğunu belki fark etmişsinizdir. Örneğin equals() veya toString() gibi metotlar Java'da bulunan tüm class'larda mevcuttur. İşte bunun gibi ortak metotlar Object sınıfından miras alınmaktadır. Object sınıfının bir diğer özelliği ise mesela bir metot yazdığımızı düşünelim. Bu metodun da her veri tipinde bir değişken alabileceğini düşünelim. Normal şartlarda her veri türü için ayrı bir metot yazmamız gerekirdi ancak Object tipinden bir değişken alıp daha sonra bu değişkenin hangi class'tan üretildiğini öğrenebiliriz ve o class'a dönüştürebiliriz.


Java'da anonymous class yapısı bir inner classtır. Yani başka bir class içinde tanımlanmış bir class'tır. Anonymous class'ların bir ismi yoktur bu yönden inner class'lardan ayrılır. İsimlerinin olmaması da bu classlardan bir object yaratılma ihtiyacı olmamasından dolayıdır. Peki bu class'lardan object yaratmayacaksak bu class'lar ne işe yaramaktadır derseniz biz bir sınıftan object yaratırken bu object'in bir veya birden fazla metodunu override etmek isteyebiliriz. Sadece yaratacağımız bu object'te bu metotların override edilmesini isteyebiliriz. Bu durumlarda anonymoun class'ları kullanılırız. Bu yapı genellikle gui sınıflarında kullanılır. Örnek;

Bir araba sınıfımızın olduğunu ve bu araba sınıfının hareketEt adında da bir metodunun olduğunu düşünelim. Bu araba sınıfından bir object oluştururken bu hareketEt metodunu değiştirme ihtiyacımız varsa ve bu değişiklik sadece bu object için olacaksa o zaman anonymous class'ları kullanabiliriz.

public class Araba {
     hareketEt(){
         System.out.println("hareket ediyor");
     }
}

public class Main {
     public static void main() {
        Araba opel = new Araba() {
              @Override
              hareketEt(){
                   System.out.println("yeni hareket");
              }
        }
     }
}


Classpath Java kodu derlenirken kaynak kodun bağlantılı olduğu sınıfların arandığı yoldur. Yani adından da anlaşılacağı gibi Java'da yazılan sınıfların yoludur. Eğer classpath yanlış verilirse derleme esnasında bağlantılı sınıflar bulunamayacağı için derleme başarısız olur.


Oracle Java dilini satın aldıktan sonra geliştiricilerin bilgilerini belgelemek için sertifikalar vermeye başlamıştır. Çeşitli belgeler ile Java dilindeki uzmanlığınızın alanını ve seviyesini belirtebilirsiniz. Ülkemizde daha çok yaygın olmamakla birlikte yurt dışı için bu sertifikalar önemlidir. Sertifikalar hakkında daha detaylı bilgi almak için benimde takip ettiğim bu bloglardaki ilgili yazıları okuyabilirsiniz.



Java'da bir nesnenin hangi sınıfa ait olduğunu çalışma anında tespit etmek için instanceof operatörünü kullanabiliriz. Bu operatör iki değer alır. Sol tarafına bir nesne ve sağ tarafına ise bir sınıf değeri alır. Eğer belirtilen nesne belirtilen sınıfa ait ise true değil ise false değerini döndürür. Örnek;

Canli canli = new Canli();
System.out.println(canli instanceof Canli);
System.out.println(canli instanceof Araba);

Ekran Çıktısı:

true
false


Java'da oluşturduğumuz nesneleri kopyalayarak yeni nesneler oluşturabiliriz. Kopyalayarak oluşturduğumuz yeni nesne tamamen yeni bir nesne de olabilir, kopyalanan nesne ile aynı nesne de olabilir. Bu yüzden Java'da iki tür nesne kopyalama yöntemi vardır.

  • Sığ Kopyalama (Shallow Copy)
  • Derin Kopyalama (Deep Copy)

Sığ kopyalamada oluşturulan iki nesnede aynı bellek bölgesini gösterir. Örneğin;

Canli canli1 = new Canli();
Canli canli2 = canli1;

Yukarıdaki ilk satırda canli1 isminde bir nesne oluşturuluyor ve bu nesne için new anahtar sözcüğü ile bellekte bir yer ayrılıyor. canli1 adındaki bu nesne bellekte onun için ayrılar yeri gösteriyor. Daha sonra canli2 adında oluşturduğumuz nesneye de canli1 adındaki nesnenin gösterdiği bellek adresini atıyoruz. Bu şekilde iki nesnede aynı bellek bölgesini gösteriyor. Bu tür nesne kopyalamasına sığ kopyalama(shallow copy) denir.

Derin kopyalamada ise yeni nesne için bellekte yeni bir alan oluşturulur ve kopyalanacak nesnenin tüm özelliklere teker teker yeni nesneye aktarılır.

Canli canli1 = new Canli();
canli1.isim = "kedi";
Canli canli2 = new Canli();
canli2.isim = canli1.isim;

Yukarıda görüldüğü gibi iki nesne içinde new anahtar sözcüğü ile bellekte iki ayrı yer ayrılıyor ve sadece nesnenin özellikleri kopyalanıyor. Bunun gibi bir kopyalama işlemine derin kopyalama(deep copy) denir.

Java dilinde veri tipleri üçe ayrılır;
  • İlkel (Primitive) Veri Tipleri
  • Referans Veri Tipleri
  • Null Veri Tipi
İlkel veri tipleri kendi içinde alt gruplara ayrılır. Bunlar;
  • Sayısal Veri Tipleri
  • Mantıksal Veri Tipleri
Referans veri tipleri kendi içinde alt gruplara ayrılır. Bunlar;
  • Class
  • Interface
  • Array
Nesne yönelimli programlamada tüm veri tipleri bir sınıftır. Ancak veri tipleri çok sık kullanıldığından dolayı her seferinde bir nesne oluşturulmak istenmemesinden dolayı Java ilkel veri tiplerini bir nesne oluşturmadan kullanmamıza olanak sağlar. Referans tipler ise direk bir nesnesini oluşturduğumuz tiplerdir. Bu nesnelerin içerisine değer atarız ve o sınıfın metotlarını kullanabiliriz.

İlkel veri tipleri sabit uzunluklu veri tipleridir. Yani içerisine girilebilecek veriler sınırlıdır. Örneğin byte veri tipi -128 ile +127 arasında sayılar alır. Bu sayılardan farklı bir sayı içerisine girilemez. Bu veri tiplerinin bellekte kapladıkları alan sabittir. Örneğin byte veri tipi içinde 0 da tutsa 127 de tutsa bellekte kapladığı yer hep 1 byte olacaktır.

Referans tipler ise değişken uzunluklu veri tipleridir. Örneğin String veri tipinin içerisine istediğimiz kadar karakter girebiliriz ve bu karakterlere göre bellekte kapladığı alan farklı olacaktır.

İlkel veri tiplerinin içerisinde değerler tutulur. Yani örnek olarak int veri tipi içerisine 8 sayısını girdiğimizde bu veri tipinin içerisinde 8 değeri olur. Daha sonra bu değişkeni bir metoda gönderdiğimizde değişkenin kendisi değil içerisindeki değer gönderilir. Referans tiplerde ise değerler tutulmaz. Değişkenlerin içerisinde değerin bellekte bulunduğu adres tutulur. Böylelikle bu veri tiplerinin metotlara parametre olarak geçtiğimizde değer değil değerin adresi metoda gönderilir. Bu şekilde değişken üzerinde işlem yapılır.

Bir istisna olarak String veri tipi bir referans tip olmasına rağmen bir nesne yaratılmadan kullanılabilir. String sınıfı çok sık kullanılan bir sınıf olduğu için Java'da böyle bir kolaylık uygulanmıştır.

İlkel veri tipleri üzerinde operatörler ile işlem yapabiliriz. Referans tipler üzerinde işlem yapabilmek için ilgili sınıfın metotlarını kullanmak zorundayız.

İstersek bir sınıf yazarak kendi referans veri tipimizi oluşturabiliriz.

İlkel Veri Tipleri
  • byte
  • short
  • int
  • long
  • float
  • double
  • boolean
  • char
Referans Veri Tipleri
  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • Boolean
  • Character
  • String
  • ...