Bölüm 4.5: Soyut Sınıflar ve Arayüzler

Java ile Nesne Yönelimli Programlama
SoyutlamaSoyut sınıflarSoyut metotlarArayüzler implements ve çoklu arayüzdefault, static, private metotlarLambda ve fonksiyonel arayüzsealed, non-sealed, permitsArayüz mü, soyut sınıf mı?Tasarım desenleri
Sercan KÜLCÜ · Giresun Üniversitesi · Elektrik-Elektronik Mühendisliği

Giriş

Öğrenme Çıktıları

Soyut sınıf

abstract sınıf ve metot tanımlamak; soyut sınıftan neden nesne oluşturulamadığını ve alt sınıfın neyi uygulamak zorunda olduğunu açıklamak.

Şablon metot

Soyut sınıfın somut bir metodunun, alt sınıfta uygulanan soyut metodu çağırdığını kodu izleyerek göstermek.

Arayüz

interface tanımlamak, implements ile bir ya da birden çok arayüzü uygulamak; default, static ve private metotları kullanmak.

Lambda

Fonksiyonel arayüzü tanımak; tek metotluk davranışı lambda ifadesiyle yazmak ve lambdanın ne zaman çalıştığını izlemek.

Tasarım desenleri

Singleton, Fabrika, Gözlemci, Strateji ve MVC'nin hangi sorunu çözdüğünü ve arayüze nasıl dayandığını açıklamak.

Tasarım kararı

Mühürlü (sealed) tipleri tanımak; bir problem için arayüz mü soyut sınıf mı (ya da ikisi birden) gerektiğine karar vermek; jenerik tipleri ve etiketleri (annotation) kullanmak.

Soyutlama

Soyutlama (Abstraction)

  • Soyutlama: karmaşık ayrıntıları gizleyip yalnızca temel özellikleri göstermek.
  • Araba kullanan kişi direksiyonu ve pedalları bilir; motorun içini bilmek zorunda değildir.
  • “Şekil” bir kavramdır: her şeklin alanı vardır, ama genel bir şeklin alanı hesaplanamaz. Formülü yalnızca Kare, Çember, Üçgen bilir.
  • Java'da soyutlamanın iki aracı vardır: soyut sınıflar (abstract class) ve arayüzler (interface).
  • İkisi de ne yapılacağını söyler; nasıl yapılacağını alt sınıflara bırakır.
Şekilalanı var, çizilebilirKarea · aÇemberπ · r²Üçgent · h / 2

Ortak kavram yukarıda, ayrıntı aşağıda.

Soyut Sınıflar

Soyut Sınıf (Abstract Class)

  • abstract anahtar sözcüğüyle tanımlanır: public abstract class Sekil.
  • Hem soyut (gövdesiz) hem de somut (concrete, gövdeli) metotlar içerebilir.
public abstract class Sekil {
    // Soyut olmayan (somut) metot
    void tasi(int x, int y) {
        System.out.println("(" + x + ", " + y + ") noktasına taşındı");
    }
    // Soyut metot (alt sınıflarca uygulanmalı)
    abstract void ciz();
}
Somut metot: tasi

Gövdesi hazırdır. Alt sınıflar onu olduğu gibi kalıtımla alır; isterse geçersiz kılar (override).

Soyut metot: ciz

Yalnızca imzası vardır. Her somut alt sınıf kendi ciz() gövdesini yazmak zorundadır.

Soyut sınıf yarım kalmış bir plan gibidir: bazı odalar bitmiş, bazılarının yalnızca yeri belli.

Soyut Sınıflar

Soyut Sınıfın Özellikleri

  • Soyut sınıftan nesne oluşturulamaz (örneği, instance'ı olamaz).
  • Başka sınıflar onu extends ile genişletir (kalıtım).
  • Somut bir alt sınıf, tüm soyut metotları uygulamak (implement) zorundadır.
  • Ortak durumu (alanlar) ve davranışları tek yerde toplar → kod yeniden kullanılır.
  • Alt sınıflar soyut metotları kendi ihtiyacına göre doldurur.
  • Yapıcı metotları (constructor) olabilir; alt sınıf onları super(...) ile çağırır.
«abstract»Sekil- isim : String+ Sekil(isim)+ getIsim() : String+ alan() : double+ ciz()Kare- kenar+ alan(): doubleCember- r+ alan(): doubleDikdortgen- genislik- yukseklik+ alan(): double

UML'de soyut sınıf ve soyut metot eğik yazılır.

Soyut Sınıflar

Soyut Sınıftan Nesne Oluşturulamaz

Sekil s1 = new Sekil("?");     // HATA
Sekil s2 = new Kare(3);        // Doğru: değişkenin türü soyut olabilir
Sekil[] dizi = new Sekil[3];   // Doğru: dizi yalnızca referans tutar

Derleyici mesajı (1. satır)

error: Sekil is abstract; cannot be instantiated
Neden yasak?

Sekil'in alan() metodunun gövdesi yok. new Sekil() olsaydı s1.alan() çağrısında çalıştırılacak kod bulunmazdı.

Referans tipi olarak serbest

Değişken, parametre, dizi öğesi ve dönüş tipi Sekil olabilir. new Sekil[3] nesne değil, 3 boş (null) referans oluşturur.

abstract final class yazılamaz: final genişletmeyi yasaklar, abstract genişletmeyi gerektirir → illegal combination of modifiers: abstract and final.

Soyut Sınıflar

Soyut Metotlar (Abstract Methods)

  • Gerçekleme (implementation) olmadan tanımlanır.
  • Süslü parantez içermez, noktalı virgülle biter:
public abstract void ciz();
  • Soyut metodu olan sınıf soyut olmak zorundadır.
  • Soyut sınıflar genellikle kalıtım hiyerarşisinin tepesinde yer alır.
public abstract class Sekil {
    // Soyut metot
    public abstract void ciz();
}

// Ayrı bir dosyada:
public class Cember extends Sekil {
    // ciz metodunu uygulamak zorunlu
    public void ciz() {
        System.out.println("Çember çiziliyor");
    }
}

new Cember().ciz(); çıktısı

Çember çiziliyor

Soyut Sınıflar

Soyut Metot Kuralları

YazılanSonuç
abstract void ciz(); (soyut sınıfta)✓ Doğru
abstract void ciz() { }abstract methods cannot have a body
class Sekil { abstract void ciz(); }Sekil is not abstract and does not override abstract method ciz() in Sekil
class Cember extends Sekil { } (ciz yok)Cember is not abstract and does not override abstract method ciz() in Sekil
private abstract, static abstract, final abstractillegal combination of modifiers: abstract and private (static, final için de aynı)
abstract class Cember extends Sekil { }✓ Doğru: soyut alt sınıf, gerçeklemeyi kendi alt sınıflarına bırakabilir
abstract class A { void f() { } }✓ Doğru: hiç soyut metodu olmayan soyut sınıf da yazılabilir (yine de new A() yapılamaz)

Neden private/static/final olmaz? Üçü de metodun alt sınıfta geçersiz kılınmasını engeller; soyut metot ise geçersiz kılınmak için vardır.

Soyut Sınıflar · Adım adım kod izleme

new Kare(3): Yapıcı Zinciri

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = nullKare'ye aitkenar = 0.0

new Kare(3): önce heap'te bir Kare nesnesi ayrılır. Alanlar varsayılan değer alır: null ve 0.0.

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = nullKare'ye aitkenar = 0.0

Kare'nin yapıcısı çalışır. Parametre kenar = 3.0 (int 3, double'a genişletildi).

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = nullKare'ye aitkenar = 0.0

İlk iş super("Kare"): üst sınıfın yapıcısı çağrılır. Soyut sınıfın da yapıcısı olabilir.

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = nullKare'ye aitkenar = 0.0

Sekil yapıcısı çalışıyor. Ayrı bir Sekil nesnesi oluşmaz: aynı nesnenin Sekil'den gelen kısmı kuruluyor.

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 0.0

this.isim = isim: nesnenin isim alanı "Kare" olur.

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 0.0

Mesaj yazdırılır. Sekil yapıcısı biter, çerçevesi yığından çıkar.

YığınHeapmains ?Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 3.0

Kare yapıcısına dönülür: this.kenar = 3.0. Nesne artık tamamen kurulmuştur.

YığınHeapmains •Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 3.0

Nesnenin referansı s'ye atanır. Değişkenin tipi Sekil (soyut), gösterdiği nesne Kare.

YığınHeapmains •Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 3.0

s.alan(): Sekil'de alan() soyut. Çalışma zamanında nesnenin tipine bakılır → Kare.alan(): 3.0 · 3.0.

YığınHeapmains •Kare nesnesiSekil'den gelenisim = "Kare"Kare'ye aitkenar = 3.0

Dönen 9.0 yazdırılır.

abstract class Sekil {
    private String isim;
    Sekil(String isim) {
        this.isim = isim;
        System.out.println(isim + " kuruldu");
    }
    abstract double alan();
}
class Kare extends Sekil {
    private double kenar;
    Kare(double kenar) {
        super("Kare");
        this.kenar = kenar;
    }
    double alan() { return kenar * kenar; }
}
// main içinde:
Sekil s = new Kare(3);
System.out.println(s.alan());

Soyut Sınıflar · Adım adım kod izleme

Somut Metot Soyut Metodu Çağırır

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

Değişken tipi Icecek (soyut), nesne Cay. Cay tek soyut metodu demle()'yi uyguladığı için somuttur.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

Cay'da hazirla() yok → Icecek'ten kalıtımla alınan somut metot çalışır. final: alt sınıflar bu sırayı değiştiremez.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

1. adım: suyuKaynat(), yani this.suyuKaynat() çağrılır.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

Icecek'teki somut suyuKaynat() çalışır.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

2. adım: demle(). Icecek'te gövdesi yok, ama this bir Cay nesnesi…

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

…bu yüzden Cay.demle() çalışır. Soyut sınıftaki kod, henüz yazılmamış alt sınıf kodunu çağırabilir.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

3. adım: servis mesajı yazdırılır.

«abstract»Icecek+ hazirla() {final}+ suyuKaynat()+ demle()Cay+ demle()

İskelet soyut sınıfta, değişen adım alt sınıfta: şablon metot (template method). Bir Kahve sınıfı yalnızca demle()'yi yazar.

abstract class Icecek {
    final void hazirla() {
        suyuKaynat();
        demle();
        System.out.println("Servis edildi");
    }
    void suyuKaynat() {
        System.out.println("Su kaynadı");
    }
    abstract void demle();
}
class Cay extends Icecek {
    void demle() {
        System.out.println("Çay demlendi");
    }
}
// main içinde:
Icecek i = new Cay();
i.hazirla();

Arayüzler

Arayüz (Interface) Nedir?

  • Bir sistemin belirli işlevlere sahip olacağını garanti eden sözleşmedir (contract).
  • İki sistem arasındaki entegrasyon noktasını temsil eder: kullanan taraf yalnızca arayüzü bilir.
  • interface anahtar sözcüğüyle tanımlanır.
  • Arayüzdeki metotlar, public yazılsın ya da yazılmasın public'tir.
  • static, default ya da private değilse metot otomatik olarak abstract'tır.
  • Alan olarak yalnızca public static final sabitler içerebilir.
Benzetme: priz ve fiş

Priz bir sözleşmedir: hangi cihaz takılırsa takılsın aynı gerilimi verir. Ütü de bilgisayar da prizin standardına uyar; şebeke cihazın içini, cihaz şebekenin içini bilmez.

Soyut sınıf “bir türüdür” (is-a) ilişkisi kurar; arayüz “yapabilir” (can-do) yeteneği tanımlar.
kullanan kodbilir«interface»GeometrikSekilvoid ciz()KareCemberimplements

Arayüzler

Arayüz Tanımlama

Yazdığımız

public interface GeometrikSekil {
    // Soyut metot (public abstract)
    void ciz();
    // Sabit (public static final)
    int KENAR_SAYISI = 4;
}

Derleyicinin gördüğü

public interface GeometrikSekil {
    public abstract void ciz();
    public static final int KENAR_SAYISI = 4;
}
  • Sabitlere arayüz adıyla erişilir: GeometrikSekil.KENAR_SAYISI → 4. Adları BÜYÜK_HARF ile yazılır.
  • Sabite değer vermek zorunludur: int deger; → error: = expected.
  • Sabit değiştirilemez: GeometrikSekil.KENAR_SAYISI = 5; → error: cannot assign a value to static final variable KENAR_SAYISI.
  • Arayüzden de nesne oluşturulamaz: new GeometrikSekil() → error: GeometrikSekil is abstract; cannot be instantiated.

Arayüzler

Arayüzü Uygulamak (implements)

  • Bir sınıf, bir arayüzü implements anahtar sözcüğüyle uygular.
  • Arayüzdeki tüm soyut metotları uygulamalı ya da kendisi abstract olmalıdır.
  • Eksik metot: Kare is not abstract and does not override abstract method ciz() in GeometrikSekil.
  • Uygulayan metot public olmalıdır. void ciz() yazılırsa: attempting to assign weaker access privileges; was public.
  • Arayüz tipinde değişken, onu uygulayan her sınıfın nesnesini gösterebilir.
public class Kare implements GeometrikSekil {
    // ciz metodunu uygulamak zorunlu
    public void ciz() {
        System.out.println("Kare çiziliyor");
    }
}
// main içinde:
GeometrikSekil g = new Kare();
g.ciz();
System.out.println(GeometrikSekil.KENAR_SAYISI);

Çıktı

Kare çiziliyor
4

Arayüzler

Bir Sınıf, Birden Fazla Arayüz

  • Bir sınıf en fazla bir üst sınıfı genişletebilir (extends)…
  • …ama sıfır ya da daha çok arayüzü uygulayabilir (implements A, B, …).
  • Önce extends, sonra implements yazılır.
  • class C extends A, B → error: '{' expected.
Hayvan~ isim : String~ Hayvan(isim)«interface»Ucabilir+ uc()«interface»Yuzebilir+ yuz()Ordek~ Ordek(isim)+ uc()+ yuz()
class Hayvan {
    String isim;
    Hayvan(String isim) { this.isim = isim; }
}
interface Ucabilir { void uc(); }
interface Yuzebilir { void yuz(); }
class Ordek extends Hayvan implements Ucabilir, Yuzebilir {
    Ordek(String isim) { super(isim); }
    public void uc() { System.out.println(isim + " uçuyor"); }
    public void yuz() { System.out.println(isim + " yüzüyor"); }
}

Arayüzler · Adım adım kod izleme

Aynı Nesne, Farklı Arayüz Referansları

Yığın: mainHeapOrdek o•Ucabilir u—Yuzebilir y—Ordek nesnesiisim = "Donald"uc()yuz()

Heap'te tek bir Ordek nesnesi oluşur; o onu gösterir.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y—Ordek nesnesiisim = "Donald"uc()yuz()

Atama nesneyi kopyalamaz: u aynı nesneyi gösterir. Ordek bir Ucabilir'dir → dönüşüm otomatik.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

y da aynı nesneyi gösterir. Bir nesne, üç farklı tipten “bakış”.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

u.uc(): u'nun tipi Ucabilir; ondan yalnızca uc() çağrılabilir. Çalışan, nesnenin kendi uc()'su.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

y.yuz(): Yuzebilir tipi yuz()'ü bilir.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

u.yuz() yazılsaydı: error: cannot find symbol. Nesnede yuz() var, ama derleyici referansın tipine bakar.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

o'nun tipi Ordek: alan isim dahil her şey erişilebilir.

Yığın: mainHeapOrdek o•Ucabilir u•Yuzebilir y•Ordek nesnesiisim = "Donald"uc()yuz()

== referansları karşılaştırır: hepsi aynı nesneyi gösterir → true.

Ordek o = new Ordek("Donald");
Ucabilir u = o;
Yuzebilir y = o;
u.uc();
y.yuz();
// u.yuz();   derleme hatası!
System.out.println(o.isim);
System.out.println(u == o);

Arayüzler

Arayüzden Kalıtım

Bir arayüz, başka bir arayüzü extends ile genişletebilir; virgülle birden çok arayüzü de genişletebilir: interface C extends A, B.

public interface KareSekli extends GeometrikSekil {
    // Ek metotlar veya sabitler eklenebilir
    void ozelFonksiyon();
}
class OzelKare implements KareSekli {
    public void ciz() {
        System.out.println("Özel kare çiziliyor");
    }
    public void ozelFonksiyon() {
        System.out.println("Köşegen: " + Math.sqrt(2));
    }
}
// main içinde:
KareSekli k = new OzelKare();
k.ciz();
k.ozelFonksiyon();
GeometrikSekil g = k;   // üst arayüze atanabilir
«interface»GeometrikSekil+ KENAR_SAYISI : int = 4+ ciz()«interface»KareSekli+ ozelFonksiyon()OzelKare+ ciz()+ ozelFonksiyon()extendsimplements

Çıktı

Özel kare çiziliyor
Köşegen: 1.4142135623730951

Arayüzler

Arayüz Tipiyle Programlama

  • Kullanan kod somut sınıfa değil, arayüze bağlanır: değişken, parametre ve dönüş tipi arayüz olur.
  • satinAl(OdemeYontemi y, double toplam) kartla mı havaleyle mi ödendiğini bilmez; y.ode(toplam) der, gerisini nesne yapar.
  • Yeni bir ödeme türü (Kripto) yalnızca arayüzü uygular; satinAl hiç değişmez (gevşek bağlılık).
  • Test kolaylaşır: gerçek banka yerine arayüzü uygulayan sahte bir sınıf verilir.
  • Java kütüphanesi de böyledir: List<String> l = new ArrayList<>();
satinAl(...)yalnızca bunu bilir«interface»OdemeYontemiode(double)KrediKartiHavaleKripto (yeni)

Arayüzler · Adım adım kod izleme

Ödeme Arayüzü: Kod İzleme

YığınHeapsatinAl()ytoplam250.0main()KrediKarti #1y: OdemeYontemi tipinde

Parametre y'nin tipi arayüzdür; gelen nesne bir KrediKarti.

YığınHeapKrediKarti.ode()tutar250.0satinAl()ytoplam250.0main()KrediKarti #1y.ode() → KrediKarti.ode()

y.ode(toplam) dinamik bağlanır: nesne kart olduğu için KrediKarti.ode() çalışır.

YığınHeapmain()KrediKarti #1

Çağrı biter; kart nesnesini artık gösteren referans yoktur, çöp toplayıcı onu silebilir.

YığınHeapsatinAl()ytoplam120.0main()Havale #2aynı satinAl, farklı nesne

Aynı satinAl, bu kez bir Havale nesnesiyle.

YığınHeapHavale.ode()tutar120.0satinAl()ytoplam120.0main()Havale #2y.ode() → Havale.ode(): 120 + 5

Aynı satır bu kez Havale.ode()'yi çalıştırır; 5 TL masraf ekler: 125.0.

YığınHeapmain()satinAl hiç değişmeden iki davranış

satinAl iki farklı sınıfla çalıştı ama hiçbirinin adını bilmiyor; yeni bir sınıf eklense de değişmez.

interface OdemeYontemi { void ode(double tutar); }
class KrediKarti implements OdemeYontemi {
    public void ode(double tutar) {
        System.out.println("Kart: " + tutar + " TL");
    }
}
class Havale implements OdemeYontemi {
    public void ode(double tutar) {
        System.out.println("Havale: " + (tutar + 5) + " TL");
    }
}
static void satinAl(OdemeYontemi y, double toplam) {
    y.ode(toplam);
}
satinAl(new KrediKarti(), 250.0);   // main
satinAl(new Havale(), 120.0);

Lambda İfadeleri

Anonim (Lambda) Fonksiyonlar

  • Anonim fonksiyonlar, isimsiz fonksiyonlardır: isim yerine doğrudan kod parçasını temsil ederler.
  • Java'da lambda ifadeleri (lambda expressions) olarak bilinir; Java 8+.
  • Söz dizimi: (ParametreListesi) -> İfade
  • Çok satırlı gövde: (a, b) -> { ...; return ...; }
  • Parametre tipleri çoğu zaman yazılmaz, derleyici çıkarır: (x, y) -> x + y. Tek parametrede parantez de gerekmez: s -> s * 2.
(int x, int y) -> x + y parametre listesi ayırıcı ifade (gövde) sonuç döner

Toplama lambda ifadesi: (int x, int y) -> x + y iki tam sayı alır ve toplar. Aynı işi yapan metot:

static int toplama(int x, int y) {
    return x + y;
}

Lambda İfadeleri

Fonksiyonel Arayüz (Functional Interface)

  • Fonksiyonel arayüz: tek bir soyut metodu olan arayüz. Lambda ancak böyle bir tipe atanabilir.
  • Lambda, o tek metodun gövdesini yazar; parametre ve dönüş tipleri metottan çıkarılır.
  • @FunctionalInterface isteğe bağlıdır; yazılırsa derleyici ikinci bir soyut metot eklenmesine izin vermez.
  • Lambda tanımlandığında çalışmaz; metot çağrıldığında (s.selamVer(...)) çalışır.
@FunctionalInterface
interface Selamlama {
    void selamVer(String ad);
}

Selamlama s = ad -> System.out.println("Merhaba " + ad);
s.selamVer("Ayşe");
Çıktı
Merhaba Ayşe
interface Selamlamavoid selamVer(String ad);ad -> System.out.println("Merhaba " + ad)tek soyut metotlambda = metodun gövdesi

Lambda İfadeleri

Hazır Fonksiyonel Arayüzler

Her seferinde arayüz yazmak gerekmez: java.util.function paketinde sık kullanılan biçimler hazırdır.

ArayüzGirdiÇıktıMetotÖrnek kullanım
Consumer<T>bir değeryokacceptyazdırma, kaydetme
Supplier<T>yokbir değergetdeğer üretme
Function<T, R>bir değerbir değerapplydönüştürme
Predicate<T>bir değerbooleantestsüzme, koşul
Consumer<String> yaz = m -> System.out.println(m);
yaz.accept("Consumer çalıştı");
Supplier<Integer> zar = () -> 4;
System.out.println(zar.get());
Function<String, Integer> uzunluk = m -> m.length();
System.out.println(uzunluk.apply("Giresun"));
Predicate<Integer> pozitifMi = x -> x > 0;
System.out.println(pozitifMi.test(-7));
Çıktı
Consumer çalıştı
4
7
false

Lambda İfadeleri · Adım adım kod izleme

Lambda Ne Zaman Çalışır? Adım Adım

ciftMi: x -> x % 2 == 0

Lambda bir Predicate<Integer> nesnesi olarak saklanır; gövdesi henüz çalışmadı. ciftMi bu davranışı gösteren bir referanstır.

ciftMi: x -> x % 2 == 0sayilar38512

Liste: 3, 8, 5, 12.

ciftMi: x -> x % 2 == 0sayilar38512sonucboş

Sonuç listesi boş başlar.

ciftMi: x -> x % 2 == 0sayilar38512sonucboş

Sıradaki öğe: s = 3.

ciftMi: x -> x % 2 == 0test(3) → falsesayilar38512sonucboş

ciftMi.test(3) lambdanın gövdesini çalıştırır: 3 % 2 == 0 → false.

ciftMi: x -> x % 2 == 0sayilar38512sonucboş

Sıradaki öğe: s = 8.

ciftMi: x -> x % 2 == 0test(8) → truesayilar38512sonucboş

ciftMi.test(8) lambdanın gövdesini çalıştırır: 8 % 2 == 0 → true.

ciftMi: x -> x % 2 == 0sayilar38512sonuc8

Koşul doğru: 8 sonuç listesine eklenir.

ciftMi: x -> x % 2 == 0sayilar38512sonuc8

Sıradaki öğe: s = 5.

ciftMi: x -> x % 2 == 0test(5) → falsesayilar38512sonuc8

ciftMi.test(5) lambdanın gövdesini çalıştırır: 5 % 2 == 0 → false.

ciftMi: x -> x % 2 == 0sayilar38512sonuc8

Sıradaki öğe: s = 12.

ciftMi: x -> x % 2 == 0test(12) → truesayilar38512sonuc8

ciftMi.test(12) lambdanın gövdesini çalıştırır: 12 % 2 == 0 → true.

ciftMi: x -> x % 2 == 0sayilar38512sonuc812

Koşul doğru: 12 sonuç listesine eklenir.

ciftMi: x -> x % 2 == 0sayilar38512sonuc812

Döngü bitti; [8, 12] yazdırılır. Lambda dört kez çalıştı: her test çağrısında bir kez.

Predicate<Integer> ciftMi = x -> x % 2 == 0;
List<Integer> sayilar = Arrays.asList(3, 8, 5, 12);
List<Integer> sonuc = new ArrayList<>();
for (int s : sayilar) {
    if (ciftMi.test(s)) {
        sonuc.add(s);
    }
}
System.out.println(sonuc);

Lambda İfadeleri

Lambda Örneği: Liste Filtreleme

import java.util.*;, java.util.function.IntBinaryOperator ve java.util.stream.Collectors içe aktarılmış main içinde:

IntBinaryOperator toplama = (int x, int y) -> x + y;
System.out.println(toplama.applyAsInt(5, 3));

List<Integer> sayilar = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
List<Integer> ciftSayilar = sayilar.stream().
                    filter(s -> s % 2 == 0).
                    collect(Collectors.toList());
System.out.println(ciftSayilar);
System.out.println(sayilar.stream().map(s -> s * s).toList());
Çıktı
8
[2, 4, 6]
[1, 4, 9, 16, 25, 36, 49]
  • Lambda bir fonksiyonel arayüz (functional interface) tipine atanır: IntBinaryOperator iki int alıp int döndürür; çağrı applyAsInt.
  • filter(s -> s % 2 == 0) çift sayıları tutar; map dönüştürür.
  • .toList() Java 16+; öncesinde collect(Collectors.toList()).

Lambda İfadeleri

Lambda ve Anonim Sınıf

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Anonim sınıf");
    }
};
Runnable r2 =
        () -> System.out.println("Lambda");
r1.run();
r2.run();
Çıktı
Anonim sınıf
Lambda

Lambdadan önce tek metotluk bir davranış anonim sınıfla yazılırdı. Lambda aynı işi tek satırda yapar.

Sınırlar

  • Lambda yalnızca bir fonksiyonel arayüz tipine atanabilir.
  • Kullandığı yerel değişkenler final ya da fiilen final (effectively final) olmalıdır: sonradan değer atanmamalıdır.
  • Lambda içindeki this, çevreleyen nesneyi gösterir; anonim sınıfta ise anonim nesnenin kendisini.
int kdv = 20;
Function<Integer, Integer> vergili =
        fiyat -> fiyat + fiyat * kdv / 100;
System.out.println(vergili.apply(150)); // 180
kdv = 18;  // eklenirse: derleme hatası
error: local variables referenced from
  a lambda expression must be final
  or effectively final

Lambda İfadeleri

Kullanım Alanları

  • Koleksiyon işlemleri: liste filtreleme, sıralama, eşleştirme (filter, sort, map, forEach).
  • İş parçacığı yönetimi: paralel yürütülen işlerin gövdesini kısaca vermek.
  • Daha okunabilir kod: tek yerde kullanılacak küçük işlemler için ayrı fonksiyon tanımlamak yerine.
List<String> isimler = new ArrayList<>(List.of("Zeynep", "Ali", "Mehmet"));
isimler.sort((a, b) -> a.length() - b.length());
System.out.println(isimler);
isimler.forEach(isim -> System.out.println("Merhaba " + isim));
Thread is = new Thread(() -> System.out.println("İş parçacığı çalıştı"));
is.start();
Çıktı
[Ali, Zeynep, Mehmet]
Merhaba Ali
Merhaba Zeynep
Merhaba Mehmet
İş parçacığı çalıştı

Sıralama ölçütü bir lambdadır: fark negatifse a önce gelir. Eşit uzunluktakiler (Zeynep, Mehmet) ilk sıralarını korur. () -> ... parametresiz bir lambdadır.

Arayüz Metotları

Varsayılan Metotlar (default, Java 8+)

  • Arayüz, gövdesi olan varsayılan (default) metotlara sahip olabilir.
  • Uygulayan sınıf onu kalıtımla alır; isterse geçersiz kılar. Amaç: arayüze yeni metot eklerken onu uygulayan eski sınıfları bozmamak.
interface Collection {
    void add(Object o);
    void remove(Object o);
    int size();
    default boolean isEmpty() {
        return size() == 0;
    }
}

Çıktı

true
2 false
class Sepet implements Collection {
    private int adet = 0;
    public void add(Object o) { adet++; }
    public void remove(Object o) { adet--; }
    public int size() { return adet; }
    // isEmpty() yazılmadı: default sürüm kullanılır
}
// main içinde:
Sepet sepet = new Sepet();
System.out.println(sepet.isEmpty());
sepet.add("elma");
sepet.add("armut");
System.out.println(sepet.size() + " " + sepet.isEmpty());

isEmpty(), soyut size()'ı çağırır; hangi size()'ın çalışacağı nesneye göre belirlenir. (Bu Collection bizim arayüzümüzdür; java.util.Collection ile karıştırılmamalı.)

Arayüz Metotları · Adım adım kod izleme

Varsayılan Metot: Kod İzleme

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet0

Sepet yalnızca iki soyut metodu yazdı; isEmpty()'i yazmadı.

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet0isEmpty() yok → default sürüm

Arama Sepet'te isEmpty() bulamaz; arayüzdeki default gövde çalışır.

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet0size() → nesneye göre: Sepet.size() = 0

Default metot soyut size()'ı çağırır; hangi gövdenin çalışacağını nesne belirler: Sepet.size() → 0.

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet00 == 0 → true

0 == 0: true.

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet1

add Sepet'in sürümüdür: adet 1 olur.

«interface»Collectionadd(Object)size()default isEmpty()Sepetadd(Object)size()Sepet #1adet11 == 0 → false

Aynı default gövde bu kez 1 görür: false. Arayüz yazıldıktan sonra eklenen bir default metot, eski sınıfları bozmadan onlara da gelir.

interface Collection {
    void add(Object o);
    int size();
    default boolean isEmpty() {
        return size() == 0;
    }
}
class Sepet implements Collection {
    private int adet = 0;
    public void add(Object o) { adet++; }
    public int size() { return adet; }
}
Collection c = new Sepet();      // main
System.out.println(c.isEmpty());
c.add("elma");
System.out.println(c.isEmpty());

Arayüz Metotları

static Metotlar (Java 8+)

interface Geometry {
    static double circleArea(double r) { return Math.PI * r * r; }
    static double squareArea(double s) { return s * s; }
    static double perimeter(double s) { return s * 4.0; }
    static double boxVolume(double w, double h, double d) {
        return w * h * d;
    }
}
// main içinde:
System.out.println(Geometry.circleArea(2));
System.out.println(Geometry.squareArea(3));
System.out.println(Geometry.perimeter(2.5));
System.out.println(Geometry.boxVolume(2, 3, 4));

Çıktı

12.566370614359172
9.0
10.0
24.0
  • Nesneye bağlı olmayan yardımcı işlevler; yalnızca arayüz adıyla çağrılır: Geometry.circleArea(2).
  • Uygulayan sınıfa kalıtımla geçmez: Daire implements Geometry olsa da Daire.squareArea(2) → cannot find symbol.
  • Yalnız PI yazılırsa cannot find symbol; sabit Math sınıfındadır: Math.PI.

Arayüz Metotları

Default Metot Çakışması

  • İki arayüz aynı imzalı default metot getirirse sınıf hangisini alacağını bilemez; metodu kendisi yazmak (geçersiz kılmak) zorundadır.
  • İstenen arayüzün sürümü ArayuzAdi.super.metot() ile çağrılabilir.
interface Selamlayan {
    default void selam() { System.out.println("Merhaba"); }
}
interface Karsilayan {
    default void selam() { System.out.println("Hoş geldiniz"); }
}
class Gorevli implements Selamlayan, Karsilayan {
    public void selam() {       // geçersiz kılmak zorunlu
        Selamlayan.super.selam();
        Karsilayan.super.selam();
    }
}

new Gorevli().selam();

Merhaba
Hoş geldiniz

selam() yazılmasaydı

error: types Selamlayan and Karsilayan are incompatible;
  class Gorevli inherits unrelated defaults for selam()
  from types Selamlayan and Karsilayan

Arayüz Metotları · Adım adım kod izleme

Default Çakışması: Kod İzleme

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()çakışma → sınıf kendisi yazar

İki arayüz de calis() için default gövde getirir. Belirsizliği çözmek için OfisMakinesi metodu kendisi yazmıştır.

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()

Referans tipi Yazici olsa da nesne OfisMakinesi: sınıfın kendi calis()'ı çalışır.

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()Tarayici.super.calis()

Tarayici.super.calis(): adı verilen arayüzün default gövdesi çağrılır.

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()Yazici.super.calis()

Sonra Yazici'nınki. Hangi sürümlerin hangi sırayla kullanılacağını sınıf seçer.

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()

Son olarak sınıf kendi eklemesini yapar: iki yeteneği birleştiren bir davranış.

«interface»Yazicicalis()«interface»Tarayicicalis()OfisMakinesicalis()calis() yazılmasaydı: derleme hatası

OfisMakinesi calis()'ı yazmasaydı sınıf derlenmezdi: inherits unrelated defaults.

interface Yazici {
    default void calis() { System.out.println("Yazdırılıyor"); }
}
interface Tarayici {
    default void calis() { System.out.println("Taranıyor"); }
}
class OfisMakinesi implements Yazici, Tarayici {
    public void calis() {
        Tarayici.super.calis();
        Yazici.super.calis();
        System.out.println("Kopya hazır");
    }
}
Yazici y = new OfisMakinesi();   // main
y.calis();

Arayüz Metotları

Özel (private) Arayüz Metotları (Java 9+)

interface Yazdirilabilir {
    String icerik();
    default void yazdir() {
        System.out.println(baslik() + ": " + icerik());
    }
    private String baslik() {   // Java 9+
        return "Belge";
    }
}
class Rapor implements Yazdirilabilir {
    public String icerik() { return "Vize sonuçları"; }
}

new Rapor().yazdir();

Belge: Vize sonuçları
  • private metotlar, default (ve static) metotların ortak yardımcı kodunu dışarı açmadan paylaşır.
  • Gövdeleri vardır ve yalnızca arayüzün içinden çağrılabilir.
  • Uygulayan sınıf onları göremez: Rapor içinde baslik() çağrılamaz.
  • Akış: yazdir() (default) → baslik() (private) + icerik() (Rapor'un uyguladığı soyut metot).
yazdir()defaultbaslik()private: yalnızca arayüz içiicerik()Rapor uygular"Belge: Vize sonuçları"

Arayüz Metotları

Arayüz Üyeleri: Özet Tablosu

ÜyeÖrnekÖrtük belirteçlerGövdeJava
Soyut metotvoid ciz();public abstractyok1.0
Sabitint KENAR_SAYISI = 4;public static final—1.0
Varsayılan metotdefault boolean isEmpty() {…}publicvar8
static metotstatic double squareArea(…) {…}publicvar8
Özel metotprivate String baslik() {…}yalnızca arayüz içindevar9
Örnek alanı, yapıcı—İzin verilmez: arayüzün nesnesi yoktur, durum (state) tutamaz
Uygulayan sınıf için kural: arayüzdeki tüm soyut metotları public olarak yazmak. Default metotlar isteğe bağlı, static ve private metotlar zaten kalıtımla geçmez.

Mühürlü Tipler

Mühürleme: sealed, non-sealed, permits

  • Sınıflar ve arayüzler mühürlenebilir (Java 17+).
  • sealed: tipi yalnızca izin verilen alt tipler genişletebilir ya da uygulayabilir.
  • permits: izin verilen alt tiplerin listesi.
  • İzin verilen her alt tip birini seçmek zorundadır:
    final: hiyerarşi burada biter;
    sealed: yeniden kısıtlar;
    non-sealed: kısıtı kaldırır, herkes genişletebilir.
  • Yarar: derleyici tüm alt tipleri bilir (kapalı hiyerarşi).
«sealed interface»Hayvan+ hareketEt()«final»Kedi+ hareketEt()«non-sealed»Kopek+ hareketEt()Kangal+ hareketEt()Kusizin yok ✗permits Kedi, Kopek

Mühürlü Tipler

Mühürlü Arayüz Örneği

public sealed interface Hayvan permits Kedi, Kopek {
    void hareketEt();
}
final class Kedi implements Hayvan {
    public void hareketEt() {
        System.out.println("Kedi sessizce yürüyor");
    }
}
non-sealed class Kopek implements Hayvan {
    public void hareketEt() {
        System.out.println("Köpek koşuyor");
    }
}
class Kangal extends Kopek {   // serbest
    public void hareketEt() {
        System.out.println("Kangal sürüyü koruyor");
    }
}
// Java 21: switch tüm alt türleri kapsar,
// default dalı gerekmez
static String tur(Hayvan h) {
    return switch (h) {
        case Kedi k -> "kedi";
        case Kopek k -> "köpek";
    };
}
// main içinde:
Hayvan[] hayvanlar =
    { new Kedi(), new Kopek(), new Kangal() };
for (Hayvan h : hayvanlar) {
    h.hareketEt();
    System.out.println(tur(h));
}

Çıktı

Kedi sessizce yürüyor
kedi
Köpek koşuyor
köpek
Kangal sürüyü koruyor
köpek

Karşılaştırma

Arayüz ve Soyut Sınıf Karşılaştırması

ÖzellikArayüzSoyut sınıf
Tanım / kullanıminterface · implementsabstract class · extends
MetotlarSoyut; Java 8+ default ve static, Java 9+ privateHem soyut hem somut metotlar
AlanlarYalnızca public static final sabitler; örnek alanı (instance field) olamazHer türlü alan (örnek alanları dahil)
YapıcıYokOlabilir; alt sınıf super(…) ile çağırır
ErişimÜyeler örtük public (private yardımcılar hariç)public, protected, private, paket
KalıtımBir sınıf çok sayıda arayüz uygular; arayüz birden çok arayüzü genişletir → çoklu kalıtım özelliğiBir sınıf yalnızca tek bir sınıftan türetilir
Amaçİlgisiz sınıflara ortak yetenek (“yapabilir”)Ortak alanlara sahip sınıfları gruplamak (“bir türüdür”)
Nesneİkisinden de new ile nesne oluşturulamaz; ikisi de referans tipi olarak kullanılır

Karşılaştırma

Hangisini Seçmeli?

Soyut sınıf uygundur

  • Alt sınıflar yakın akraba: Kare, Çember birer Sekil'dir.
  • Ortak alanlar ve yapıcı gerekiyor (isim).
  • Ortak kodun bir kısmı hazır (şablon metot).

Arayüz uygundur

  • İlgisiz sınıflara aynı yetenek: Kus, Ucak, Drone hepsi Ucabilir.
  • Bir sınıfa birden çok yetenek kazandırmak.
  • Kullanan kodla uygulayan kod arasında sözleşme (API).

Çoğu zaman ikisi birlikte kullanılır:

abstract class Hayvan implements Sesli { … }
class Ordek extends Hayvan implements Ucabilir, Yuzebilir { … }
Java, sınıflarda çoklu kalıtıma izin vermez: iki üst sınıf aynı alanı ya da metodu farklı tanımlarsa hangisinin geçerli olduğu belirsizleşir (elmas problemi). Arayüzler durum taşımadığı için bu sorun büyük ölçüde ortadan kalkar; kalan default çakışmasını ise derleyici sınıfa çözdürür.

Jenerikler

Neden Jenerikler?

Object ile

class KutuObj {
    private Object icerik;
    void koy(Object o) { icerik = o; }
    Object al() { return icerik; }
}
KutuObj k = new KutuObj();
k.koy("Merhaba");
String s = (String) k.al(); // dönüşüm şart
k.koy(42);                  // itiraz yok
String t = (String) k.al(); // çalışırken hata
Exception in thread "main"
java.lang.ClassCastException

Jenerik sınıf ile

class Kutu<T> {
    private T icerik;
    void koy(T x) { icerik = x; }
    T al() { return icerik; }
}
Kutu<String> k = new Kutu<>();
k.koy("Merhaba");
String s = k.al();          // dönüşüm yok
k.koy(42);                  // derlenmez
error: incompatible types:
  int cannot be converted to String
  • Tip güvenliği: yanlış tipteki değer çalışırken değil, derlerken yakalanır.
  • Okurken açık dönüşüm (cast) gerekmez; tek gerçekleme her tip için çalışır.

Jenerikler

Jenerik Sınıf ve Jenerik Metot

class Kutu<T> {                    // T: tip parametresi
    private T icerik;
    void koy(T x) { icerik = x; }
    T al() { return icerik; }
}
static <T> void yazdir(T oge) {    // jenerik metot
    System.out.println("Öğe: " + oge);
}
// main içinde:
Kutu<String> k1 = new Kutu<>();
k1.koy("Merhaba");
Kutu<Integer> k2 = new Kutu<>();
k2.koy(42);
int n = k2.al() + 1;               // kutudan çıkarma
System.out.println(k1.al() + " " + n);
yazdir(3.14);
yazdir('A');

Çıktı

Merhaba 43
Öğe: 3.14
Öğe: A
  • Kutu<String>: tip argümanı T'nin yerine geçer.
  • new Kutu<>(): elmas (<>), tipi soldan çıkarır.
  • Jenerik metot tip parametresini dönüş tipinden önce bildirir: <T>; tipi argümandan çıkarılır.
  • Tip argümanı referans tipi olmalı: Kutu<int> derlenmez, Kutu<Integer> yazılır.
  • Yaygın adlar: T tip, E öğe, K anahtar, V değer, R sonuç.

Jenerikler

Sınırlı Tip Parametresi

<T extends Ust>: T yalnızca Ust ya da onun alt tipi olabilir (üst sınır). Karşılığında metot gövdesi Ust'un metotlarını kullanabilir.

static <T extends Number> double ikiKati(T x) {
    return x.doubleValue() * 2;   // Number'ın metodu
}
// main içinde:
System.out.println(ikiKati(21));    // T = Integer
System.out.println(ikiKati(1.5));   // T = Double
System.out.println(ikiKati("on")); // derlenmez

İlk iki satır

42.0
3.0

Üçüncü satır

error: method ikiKati in class Main cannot be
  applied to given types; ... inference variable T
  has incompatible bounds (upper: Number, lower: String)

Arayüz de sınır olabilir (extends yazılır). Aşağıdaki metot her karşılaştırılabilir tipte (Integer, String…) en büyük öğeyi bulur, çünkü compareTo çağırabileceği garantidir:

static <T extends Comparable<T>> T enBuyuk(T[] dizi)

Jenerikler · Adım adım kod izleme

enBuyuk: Kod İzleme

iki dizi7125eb?

İki dizi de uygun: Integer ve String, Comparable'ı kendi tipleriyle uygular. Sayılar otomatik kutulanır.

T = Integer7125eb7

Derleyici T'yi Integer olarak çıkarır. İlk öğe en büyük aday: eb = 7.

T = Integer7125i = 1eb12compareTo = 1 > 0 → eb = 12

compareTo pozitifse öğe daha büyüktür: eb = 12.

T = Integer7125i = 2eb12compareTo = -1 → değişmez

Negatif sonuç: 5 daha küçük, eb değişmez.

T = Integer7125eb12

Döngü biter, 12 döner. Dönüş tipi T = Integer: dönüşüm gerekmez.

T = String"elma""kiraz""armut"eb"elma"

Aynı metot, bu kez T = String. compareTo artık sözlük sırasıyla karşılaştırır.

T = String"elma""kiraz""armut"i = 1eb"kiraz"compareTo = 6 > 0 → eb = "kiraz"

İlk farklı harfler 'k' − 'e' = 6: pozitif, eb = "kiraz".

T = String"elma""kiraz""armut"i = 2eb"kiraz"compareTo = -10 → değişmez

'a' − 'k' = −10: negatif, eb değişmez.

T = String"elma""kiraz""armut"eb"kiraz"

kiraz döner. Tek gerçekleme iki tipte çalıştı; sınır sayesinde compareTo çağrısı derleme zamanında güvence altında.

static <T extends Comparable<T>> T enBuyuk(T[] dizi) {
    T eb = dizi[0];
    for (int i = 1; i < dizi.length; i++) {
        if (dizi[i].compareTo(eb) > 0) {
            eb = dizi[i];
        }
    }
    return eb;
}
// main içinde:
Integer[] sayilar = {7, 12, 5};
String[] adlar = {"elma", "kiraz", "armut"};
System.out.println(enBuyuk(sayilar));
System.out.println(enBuyuk(adlar));

Jenerikler

Joker Tipler (?) ve Değişmezlik

static double topla(List<? extends Number> liste) {
    double t = 0;
    for (Number n : liste) t += n.doubleValue();  // okuma
    return t;
}
static void doldur(List<? super Integer> liste) {
    liste.add(1);                                 // yazma
    liste.add(2);
}
// main içinde:
List<Integer> tamlar = Arrays.asList(1, 2, 3);
List<Number> sayilar = new ArrayList<>();
doldur(sayilar);
sayilar.add(2.5);
System.out.println(topla(tamlar) + " " + sayilar);
// List<Number> x = tamlar;   derlenmez

Çıktı

6.0 [1, 2, 2.5]
IntegerNumber✓ is-aList<Integer>List<Number>✗ değilList<Integer>List<? extends Number>✓
  • Değişmezlik: List<Integer>, List<Number>'un alt tipi değildir; olsaydı listeye 2.5 eklenebilirdi.
  • ?: bilinmeyen herhangi bir tip.
  • ? extends T: liste değer üretir, okunur.
  • ? super T: liste değer tüketir, yazılır.
  • Kural (PECS): Producer Extends, Consumer Super.

Jenerikler

Tip Silme (Type Erasure)

class Kutu<T> {
    private T icerik;
    // T yeni() { return new T(); }   derlenmez
}
// main içinde:
Kutu<String> a = new Kutu<>();
Kutu<Integer> b = new Kutu<>();
System.out.println(a.getClass() == b.getClass());
System.out.println(a.getClass().getName());

Çıktı

true
Kutu
kaynak kodKutu<String> kString s = k.al();javacdenetler,silerbytecodeKutu kString s = (String) k.al();
  • Jenerik tip bilgisi çoğunlukla derleyici içindir; denetimden sonra silinir (type erasure). T yerine Object (ya da sınırı) kalır.
  • Çalışırken Kutu<String> ile Kutu<Integer> aynı sınıftır.
  • Bu yüzden new T() (unexpected type) ve new T[10] (generic array creation) yazılamaz.

Etiketler

Etiketler (Annotations)

Etiket (annotation), sınıf, metot, alan ya da parametreye eklenen üst veridir. Kendi başına davranış değiştirmez; onu derleyici, araçlar ya da çalışan program (yansıma ile) okur.

EtiketKim okur?Ne yapar?
@OverridederleyiciÜst tipte böyle bir metot yoksa hata verir
@DeprecatedderleyiciEskimiş öğe; kullanan kod uyarı alır
@SuppressWarnings("unchecked")derleyiciBelirtilen uyarıyı susturur
@FunctionalInterfacederleyiciArayüzde yalnızca bir soyut metot olduğunu denetler
@Test (JUnit)test aracıTest metotlarını bulur ve çalıştırır
@FunctionalInterface
interface Islem {
    int uygula(int a, int b);
    int ters(int a);   // 2. soyut metot
}
error: Unexpected @FunctionalInterface annotation
  Islem is not a functional interface
    multiple non-overriding abstract methods
    found in interface Islem

Etiketler

Özel Etiket ve Yansıma

@Retention(RetentionPolicy.RUNTIME)  // çalışırken okunur
@Target(ElementType.METHOD)          // yalnızca metotlara
@interface Gorev {
    String yazar();
    int oncelik() default 1;
}
class Isler {
    @Gorev(yazar = "Ayşe", oncelik = 3)
    public void yedekle() { System.out.println("Yedek alındı"); }
    public void temizle() { System.out.println("Temizlendi"); }
}
// main içinde (yansıma, java.lang.reflect.Method):
Isler isler = new Isler();
for (Method m : Isler.class.getDeclaredMethods()) {
    Gorev g = m.getAnnotation(Gorev.class);
    if (g != null) {
        System.out.println(m.getName() + ", "
            + g.yazar() + ", " + g.oncelik());
        m.invoke(isler);
    }
}

Çıktı

yedekle, Ayşe, 3
Yedek alındı
@RetentionEtiket nerede kalır?
SOURCEYalnızca kaynakta (@Override)
CLASS.class dosyasında; yansıma görmez
RUNTIMEÇalışırken yansımayla okunur
  • Yansıma (reflection): program kendi sınıflarını, metotlarını ve etiketlerini çalışırken inceler.
  • Derleyici denetimi zayıflar, kod yavaşlar; yalnızca çerçeveler gibi gerçekten gerektiğinde kullanılır.

Tasarım Desenleri

Tasarım Desenleri (Design Patterns)

  • Tasarım deseni: sık karşılaşılan bir tasarım sorununa denenmiş, yeniden kullanılabilir bir çözüm kalıbı. Kopyalanacak kod değil, sorunu ve ödünleşimleri adlandıran ortak bir sözlüktür.
  • Sorumlulukları ayırır, sınıflar arasındaki bağımlılığı azaltır: bir parça, ötekilere dokunmadan değiştirilebilir ve tek başına denenebilir.
Oluşturucunesne nasıl oluşturulur?Singleton, Fabrika, BuilderYapısalnesneler nasıl birleştirilir?Adapter, Decorator, FacadeDavranışsalnesneler nasıl iletişim kurar?Observer, Strategy, Command
Desen ancak gerçek bir sorunu çözüyorsa yararlıdır. Gereksiz eklenen desen programı karmaşıklaştırır. Desenler, “az bağımlılık”, “kalıtım yerine birleşim” gibi daha genel tasarım ilkelerinin somut uygulamalarıdır.

Tasarım Desenleri

Singleton: Tek Nesne

public final class Ayarlar {
  private static final Ayarlar TEK = new Ayarlar();
  private Ayarlar() {
    System.out.println("Ayarlar oluşturuldu");
  }
  public static Ayarlar getInstance() { return TEK; }
}
// main:
System.out.println("main başladı");
Ayarlar a = Ayarlar.getInstance();
Ayarlar b = Ayarlar.getInstance();
System.out.println(a == b);
Çıktı
main başladı
Ayarlar oluşturuldu
true
  • Sınıfın yalnızca bir nesnesi olur ve ona tek bir yerden erişilir.
  • private yapıcı: dışarıdan new Ayarlar() derlenmez.
  • Nesne, sınıf ilk kullanıldığında JVM tarafından bir kez oluşturulur: iş parçacığı güvenlidir.
  • Gizli bir küresel değişken gibidir: yerine başka bir nesne koymak zorlaşır. Çoğu zaman nesneyi yapıcıya parametre olarak vermek (bağımlılık enjeksiyonu) daha iyidir.

Tasarım Desenleri

Fabrika (Factory)

interface Sekil { double alan(); }
class Kare implements Sekil { … }   // alan: a * a
class Daire implements Sekil { … }  // alan: π r²

class SekilFabrikasi {
  static Sekil olustur(String tur, double olcu) {
    switch (tur.toLowerCase(Locale.ROOT)) {
      case "kare":  return new Kare(olcu);
      case "daire": return new Daire(olcu);
      default:      return null;  // bilinmeyen tür
    }
  }
}
Sekil s = SekilFabrikasi.olustur("Kare", 3);
System.out.println(s.alan());   // 9.0
  • Nesne oluşturma tek bir yerde toplanır; çağıran kod somut sınıfı (Kare) değil, arayüzü (Sekil) bilir.
  • Yeni bir şekil eklemek yalnızca fabrikayı değiştirir. Bilinmeyen tür için null döner; çağıran kod sonucu denetler.
  • Locale.ROOT: Türkçe yerel ayarda "DAIRE".toLowerCase() → "daıre" olur ve eşleşmez.
  • Zaten basit oluşturulan nesneler için fabrika yazmak gereksiz karmaşıklıktır.
Bilinmeyen tür
Sekil u = SekilFabrikasi.olustur("üçgen", 2);
System.out.println(u == null);   // true

Tasarım Desenleri

Gözlemci (Observer)

interface Gozlemci { void guncelle(int derece); }

class Istasyon {
  private final List<Gozlemci> liste = new ArrayList<>();
  void ekle(Gozlemci g) { liste.add(g); }
  void setSicaklik(int d) {
    for (Gozlemci g : liste) g.guncelle(d);
  }
}
Istasyonkonu (subject)Ekran gözlemcisiUyarı gözlemcisiKayıt gözlemcisisetSicaklik → her gözlemcide guncelle(d)
  • Bire çok ilişki: konu değişince kayıtlı bütün gözlemciler haberdar edilir.
  • Konu, gözlemcilerin somut sınıfını bilmez; yalnızca arayüzü çağırır.
  • Gözlemciler konsola yazan küçük nesneler olabilir: biri ölçümü gösterir, biri uyarır, biri kaydeder.

Tasarım Desenleri · Adım adım kod izleme

Gözlemci Deseni Adım Adım

interface Gozlemci { void guncelle(int derece); }
class Istasyon {
  private final List<Gozlemci> liste = new ArrayList<>();
  void ekle(Gozlemci g) { liste.add(g); }
  void setSicaklik(int d) {
    for (Gozlemci g : liste) g.guncelle(d);
  }
}
// main:
Istasyon ist = new Istasyon();
ist.ekle(d -> System.out.println("Ekran: " + d + " °C"));
ist.ekle(d -> { if (d > 30) System.out.println("Uyarı: sıcak!"); });
ist.setSicaklik(25);
ist.setSicaklik(34);
istson: –liste

Konu nesnesi oluşur; gözlemci listesi boş.

istson: –liste[0] ekran

İlk gözlemci bir lambda: Gozlemci arayüzünün tek metodunu uygular. Listeye eklenir, çalışmaz.

istson: –liste[0] ekran[1] uyarı (d > 30)

İkinci gözlemci eklenir: yalnızca 30 derecenin üstünde konuşur.

istson: 25liste[0] ekran[1] uyarı (d > 30)

setSicaklik(25): döngü listede baştan gezinir.

istson: 25liste[0] ekran[1] uyarı (d > 30)

Ekran gözlemcisinin guncelle(25)'i çağrılır.

istson: 25liste[0] ekran[1] uyarı (d > 30)

Uyarı gözlemcisi de haber alır ama 25 > 30 yanlış: bir şey yazmaz.

istson: 34liste[0] ekran[1] uyarı (d > 30)

Yeni ölçüm: 34.

istson: 34liste[0] ekran[1] uyarı (d > 30)

Ekran yeni değeri yazar.

istson: 34liste[0] ekran[1] uyarı (d > 30)

Bu kez koşul doğru: uyarı yazılır. Istasyon hiçbir gözlemcinin ne yaptığını bilmedi; yeni gözlemci eklemek onun kodunu değiştirmez.

Tasarım Desenleri

Strateji (Strategy)

interface Indirim { double uygula(double tutar); }

class OgrenciIndirimi implements Indirim {
  public double uygula(double tutar) { return tutar * 0.90; }
}
class Kasa {
  private final Indirim strateji;
  Kasa(Indirim strateji) { this.strateji = strateji; }
  double odenecek(double tutar) {
    return strateji.uygula(tutar);
  }
}
Kasa k = new Kasa(new OgrenciIndirimi());
System.out.println(k.odenecek(250));   // 225.0
  • Birbirinin yerine geçebilen algoritmalar ortak bir arayüzün arkasına konur.
  • Kasa'nın kodu değişmeden başka bir strateji nesnesiyle davranış değişir.
  • Arayüzün tek metodu olduğundan strateji bir lambda da olabilir: new Kasa(t -> t * 0.90).
  • Comparator ile sıralama da bir strateji örneğidir.

Tasarım Desenleri · Adım adım kod izleme

Strateji Deseni Adım Adım

interface Indirim { double uygula(double tutar); }
class Kasa {
  private final Indirim strateji;
  Kasa(Indirim strateji) { this.strateji = strateji; }
  double odenecek(double tutar) { return strateji.uygula(tutar); }
}
// main:
Kasa ogrenci = new Kasa(t -> t * 0.90);
Kasa kampanya = new Kasa(t -> t >= 200 ? t - 50 : t);
System.out.println(ogrenci.odenecek(250));
System.out.println(kampanya.odenecek(250));
System.out.println(kampanya.odenecek(120));
// sabit strateji: Kasa tek başına denenir
System.out.println(new Kasa(t -> 7).odenecek(100));
ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : t

Kasa indirimi kendisi hesaplamaz; yapıcıda verilen strateji nesnesine sorar. İlk kasaya %10 indirim takılır.

ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : t

Aynı Kasa sınıfı, başka bir stratejiyle: 200 TL ve üstüne 50 TL indirim.

ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : todenecek(250) → 225.0

odenecek işi stratejiye devreder: 250 × 0.90 = 225.0.

ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : todenecek(250) → 200.0

Kampanya stratejisi: 250 ≥ 200 → 250 − 50 = 200.0.

ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : todenecek(120) → 120.0

120 < 200: indirim yok. Kasa'nın kodu hiç değişmeden davranış değişti.

ogrencit × 0.90kampanyat ≥ 200 ? t − 50 : tsabit kasaher zaman 7odenecek(100) → 7.0

Gerçek indirim kuralı yerine sabit bir strateji takılır: tutar ne olursa olsun sonuç 7.0. Böylece yalnızca Kasa'nın işi stratejiye doğru devrettiği görülür.

Tasarım Desenleri

MVC: Model, Görünüm, Denetleyici

Kullanıcı girdisiDenetleyiciModel (veri)Görünüm (ekran)güncelleryenilerveriyi okur
  • Model: veri ve kurallar (Ogrenci, getAd()); ekrandan habersizdir, ekran olmadan denenebilir.
  • Görünüm: yalnızca gösterir (OgrenciGorunum.goster(ad) konsola yazar).
  • Denetleyici: girdiyi alır, modeli ve görünümü yönetir; guncelle() modeli okur, sonucu görünüme verir.
  • Çerçeveler MVC'yi farklı biçimlerde uygular; tek bir “doğru” sınıf yapısı yoktur.
Ogrenci m = new Ogrenci("Ayşe");
OgrenciGorunum g = new OgrenciGorunum();
OgrenciDenetleyici d =
    new OgrenciDenetleyici(m, g);
d.guncelle();  // g.goster(m.getAd())
Çıktı
Öğrenci: Ayşe

Tasarım Desenleri

Desenler: Ne Zaman Kullanılmaz?

Singleton'ı küresel değişken yapmak

Her yerden erişilen değişebilir durum, programın parçalarını birbirine bağımlı kılar.

Gereksiz fabrika

new Nokta(3, 4) kadar basit bir oluşturma için ayrı sınıf yazmak.

Gereksiz gözlemci

Tek bir doğrudan metot çağrısı yeterken olay sistemi kurmak; akışı izlemek zorlaşır.

Deseni ad olsun diye kullanmak

Önce sorun, sonra desen: “Hangi değişikliği kolaylaştırıyor? Hangi bağımlılığı azaltıyor?”

Strateji ve bağımlılığın dışarıdan verilmesi, gerçek parçanın yerine basit bir sahte nesne koymayı sağlar (Kasa'ya sabit strateji takmak gibi). Tek başına denemesi zor bir sınıf, çoğu zaman tasarımının gözden geçirilmesi gerektiğini gösterir.

Özet

Özet

KavramÖzü
SoyutlamaAyrıntıyı gizleyip “ne yapılır”ı tanımlamak; “nasıl”ı alt sınıflara bırakmak
Soyut sınıfabstract class; nesnesi olmaz; alan, yapıcı, soyut ve somut metot içerebilir
Soyut metotGövdesiz, ; ile biter; somut alt sınıf uygulamak zorunda
ArayüzSözleşme; metotlar örtük public abstract, alanlar public static final
implementsTek extends, çok sayıda implements; uygulayan metot public
Lambda(parametreler) -> ifade; fonksiyonel arayüze atanır (Java 8+)
default / static / privateGövdeli arayüz metotları (Java 8 / 8 / 9)
sealedpermits ile izinli alt tipler; her biri final, sealed ya da non-sealed
JeneriklerKutu<T>, <T extends Ust>, joker ?; denetim derlemede, tip bilgisi sonra silinir
Etiketler@Override gibi üst veri; RUNTIME etiketleri yansımayla okunur
Tasarım desenleriSingleton, Fabrika, Gözlemci, Strateji, MVC: denenmiş çözüm kalıpları

Özet

İlgili Sayfalar

Dinamik Bağlama

Soyut tipten referansla çağrılan metodun hangi sınıftan seçildiğini adım adım gösteren simülatör.

Çok Biçimlilik

Arayüz ve soyut sınıf tipindeki referanslarla çalışan dinamik bağlamanın temelleri.

Soyut metot ya da arayüz metodu uygulanırken yazılan @Override, ad ya da parametre yanlış yazıldığında derleme hatasıyla hemen fark edilir.

Özet

Sık Yapılan Hatalar (1): Soyut Sınıflar

Soyut sınıftan nesne

new Sekil("?") → Sekil is abstract; cannot be instantiated. Somut bir alt sınıf kullanılır.

Soyut metodu unutmak

Cember is not abstract and does not override abstract method ciz() in Sekil. Metot yazılır ya da sınıf abstract yapılır.

Soyut metoda gövde

abstract void ciz() { } → abstract methods cannot have a body. Ya abstract ya da gövde.

Sınıfı abstract yapmamak

class Sekil { abstract void ciz(); } → Sekil is not abstract and does not override abstract method ciz() in Sekil.

Çelişen belirteçler

abstract final, private abstract, static abstract → illegal combination of modifiers.

İmzayı yanlış yazmak

double Alan() ya da double alan(int x) yeni bir metottur, soyut alan() hâlâ eksiktir. @Override bunu yakalar.

Özet

Sık Yapılan Hatalar (2): Arayüzler

public'i unutmak

void ciz() { … } → attempting to assign weaker access privileges; was public. Arayüz metodu public yazılır.

Sabiti değiştirmek

GeometrikSekil.KENAR_SAYISI = 5; → cannot assign a value to static final variable KENAR_SAYISI.

İki sınıftan türetmek

class C extends A, B → '{' expected. Tek extends, çok sayıda implements.

Referans tipinde olmayan metot

Ucabilir u = new Ordek(); u.yuz(); → cannot find symbol. Metot, onu bilen tipte bir referansla çağrılır.

Çakışan default metotlar

class Gorevli inherits unrelated defaults for selam(). Metot sınıfta geçersiz kılınır.

Mühürleme hataları

İzinsiz alt tip: class is not allowed to extend sealed class: Hayvan. İzinli alt tipte belirteç yok: sealed, non-sealed or final modifiers expected.

Özet

Örnek Problemler (1): Derlenir mi?

// (a)
abstract class A {
    void f() { System.out.println("f"); }
}
// (b)
class B { abstract void g(); }
// (c)
interface I { int X = 1; }
...  I.X++;
// (d)
interface J { void h(); }
class C implements J { void h() { } }
// (e)
abstract class D { abstract void k(); }
abstract class E extends D { }
  1. Her parça (a)–(e) için: derlenir mi? Derlenmiyorsa hata nedir?
  2. Derlenen parçalardan hangisinin nesnesi new ile oluşturulabilir?
Cevapları göster
  1. (a) Derlenir: soyut metodu olmayan soyut sınıf geçerlidir. (b) Hata: B is not abstract and does not override abstract method g() in B. (c) Hata: cannot assign a value to static final variable X. (d) Hata: h() public değil → weaker access privileges; was public. (e) Derlenir: E de soyut olduğu için k()'yı yazmak zorunda değil.
  2. Hiçbiri: A ve E soyut olduğundan new A(), new E() da derleme hatasıdır.

Özet

Örnek Problemler (2): Çıktı Nedir?

interface Sesli {
    default String ses() { return "..."; }
}
abstract class Hayvan implements Sesli {
    Hayvan() { System.out.print("H "); }
    abstract String ad();
}
class Kedi extends Hayvan {
    Kedi() { System.out.print("K "); }
    String ad() { return "kedi"; }
    public String ses() { return "miyav"; }
}
class Balik extends Hayvan {
    String ad() { return "balık"; }
}
// main içinde:
Hayvan[] d = { new Kedi(), new Balik() };
System.out.println();
for (Hayvan h : d)
    System.out.println(h.ad() + ": " + h.ses());
  1. Program ne yazdırır?
  2. Balik, ses()'i yazmadığı hâlde neden derlenir?
  3. Kedi'deki ses()'ten public silinirse ne olur?
  4. Sesli s = new Balik(); s.ad(); derlenir mi?
Cevapları göster
  1. H K H, sonra kedi: miyav ve balık: .... Kedi oluşurken önce Hayvan yapıcısı (H), sonra Kedi yapıcısı (K); Balik'te yalnızca H.
  2. ses() soyut değil, default: Balik onu kalıtımla alır; tek soyut metot ad()'ı yazmıştır.
  3. Derleme hatası: weaker access privileges; was public.
  4. Hayır: cannot find symbol. Sesli tipi yalnızca ses()'i bilir.

Özet

Alıştırmalar (1)

  1. Soyut Calisan sınıfı yazın: isim alanı, yapıcı, soyut double maas() ve adı ile maaşı yazdıran bilgi(). Alt sınıflar: TamZamanli (aylık 30000), SaatUcretli (120 saat × 200). Bir Calisan[] dizisinde bilgi() çağırın.
  2. Sekil2B arayüzü: soyut double alan() ve "Alan: " + alan() döndüren default ozet(). Ucgen(6, 4) için ozet() ne döndürür?
  3. interface Hesap { static int topla(int a, int b) … } ve class Basit implements Hesap {}: Hesap.topla(3, 4) ve Basit.topla(3, 4) sonuçları?
Cevaplar / ipuçları
  1. double maas() { return saat * ucret; } gibi; çıktı Ayşe: 30000.0 / Ali: 24000.0.
  2. public double alan() { return taban * yukseklik / 2; } → Alan: 12.0.
  3. 7; ikincisi derlenmez: cannot find symbol (static arayüz metotları kalıtımla geçmez).

Özet

Alıştırmalar (2)

  1. sealed abstract class Sekil permits Kare, Cember yazın; Kare final, Cember non-sealed. Elips extends Cember ve Ucgen extends Sekil derlenir mi?
  2. Soyut sınıf mı, arayüz mü? (a) plakası ve yapıcısı olan Tasit; (b) Ogrenci ve Fatura'nın ortak kaydet() yeteneği; (c) hem Hayvan hem yüzebilen Balik.
  3. class Kare implements GeometrikSekil içinde public void Ciz() { … } yazıldı. Ne olur? Metodun üstüne @Override eklenirse?
Cevaplar / ipuçları
  1. Elips derlenir (Cember non-sealed); Ucgen: class is not allowed to extend sealed class: Sekil.
  2. (a) soyut sınıf (ortak alan + yapıcı); (b) arayüz (ilgisiz sınıflar); (c) class Balik extends Hayvan implements Yuzebilir.
  3. Ciz ≠ ciz (büyük/küçük harf): Kare is not abstract and does not override abstract method ciz() in GeometrikSekil. @Override ile ek olarak method does not override or implement a method from a supertype.

Özet

Kaynaklar

  • Oracle, The Java Tutorials: Abstract Methods and Classes; Interfaces; Default Methods; Lambda Expressions.
  • J. Gosling ve diğ., The Java Language Specification, Java SE 21 Edition (8.1.1.1 abstract Classes, 8.1.1.2 sealed Classes, Bölüm 9: Interfaces, 15.27 Lambda Expressions).
  • Oracle, JEP 409: Sealed Classes (Java 17) ve JEP 441: Pattern Matching for switch (Java 21).
  • P. Deitel ve H. Deitel, Java: How to Program, 11. baskı, Pearson (Bölüm 10: Object-Oriented Programming: Polymorphism and Interfaces).
  • K. Sierra ve B. Bates, Head First Java, 3. baskı, O'Reilly (Bölüm 8: Interfaces and Abstract Classes).
  • C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson (Bölüm 5–6).
  • E. Gamma, R. Helm, R. Johnson ve J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994.

Son

Bölüm 4.5: Soyut Sınıflar ve Arayüzler
Sonraki bölüm: Bölüm 5: Koleksiyonlar · Terim sözlüğü
1 / 1 Sercan KÜLCÜ, Tüm hakları saklıdır.