0% found this document useful (0 votes)
4 views56 pages

Java

Uploaded by

dobby6104
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views56 pages

Java

Uploaded by

dobby6104
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Nesne Yönelimli Programlama (Java) Final Sınavı

Ders Notları
1. HAFTA 1: Nesne Yönelimli Programlamaya Giriş
1.1. Kapsamlı Konu Anlatımı

Bu hafta, Nesne Yönelimli Programlama (NYP) dersine temel bir giriş niteliği taşımaktadır. Dersin
başarılı bir şekilde tamamlanabilmesi için idari kuralları ve beklentileri anlamak önemlidir. Devam
zorunluluğu kapsamında, öğrencilerin en fazla 5 defa devamsızlık hakkı bulunmaktadır; bu sınırı
aşanlar final ve bütünleme sınavlarına katılamayacaktır. Dönem sonu değerlendirmesi; %40 Vize,
%50 Final ve ek olarak +%10 Quiz/Proje notlarından oluşacaktır. Bu dersin sonunda öğrencilerin
Nesne Yönelimli Programlama kavramlarını, Java dilinin özelliklerini anlaması (CO1), sınıflar,
nesneler ve kurucu metotlar gibi temel yapıları uygulayabilmesi (CO2), kalıtım mekanizmalarını
tasarlayabilmesi (CO3), çok biçimlilik ve jenerik programlama gibi ileri konuları analiz edebilmesi
(CO4) ve G/Ç işlemleri ile istisna yönetimini keşfedebilmesi (CO5) hedeflenmektedir.

Java programlama sürecinde kullanılacak Entegre Geliştirme Ortamları (IDE) olarak IntelliJ IDEA
(Community Edition) ve Visual Studio Code öne çıkmaktadır. Her ikisi de ücretsiz, açık kaynaklı ve
çoklu platform desteği sunan güçlü araçlardır.

Bilgisayar dilleri, soyutlama seviyelerine göre üç ana kategoriye ayrılır:

1. Düşük Seviyeli Diller (Makine Dili): Donanıma en yakın, "1" ve "0"lardan oluşan dillerdir. Çok
hızlıdırlar ve az bellek kullanırlar ancak insan için anlaşılması zordur.
2. Orta Seviyeli Diller (Assembly): Makine diline göre bir üst seviyede soyutlama sunar ve LOAD,
STORE gibi anımsatıcı komutlar kullanır.
3. Yüksek Seviyeli Diller (Java, C++, Python): İnsan diline en yakın olan, taşınabilir ve yüksek
soyutlama sunan dillerdir. Bu diller, geliştirme kolaylığı sağlarken, derleme veya yorumlanma
ihtiyacı duydukları için düşük seviyeli dillere göre daha yavaştır.

Java, bu sınıflandırmada özel bir yere sahiptir; çünkü hem derlenen hem de yorumlanan bir dildir.
Geliştirici tarafından yazılan kaynak kod (.java dosyası), javac derleyicisi ile bytecode adı verilen bir
ara koda (.class dosyası) dönüştürülür. Bu bytecode, platformdan bağımsızdır ve herhangi bir işletim
sisteminde yüklü olan Java Sanal Makinesi (JVM) içerisindeki Yorumlayıcı (Interpreter) tarafından
o sisteme özgü makine koduna çevrilerek çalıştırılır. Bu "bir kere yaz, her yerde çalıştır" (Write Once,
Run Anywhere) felsefesi, Java'nın en güçlü yanlarından biridir. Java, kullanım alanlarına göre Java
SE (Standard Edition - masaüstü ve sunucu uygulamaları), Java EE (Enterprise Edition - büyük
ölçekli ağ uygulamaları) ve Java ME (Micro Edition - gömülü sistemler ve mobil cihazlar) olmak üzere
üç ana sürüme ayrılır.

Programlama paradigmaları açısından bakıldığında, Nesne Yönelimli Programlama (OOP),


kendisinden önceki Prosedür Odaklı Programlama (POP) anlayışına bir alternatif olarak doğmuştur.
POP, "bir işin nasıl yapılacağına" odaklanır ve büyük programları fonksiyonlara ayırır. Bu yaklaşımda
veri, genellikle global olarak tanımlanır ve birçok fonksiyon tarafından serbestçe erişilebilir, bu da veri
güvenliğini zayıflatır. OOP ise temel odağını veri güvenliğine ve veriyi işleyen fonksiyonları bir araya
getiren nesnelere kaydırır. Program, fonksiyonlara değil, nesnelere bölünür.

Bu nesne teknolojisinin temelinde üç anahtar kavram yatar:

Sınıf (Class): Bir nesne için bir şablon veya taslaktır. Örneğin, bir "Otomobil" sınıfı, tüm
otomobillerin ortak özelliklerini (renk, model, hız gibi nitelikler) ve yapabilecekleri eylemleri
(hızlanma, fren yapma gibi davranışlar) tanımlar.
Nesne (Object): Bir sınıftan oluşturulmuş, somut bir varlıktır. "Otomobil" sınıfından oluşturulan
sarı renkli, 2023 model bir Mustang, bir nesnedir.
Örnek (Instance): Belirli bir sınıftan oluşturulmuş somut bir nesneye verilen addır. Yukarıdaki
Mustang, Otomobil sınıfının bir örneğidir.

1.2. Kritik Bilgi Maddeleri

Ders Kuralları:
Devamsızlık: 5 defa devamsızlık hakkı vardır. Bu sınır aşılırsa final ve bütünleme sınavına
girilemez.
Değerlendirme: Vize notu %40, Final notu %50 oranında etkilidir. Quiz veya Proje notu ise
+%10 bonus katkı sağlayabilir.
Bilgisayar Dilleri Arasındaki Farklar:
Hız: Düşük seviyeli diller derleme/yorumlanma gerektirmediği için en hızlı olanlardır.
Bellek: Düşük seviyeli diller daha az bellek tüketir.
Kullanım Kolaylığı: Yüksek seviyeli diller insan diline daha yakın ve kullanıcı dostudur.
Taşınabilirlik: Yüksek seviyeli diller farklı makinelerde çalıştırılabilir (makineden bağımsızdır).
Soyutlama: Yüksek seviyeli diller, donanım detaylarını gizleyerek en yüksek soyutlama
seviyesini sunar.
Java Derleme Süreci:
1. Geliştirici, insan tarafından okunabilir Kaynak Kodu (.java dosyası) yazar.
2. javac derleyicisi, kaynak kodu platformdan bağımsız Bytecode'a (.class dosyası) çevirir.
3. JVM üzerindeki Yorumlayıcı (Interpreter), Bytecode'u çalıştırarak anlık olarak makine
koduna dönüştürür.
POP ve OOP Karşılaştırması:
POP (Prosedür Odaklı Programlama): Odak noktası prosedürler ve fonksiyonlardır.
Program, fonksiyonlara bölünür. Veri güvenliği düşüktür; global veriler fonksiyonlar arasında
serbestçe paylaşılabilir. Örnek Diller: C, Pascal.
OOP (Nesne Yönelimli Programlama): Odak noktası veri ve nesnelerdir. Program,
nesnelere bölünür. Veri güvenliği yüksektir; verilere sadece ait oldukları sınıfın metotları
üzerinden kontrollü bir şekilde erişilir. Örnek Diller: Java, C++, Python.
Temel Kavramlar:
Sınıf (Class): Belirli bir türde nesne oluşturmak için kullanılan bir şablon veya taslaktır.
Nesne (Object): Bir sınıftan türetilen, kendine ait nitelikleri (veri) ve davranışları (metot) olan
somut bir bileşendir.
Örnek (Instance): Belirli bir sınıftan oluşturulmuş spesifik bir nesnedir.

1.3. Teknik Terimler ve Tanımlar


JVM (Java Virtual Machine): Java Sanal Makinesi. Java bytecode'unu çalıştırarak platform
bağımsızlığı sağlayan yazılım katmanıdır.
Bytecode: Java kaynak kodunun derlenmesiyle oluşan ve JVM tarafından anlaşılan ara koddur.
IDE (Integrated Development Environment): Entegre Geliştirme Ortamı. Kod yazma, derleme
ve hata ayıklama süreçlerini kolaylaştıran yazılım (örn. IntelliJ IDEA, Visual Studio Code).
Encapsulation (Kapsülleme): Veriyi (nitelikler) ve onu işleyen metotları (davranışlar) bir sınıf
içinde birleştirerek, veriyi dış müdahalelerden koruma prensibi.
Inheritance (Kalıtım): Bir sınıfın özelliklerini ve davranışlarını başka bir sınıfa miras olarak
aktarmasıdır.
Polymorphism (Çok Biçimlilik): Farklı nesnelerin aynı mesaja (metot çağrısına) farklı şekillerde
yanıt vermesi yeteneğidir.
Abstraction (Soyutlama): Karmaşıklığı azaltmak için bir nesnenin yalnızca gerekli özelliklerini
gösterip gereksiz ayrıntıları gizleme işlemidir.
Attribute (Nitelik): Bir nesnenin özelliklerini tanımlayan değişkenlerdir (örneğin, bir arabanın
renk niteliği).
Behavior (Davranış): Bir nesnenin gerçekleştirebildiği eylemleri tanımlayan metotlardır (örneğin,
bir arabanın hizlan() davranışı).
Instantiation (Örnekleme): Bir sınıftan new anahtar kelimesiyle yeni bir nesne (örnek)
oluşturma sürecidir.

1.4. Şekil/Tablo Yorumları

Bilgisayar Dilleri Şeması:Bu şema, bilgisayar dillerini soyutlama seviyelerine göre üç kategoriye
ayırır:
1. Düşük Seviyeli Dil (Makine Dili): Talimatları '1' ve '0'lar (Binary) kullanarak doğrudan
donanıma hitap eder.
2. Orta Seviyeli Dil (Assembly Dili): LOAD, STORE gibi anımsatıcılar (mnemonics) kullanarak
makine diline göre daha okunabilir talimatlar oluşturur.
3. Yüksek Seviyeli Dil: COBOL, C++, JAVA gibi insan diline yakın, okunabilirliği ve taşınabilirliği
yüksek dillerdir.
Assembly Dili Tablosu: Bu tablo, iki tam sayıyı toplamak için yazılmış bir Assembly programı
örneğini sunar. LOAD RF Keyboard (klavyeden veriyi F yazmacına yükle) gibi her komut satırının
yanında, o komutun ne işe yaradığını açıklayan bir metin bulunur. Bu, makine dilinden bir seviye
yukarıda, ancak modern yüksek seviyeli dillerden oldukça uzak bir programlama düzeyini
gösterir.
Java Derleme Süreci Şeması: Bu şema, Java'nın "derle ve yorumla" modelini görselleştirir.
Süreç şu adımlarla ilerler: "Java Source code" (.java dosyası) -> "Compiler" (Derleyici) ->
"Bytecode" (.class dosyası) -> "Interpreter" (Yorumlayıcı) -> "Machine code" (Makine Kodu) ->
"Real Machine" (Gerçek Makine). Bu yapı, Java'nın platform bağımsızlığını sağlar.
POP ve OOP Veri Akış Şemaları:Bu iki şema, paradigmalar arasındaki en temel farkı vurgular:
POP Şeması: Ortada bulunan "Global Data"ya birden fazla fonksiyonun (Function-A,
Function-B, vb.) doğrudan ve kontrolsüz bir şekilde eriştiğini gösterir. Veri güvenliği zayıftır.
OOP Şeması: Veri ve fonksiyonların (Data-A ve Functions-A gibi) bir arada paketlendiği
nesneleri gösterir. Veri akışı, bu nesnelerin kendi fonksiyonları (metotları) aracılığıyla kontrollü
bir şekilde gerçekleşir; global ve serbest erişim yoktur.
Sınıf ve Örnek Şeması:Bu şema, Sınıf (soyut bir kavram) ile Örnek (somut bir varlık) arasındaki
ilişkiyi üç örnekle açıklar:
Sınıf: Building (Bina) -> Örnek: Empire State
Sınıf: Dog (Köpek) -> Örnek: Lassie
Sınıf: Computer (Bilgisayar) -> Örnek: Your computer (Sizin bilgisayarınız) Şema, sınıfın bir
şablon, örneğin ise o şablondan üretilmiş gerçek bir nesne olduğunu net bir şekilde ortaya
koyar.

1.5. Sınav Dikkat Noktası

Sınava hazırlanırken, Prosedür Odaklı Programlama (POP) ile Nesne Yönelimli Programlama
(OOP) arasındaki temel felsefe farkını net bir şekilde anlamalısınız. Bu iki yaklaşımı veri yönetimi,
program yapısı ve temel odak noktaları açısından karşılaştıran bir tablo oluşturun. Özellikle OOP'nin
veri güvenliğini (encapsulation) nasıl önceliklendirdiğini ve verilerin global olmak yerine nesneler
içinde nasıl korunduğunu açıklayabilmeniz kritik öneme sahiptir. POP'un fonksiyon odaklı yapısı ile
OOP'nin nesne odaklı yapısı arasındaki zıtlığı kavramak, sınavda başarılı olmanız için esastır.

1.6. Örnekler

Aşağıda, dersin ilk haftasında incelenen temel Java kod yapılarına ilişkin örnekler bulunmaktadır.

Bu örnek, Java gibi yüksek seviyeli bir dilde, nesneler (human, pc) ve sınıflar (Player) aracılığıyla
karmaşık bir oyun mantığının nasıl ifade edildiğini göstermektedir.

public static void main(String[] args) {

Player human = new Player("HUMAN",5);

Player pc = new Player("PC");

do {

[Link](); // Player sınıfından bir insan nesnesi hareket ediyor!

[Link](); // Player sınıfından bir pc nesnesi hareket ediyor!

[Link](human,pc); // static metot.

} while(![Link]);

[Link](human, pc);

Bu kod, en temel Java uygulamasının yapısını gösterir: paket tanımı, kütüphane ekleme, sınıf ve
main metodu.

import [Link]; // C'deki #include gibi kütüphane/paket ekleme

package chapter1; // C++'daki namespace gibi bir yapı

public class Welcome { // Sınıf tanımı

// main metodu (fonksiyonu), programın başlangıç noktasıdır


public static void main(String[] args) {

[Link]("Welcome to Java!");

Bu örnek, bir Dog sınıfının (şablon) nasıl tanımlandığını ve başka bir sınıf içindeki main metodunda
bu şablondan somut bir dog nesnesinin nasıl oluşturulduğunu (new anahtar kelimesiyle) ve
kullanıldığını gösterir.

// Sınıf Tanımı (Şablon)

public class Dog {

// Nitelikler (alanlar/instance variables)

String breed;

int age;

String color;

// Davranışlar (metotlar)

void bark() {}

void hunger() {}

void sleep() {}

// Başka bir sınıfta nesne oluşturma ve metot çağırma

public class JavaApplication1 {

public static void main(String[] args) {

// Dog sınıfından bir nesne (örnek) oluşturuluyor

Dog dog = new Dog();

// Nesnenin bir davranışı (metodu) çağrılıyor

[Link]();

}
}

--------------------------------------------------------------------------------

2. HAFTA 3: Sınıflara Giriş (Bölüm 2) - Erişim Belirleyiciler,


Kurucular, UML ve Kapsülleme
2.1. Kapsamlı Konu Anlatımı

Geçen haftanın konuları olan ilkel tipler (primitive types) ve referans tipleri (reference types)
arasındaki farkı hatırlayarak başlayalım. İlkel tipler (int, double vb.) doğrudan değeri saklarken,
referans tipleri bellekteki bir nesnenin adresini saklar. Bu haftanın ana odağı; sınıfların erişim
seviyelerini kontrol etmek, nesneleri başlangıç değerleriyle oluşturmak ve sınıf tasarımlarını standart
bir dille modellemektir.

Erişim Belirleyiciler (Access Modifiers), bir sınıf üyesinin (alan veya metot) nereden erişilebilir
olduğunu kontrol eder. Temel belirleyiciler şunlardır:

public: Üye her yerden (farklı paketlerden bile) erişilebilirdir.


default (anahtar kelime kullanılmaz): Üye sadece aynı paket içerisindeki sınıflardan erişilebilirdir.
private: Üye sadece tanımlandığı sınıfın içinden erişilebilirdir. Bu, veri gizlemenin ve
kapsüllemenin temelini oluşturur. Java'da ayrıca protected adında, kalıtım konusunda
göreceğimiz bir erişim belirleyici daha bulunur; ancak bu konuyu şimdilik atlayacağız.

Sınıf Kurucuları (Class Constructors), bir nesne new anahtar kelimesiyle oluşturulduğu anda
otomatik olarak çalışan özel metotlardır. Kurucuların temel kuralları şunlardır: Sınıf ile aynı isme sahip
olmalı ve geri dönüş tipleri (void dahil) olmamalıdır. Bir sınıfta hiçbir kurucu tanımlanmazsa, derleyici
otomatik olarak parametresiz bir varsayılan kurucu (default constructor) ekler. Ancak, geliştirici
tarafından herhangi bir kurucu (parametreli veya parametresiz) yazıldığında, bu otomatik varsayılan
kurucu artık eklenmez. Bu durum, nesnelerin farklı başlangıç bilgileriyle oluşturulmasına olanak
tanıyan Aşırı Yüklenmiş Kurucular (Overloaded Constructors) kavramını doğurur. Aynı isimde
fakat farklı parametre listelerine sahip birden fazla kurucu metot tanımlanabilir.

UML (Unified Modeling Language), yazılım sistemlerinin tasarımını görselleştirmek için kullanılan
standart bir grafiksel dildir. Bir UML sınıf diyagramı, bir sınıfı üç bölümlü bir dikdörtgen ile temsil eder:

1. Üst Bölüm: Sınıfın adı.


2. Orta Bölüm: Sınıfın nitelikleri (alanları).
3. Alt Bölüm: Sınıfın operasyonları (metotları ve kurucuları). Diyagramda + sembolü public erişimi,
- sembolü ise private erişimi temsil eder.

Son olarak, NYP'nin en önemli prensiplerinden biri olan Kapsülleme (Encapsulation) veya Veri
Saklama (Data Hiding), bu kavramları bir araya getirir. Bu prensip, sınıfın niteliklerini (instance
variables) private yaparak dış dünyadan doğrudan erişimi engellemeyi ve bu verilere yalnızca public
olan get (okuma) ve set (yazma) metotları aracılığıyla kontrollü erişim sağlamayı hedefler. Örneğin,
bir banka hesabı sınıfında balance (bakiye) niteliği private yapılır. Para yatırma işlemi, setBalance
veya deposit gibi bir public metot üzerinden yapılır. Bu metot, yatırılan miktarın negatif olup
olmadığını kontrol ederek verinin tutarlılığını ve güvenliğini sağlar.
2.2. Kritik Bilgi Maddeleri

İlkel ve Referans Tipleri:


İlkel tipler (int, double, boolean vb.) değeri doğrudan saklar. Varsayılan değerleri 0, 0.0 veya
false'tur.
Referans tipleri (sınıflar, diziler) bellekteki bir nesnenin adresini saklar. Varsayılan değerleri
null'dur.
Erişim Belirleyiciler:
public: Her yerden erişim mümkündür.
private: Sadece tanımlandığı sınıf içinden erişilebilir. Kapsüllemenin temelidir.
default (anahtar kelimesiz): Sadece aynı paket içindeki sınıflardan erişilebilir.
Kurucu Metot (Constructor) Kuralları:
Sınıf ile aynı isme sahip olmalıdır.
Geri dönüş tipi (void, int vb.) yoktur.
Nesne new anahtar kelimesiyle oluşturulduğunda otomatik olarak çağrılır.
Bir sınıfa özel bir kurucu metot yazılırsa, Java'nın otomatik olarak sağladığı parametresiz
varsayılan kurucu (default constructor) artık oluşturulmaz.
UML Sınıf Diyagramı Yapısı:
Üst Bölüm: Sınıf Adı
Orta Bölüm: Nitelikler (Değişkenler). - name: String formatında yazılır (- private anlamına
gelir).
Alt Bölüm: Operasyonlar (Metotlar ve Kurucular). + getName(): String formatında yazılır (+
public anlamına gelir).
Kurucular <<constructor>> etiketi ile belirtilir.
Kapsülleme (Encapsulation):
Amaç: Veri güvenliğini sağlamak ve veri üzerinde tam kontrol kurmak.
Yöntem: Nitelikleri (instance variables) private olarak tanımla. Bu niteliklere erişmek ve onları
değiştirmek için public olan get() ve set() metotları oluştur.
Faydası: set metodu içinde koşullu mantık (örneğin, bakiye negatif olamaz) kullanarak verinin
bütünlüğünü ve tutarlılığını garanti altına almayı sağlar.

2.3. Teknik Terimler ve Tanımlar

Access Modifier (Erişim Belirleyici): Bir sınıfın, metodun veya alanın erişim seviyesini
belirleyen public, private gibi anahtar kelimelerdir.
Constructor (Kurucu Metot): Bir sınıftan nesne oluşturulduğunda, o nesnenin başlangıç
durumunu ayarlamak için kullanılan özel bir metottur.
Overloading (Aşırı Yükleme): Bir sınıf içinde aynı isimde fakat farklı parametre imzalarına sahip
birden fazla metot (veya kurucu metot) tanımlama işlemidir.
UML (Unified Modeling Language): Birleşik Modelleme Dili. Yazılım sistemlerinin tasarımını
görselleştirmek ve standartlaştırmak için kullanılan grafiksel bir dildir.
Encapsulation (Kapsülleme / Veri Saklama): Nesnenin verilerini (private nitelikler) gizleyip bu
verilere sadece kendi public metotları aracılığıyla kontrollü erişim izni verme prensibidir.
Getter (Alıcı Metot): Bir nesnenin private bir niteliğinin değerini okuyup döndüren public metottur
(örneğin, getName()).
Setter (Ayarlayıcı Metot): Bir nesnenin private bir niteliğine değer atayan veya onu değiştiren
public metottur (örneğin, setName(String newName)).
Reference Variable (Referans Değişkeni): Belleğin 'Heap' bölgesindeki bir nesnenin adresini
tutan ve 'Stack' bölgesinde yaşayan değişkendir.
Heap Memory (Yığın Bellek): Program çalışırken nesnelerin (new ile oluşturulan) dinamik
olarak yaratıldığı ve saklandığı bellek alanıdır.

2.4. Şekil/Tablo Yorumları

Referans Değişkeni, Nesneler ve Heap Bellek Şeması: Bu şema, Java'da nesne yönetimini
görselleştirir. dog1, dog2 gibi referans değişkenleri belleğin 'Stack' alanında bulunur ve 'Heap'
alanında oluşturulan gerçek Dog nesnelerinin bellek adreslerini tutarlar. Dog dog3 = dog2;
ataması yapıldığında, dog3 referansı yeni bir nesne oluşturmaz; bunun yerine dog2'nin işaret
ettiği D2 nesnesinin adresini kopyalar. Bu nedenle, [Link] = 12; gibi bir değişiklik yapıldığında,
aynı nesneyi işaret eden dog2 referansı üzerinden de bu değişiklik görülebilir. Dog dog4; ise
sadece bir referans olarak tanımlanmış olup henüz bir nesneyi işaret etmemektedir (null değerine
sahiptir).
UML Sınıf Diyagramı: Account Sınıfı: Bu diyagram, Accountadında bir sınıfı standart UML
formatında modeller:
Üst Bölüm: Sınıfın adı Account olarak belirtilmiştir.
Orta Bölüm: İki adet private (- sembolü) nitelik listelenmiştir: name (String tipinde) ve balance
(double tipinde).
Alt Bölüm: Sınıfın operasyonları (metotları) listelenmiştir. <<constructor>> Account(...)
ifadesi, bunun bir kurucu metot olduğunu belirtir. deposit, getBalance, setName, getName gibi
diğer metotlar public (+ sembolü) erişime sahiptir. Metotların parametreleri (depositAmount:
double) ve geri dönüş tipleri (: double) de açıkça gösterilmiştir.

2.5. Sınav Dikkat Noktası

Bu haftanın en kritik sınav konusunu Kapsülleme (Encapsulation) ilkesi oluşturur. Sınava


hazırlanırken kendinize şu soruyu sorun ve kapsamlı bir şekilde cevaplayın: "Bir sınıfın niteliklerini
neden private yaparız ve bu niteliklere neden public get/set metotları ile erişiriz?" Cevabınızın
anahtarı sadece "veri gizleme" değil, aynı zamanda "veri üzerinde tam kontrol sağlama" olmalıdır.
set metodu içine yazılacak bir if koşulu ile geçersiz verilerin (örneğin, negatif bir bakiye veya boş bir
isim) nesneye atanmasını engelleme yeteneğini örneklerle açıklayabilmelisiniz. Ayrıca, kurucu
metotların geri dönüş tipi olmaması ve geliştirici tarafından bir kurucu yazıldığında varsayılan
kurucunun iptal olması gibi detaylar, potansiyel 'tuzak' sorular için önemlidir.

2.6. Örnekler

Aşağıdaki Account sınıfı, kapsülleme ilkesini, veri doğrulaması yapan bir kurucu metodu ve get/set
metotlarını göstermektedir.

// Fig. 3.8: [Link]

public class Account {

private String name; // instance variable (private yapıldı)

private double balance; // instance variable (private yapıldı)


// İki parametre alan ve başlangıç değerlerini atayan kurucu metot

public Account(String name, double balance) {

[Link] = name; // Gelen 'name' parametresini sınıfın 'name' niteliğine ata

// Gelen bakiye 0.0'dan büyükse ata, değilse varsayılan 0.0 değeri kalır

if (balance > 0.0) {

[Link] = balance;

// Para yatırma metodu (sadece pozitif değerleri kabul eder)

public void deposit(double depositAmount) {

if (depositAmount > 0.0) { // Veri kontrolü

balance = balance + depositAmount;

// Bakiye bilgisini güvenli bir şekilde dışarıya döndüren get metodu

public double getBalance() {

return balance;

// İsim bilgisini güncelleyen set metodu

public void setName(String name) {

[Link] = name;

}
// İsim bilgisini dışarıya döndüren get metodu

public String getName() {

return name;

AccountTest sınıfı ise Account sınıfından nesneler oluşturarak bu sınıfın doğru çalışıp çalışmadığını
test eder. Özellikle negatif bakiye ile nesne oluşturma denemesinin nasıl sonuçlandığına dikkat
ediniz.

// Fig. 3.9: [Link]

import [Link];

public class AccountTest {

public static void main(String[] args) {

// Kurucu metotları kullanarak iki Account nesnesi oluşturuluyor

Account account1 = new Account("Jane Green", 50.00);

Account account2 = new Account("John Blue", -7.53); // Negatif bakiye kurucudaki if koşulu
tarafından engellenecek

// Başlangıç bakiyeleri gösteriliyor

[Link]("%s balance: $%.2f%n", [Link](), [Link]());

[Link]("%s balance: $%.2f%n", [Link](), [Link]()); // Çıktı:


$0.00 olacak

// Kullanıcıdan girdi alarak para yatırma işlemi yapılıyor

Scanner input = new Scanner([Link]);

[Link]("Enter deposit amount for account1: ");

double depositAmount = [Link]();

[Link](depositAmount); // deposit metodu çağrılıyor


// Son bakiyeler gösteriliyor

[Link]("%s balance: $%.2f%n", [Link](), [Link]());

[Link]("%s balance: $%.2f%n", [Link](), [Link]());

--------------------------------------------------------------------------------

3. HAFTA 4: Sınıflarla Çalışma - İstisna Yönetimi ve static Anahtar


Kelimesi
3.1. Kapsamlı Konu Anlatımı

Programlamada karşılaşılan hatalar temel olarak ikiye ayrılır: derleme zamanı (compile-time) ve
çalışma zamanı (run-time) hataları. Derleme zamanı hataları, kodun sözdizimindeki yanlışlıklardan
kaynaklanırken, çalışma zamanı hataları programın normal akışını bozan beklenmedik olaylardır. Bu
olaylara İstisna (Exception) denir. Yönetilmedikleri takdirde istisnalar programın çökmesine neden
olur.

Java, bu istisnaları yönetmek için güçlü bir try-catch mekanizması sunar. Hata oluşturma potansiyeli
olan kodlar try bloğu içine yazılır. Eğer bu blok içinde bir istisna meydana gelirse, programın normal
akışı durdurulur ve kontrol, o istisna türünü yakalamak için tanımlanmış olan ilgili catch bloğuna
geçer. Bu sayede program çökmeden hatayı yönetebilir. İstisna yönetimi için Java'da beş anahtar
kelime bulunur:

try: Hata riski taşıyan kodu çevreler.


catch: Fırlatılan istisnayı yakalar ve ele alır.
throw: Manuel olarak bir istisna fırlatmak için kullanılır.
throws: Bir metodun hangi istisnaları fırlatabileceğini deklare eder.
finally: try-catch bloğundan sonra, bir hata oluşsa da oluşmasa da her zaman çalıştırılan koddur.
Genellikle dosya kapatma gibi kaynak temizleme işlemleri için kullanılır. try-catch yapısı, basit bir
koşul kontrolü olan if-else'ten farklıdır. if-else beklenen durumları yönetirken, try-catch programın
akışını aniden "kıran" beklenmedik durumları ele almak için tasarlanmıştır.

Haftanın ikinci önemli konusu static anahtar kelimesidir. Bu anahtar kelimenin temel amacı bellek
yönetimidir. static olarak tanımlanan bir alan veya metot, belirli bir nesneye (instance) değil,
doğrudan sınıfın kendisine aittir. Bu şu anlama gelir:

Static Değişken (Sınıf Alanı): Bir sınıftan yüzlerce nesne oluşturulsa bile, static bir değişkenden
bellekte sadece bir tane bulunur. Tüm nesneler bu tek kopyayı paylaşır. Bu, bellekten tasarruf
sağlar ve ortak verileri (örneğin, bir şirketteki tüm çalışanlar için ortak olan şirket adı) tutmak için
idealdir.
Static Metot (Sınıf Metodu): Bir static metodu çağırmak için o sınıftan bir nesne oluşturmak
gerekmez. Doğrudan [Link]() şeklinde çağrılabilir. Ancak önemli bir kısıtlaması vardır:
static metotlar, nesneye özgü olan static olmayan (instance) alanlara veya metotlara doğrudan
erişemezler, çünkü hangi nesneye ait üyeye erişeceklerini bilemezler. Sadece diğer static üyelere
erişebilirler.

Son olarak, final anahtar kelimesi bir değişkenin değerinin sabit (değiştirilemez) olmasını sağlar.
static ile birlikte public static final şeklinde kullanılarak sınıf seviyesinde sabitler oluşturmak yaygın bir
pratiktir.

3.2. Kritik Bilgi Maddeleri

Hata Türleri:
Derleme Zamanı Hataları: Sözdizimi (syntax) hataları, tip uyuşmazlıkları gibi kod derlenirken
tespit edilen hatalardır.
Çalışma Zamanı Hataları (İstisnalar): Program çalışırken ortaya çıkan ve programın normal
akışını bozan hatalardır (örneğin, sıfıra bölme, olmayan bir dosyayı okumaya çalışma).
İstisna Yönetimi Anahtar Kelimeleri:
try: İstisna fırlatma potansiyeli olan kodları içeren bloktur.
catch: try bloğunda fırlatılan istisnayı yakalayan ve ilgili hata yönetimi kodunu içeren bloktur.
throw: Manuel olarak bir istisna nesnesi fırlatmak için kullanılır.
throws: Bir metodun, kendisini çağıran metoda hangi türde istisnalar fırlatabileceğini bildirmek
için metot imzasında kullanılır.
finally: Hata oluşsa da oluşmasa da her durumda çalışan bloktur. Genellikle kaynak
temizleme ("cleanup") kodu için kullanılır.
try/catch vs if/else:
if/else: Programın beklenen, normal akışını ve mantıksal dallanmalarını kontrol etmek için
kullanılır.
try/catch: Programın normal akışını bozan beklenmedik, istisnai durumları (hataları) yönetmek
için kullanılır.
static Anahtar Kelimesi:
Amaç: Bellek yönetimi.
static Değişken (Sınıf Değişkeni): Sınıfa aittir, nesneye değil. Tüm nesneler tarafından
paylaşılan tek bir kopyası vardır. Bellekte sadece bir kez yer kaplar.
static Metot (Sınıf Metodu): Sınıfa aittir. Çağırmak için nesne oluşturmak gerekmez;
[Link]() şeklinde çağrılır. Yalnızca diğer static üyelere (değişken ve metot)
doğrudan erişebilir.
final Anahtar Kelimesi:
Bir değişkenin değerinin atandıktan sonra değiştirilemez (sabit) olmasını sağlar.

3.3. Teknik Terimler ve Tanımlar

Exception (İstisna): Programın çalışması sırasında normal akışı kesintiye uğratan bir olay veya
hatadır.
Exception Handling (İstisna Yönetimi): Çalışma zamanı hatalarını try-catch gibi
mekanizmalarla yöneterek programın çökmesini önleme tekniğidir.
try bloğu: Hata fırlatma olasılığı olan kodların yazıldığı bloktur.
catch bloğu: Belirli bir türdeki istisnayı yakalayan ve bu hata durumunda çalıştırılacak kodu
içeren bloktur.
finally bloğu: try-catch yapısından sonra, hata olsa da olmasa da mutlaka çalışan bloktur.
static: Bir üyenin (alan veya metot) nesneye değil, doğrudan ait olduğu sınıfa bağlı olduğunu
belirten anahtar kelimedir.
final: Bir değişkenin değerini sabit (constant) yapan, yani bir kez atandıktan sonra
değiştirilmesini engelleyen anahtar kelimedir.

3.4. Şekil/Tablo Yorumları

try-catch Akış Şeması: Bu şema, istisna yönetiminin mantığını gösterir. try bloğu içindeki kod
çalışırken bir "Runtime error" meydana gelir. Bu hata, bir "exception object" (istisna nesnesi)
fırlatır. Fırlatılan bu nesne, programın akışını uygun tipteki catch bloğuna yönlendirir. catch bloğu
istisnayı yakalar ve içindeki hata yönetimi kodunu çalıştırır, böylece programın çökmesi
engellenir.
Static Değişken Bellek Şeması: Bu şema, static bir değişkenin bellekteki yerini açıklar. Circle
sınıfından oluşturulan a ve b nesnelerinin kendilerine ait, static olmayan x ve y değişkenleri Heap
bellekte ayrı ayrı tutulur. Ancak z değişkeni static olduğu için, Heap'in özel bir alanı olan
"Permanent Generation area"da (veya modern JVM'lerde Metaspace'de) tek bir kopya olarak
tutulur. Hem a nesnesi hem de b nesnesi aynı z değişkenine erişir ve onu paylaşır. Bu, z'nin
nesneye değil, sınıfa ait olduğunun görsel bir kanıtıdır.

3.5. Sınav Dikkat Noktası

Bu konuya çalışırken, static ve static olmayan (instance) üyeler arasındaki temel farka odaklanın.
Bir üyenin static olmasının ne anlama geldiğini (sınıfa ait olma, bellekte tek kopya, nesne
oluşturmadan erişim) ve bunun bellek yönetimi üzerindeki etkisini tam olarak kavramanız gerekir.
Sınav pratiği olarak, "Bir metot ne zaman static yapılmalıdır?" sorusuna net bir cevap oluşturun.
Ayrıca, "Bir static metot içinden static olmayan bir alana neden doğrudan erişilemez?"
sorusunun mantığını anladığınızdan emin olun. Cevap basittir: static bir metot, belirli bir nesneye
bağlı değildir, bu yüzden hangi nesnenin static olmayan alanına erişeceğini bilemez. Bu temel mantığı
kavramak, sınavdaki soyut soruları cevaplamanıza yardımcı olacaktır.

3.6. Örnekler

Aşağıdaki kod parçası, kullanıcıdan alınan ikinci değerin sıfır olması durumunda ArithmeticException
fırlatma potansiyeli taşır.

// İstisna oluşturma potansiyeli olan kod

[Link]("Enter a value");

int value1 = [Link]();

[Link]("Enter another value");

int value2 = [Link]();

[Link](value1 / value2); // Eğer value2 0 ise ArithmeticException fırlatılır.


Bu istisna, aşağıdaki try-catch bloğu ile yönetilerek programın çökmesi engellenir.

// try-catch bloğu ile istisnanın yönetilmesi

try {

[Link]("Enter a value");

int value1 = [Link]();

[Link]("Enter another value");

int value2 = [Link]();

[Link](value1 / value2);

} catch (Exception e) {

// Hata yakalandığında bu blok çalışır ve program çökmez.

[Link]("you cannot divide by zero");

// Hata hakkında daha detaylı bilgi almak için:

// [Link]([Link]());

Bu Employee sınıfı, static değişkenlerin ve metotların kullanımını gösterir. companyName ve number


alanları tüm Employee nesneleri tarafından paylaşılır.

public class Employee {

// static değişken, tüm Employee nesneleri için ortak olan şirket adını tutar.

public static String companyName = "MNG";

// static olmayan (instance) değişkenler, her nesne için ayrıdır.

private int id;

private String name;

// static sayaç, oluşturulan toplam nesne sayısını tutar.

static int number;

public Employee(int id, String name) {


[Link] = id;

[Link] = name;

number++; // Her yeni nesne oluşturulduğunda ortak sayaç bir artar.

public void getInfo() {

// static değişkene Sınıf Adı.değişken Adı şeklinde erişim önerilir.

[Link]([Link] + " " + [Link] + " " + [Link]);

--------------------------------------------------------------------------------

4. HAFTA 6: Sınıfların Derinlikleri - this() & toString()


4.1. Kapsamlı Konu Anlatımı

Önceki haftalarda static metotlar ve enum tipleri gibi konuları ele almıştık. Bu hafta, sınıf içi kod
tekrarını azaltan ve nesnelerin okunabilirliğini artıran iki önemli tekniğe odaklanacağız: this() kurucu
metot çağrısı ve toString() metodunun ezilmesi.

this() Kullanımı: Bir sınıf içinde birden fazla aşırı yüklenmiş (overloaded) kurucu metot (constructor)
tanımlandığında, genellikle bu metotlar arasında ortak kodlar bulunur. Bu kod tekrarını önlemek için,
bir kurucu metottan aynı sınıfın başka bir kurucu metodunu çağırmak mümkündür. Bu işlem this()
ifadesi ile yapılır. Örneğin, parametresiz bir kurucu, varsayılan değerler belirleyerek daha fazla
parametre alan başka bir kurucuyu this() ile çağırabilir. Bu tekniğin en önemli kuralı, this() çağrısının
bir kurucu metot içindeki mutlaka ilk ifade olması gerektiğidir.

toString() Metodu: Java'da oluşturulan her sınıf, dolaylı olarak Object sınıfından miras alır. Object
sınıfı, tüm nesnelerin kullanabileceği bazı temel metotlar içerir ve bunlardan biri de toString()
metodudur. Varsayılan olarak bu metot, nesnenin sınıf adını ve bellekteki adresini temsil eden
anlamsız bir metin (örneğin, Student@15db9742) döndürür. Ancak, bu metodu sınıfımız içinde ezerek
(override ederek), nesnenin durumunu (içerdiği niteliklerin değerlerini) yansıtan daha anlamlı bir
metin döndürmesini sağlayabiliriz. Örneğin, bir Student nesnesi için toString() metodu, öğrencinin
ID'sini ve adını içeren bir metin döndürebilir. [Link]() metoduna bir nesne parametre
olarak verildiğinde, Java arka planda otomatik olarak o nesnenin toString() metodunu çağırır ve
dönen metni ekrana yazdırır. Bu, hata ayıklama ve loglama için son derece kullanışlıdır.

Çöp Toplama (Garbage Collection): C/C++ gibi dillerde, programcıların bellek ayırma (malloc) ve
serbest bırakma (free) işlemlerini manuel olarak yönetmesi gerekir. Bu, "kaynak sızıntıları" (resource
leaks) gibi ciddi hatalara yol açabilir. Java'da ise bellek yönetimi JVM tarafından otomatik olarak
yapılır. Bir nesneye artık hiçbir referans değişkeni işaret etmediğinde, o nesne "ulaşılamaz" veya
"çöp" olarak kabul edilir. JVM'nin Çöp Toplayıcısı (Garbage Collector), periyodik olarak çalışarak bu
ulaşılamaz nesneleri tespit eder ve kapladıkları bellek alanını sisteme geri kazandırır. Bu süreç,
programcının bellek yönetimi yükünü ortadan kaldırır. finalize() metodu, bir nesne çöp toplayıcı
tarafından yok edilmeden hemen önce çağrılan bir metottur. Ancak çağrılma zamanı belirsiz olduğu
ve güvenilir olmadığı için modern Java programcılığında kullanımından kesinlikle kaçınılmalıdır.

4.2. Kritik Bilgi Maddeleri

this() Kurucu Metot Çağrısı:


Amaç: Bir sınıf içindeki bir kurucudan, aynı sınıfın başka bir kurucusunu çağırmak.
Fayda: Aşırı yüklenmiş kurucularda kod tekrarını önemli ölçüde azaltır.
Kural: this() çağrısı, bir kurucu metot gövdesinin ilk satırı olmak zorundadır.
toString() Metodu:
Object sınıfında tanımlıdır ve bu nedenle tüm Java nesneleri tarafından miras alınır.
Amaç: Nesnenin durumunu yansıtan, okunabilir bir metinsel temsilini (String) döndürmek.
[Link](nesne); komutu, dolaylı olarak [Link]() metodunu çağırır.
Varsayılan davranışını değiştirmek ve anlamlı bir çıktı (örneğin, "ID: 101, İsim: Jack") üretmek
için bu metot sınıfta ezilmelidir (@Override).
Garbage Collection (Çöp Toplama):
JVM, artık referans edilmeyen (ulaşılamaz) nesneleri bellekten otomatik olarak temizler.
Programcının manuel olarak bellek boşaltmasına gerek yoktur, bu da OutOfMemoryError gibi
hataların önlenmesine yardımcı olur.
finalize() metodu, nesne yok edilmeden önce çağrılır ancak güvenilir bir mekanizma değildir
ve kullanılmamalıdır.

4.3. Teknik Terimler ve Tanımlar

this(): Bir kurucu metot içinden, aynı sınıfa ait başka bir kurucu metodu çağırmak için kullanılan
özel bir ifadedir.
toString(): Bir nesnenin String (metin) temsilini döndüren, Object sınıfından miras alınan bir
metottur.
Method Overriding (Metot Ezme): Alt sınıfın, üst sınıftan miras aldığı bir metodun gövdesini
kendi ihtiyaçlarına göre yeniden yazarak davranışını değiştirmesidir (toString() bu şekilde
kullanılır).
Garbage Collection (Çöp Toplama): Bellekte artık referans edilmeyen nesnelerin JVM
tarafından otomatik olarak tespit edilip silinerek belleğin geri kazanılması sürecidir.
Resource Leak (Kaynak Sızıntısı): Artık kullanılmayan bellek veya diğer sistem kaynaklarının
(dosya tanıtıcıları, ağ bağlantıları vb.) programa geri iade edilmemesi durumudur.

4.4. Şekil/Tablo Yorumları

Kütüphane Örneği UML Diyagramı:Bu diyagram, bir kütüphane sisteminin sınıfları arasındaki
ilişkileri modeller:
Association (İlişki): Book ve Author sınıfları arasında bir "has-a" (sahiptir) ilişkisi vardır. Her
kitap bir yazara sahiptir.
Aggregation/Composition (Birleştirme/İçerme): Library ve Book sınıfları arasında bir
"contains" (içerir) ilişkisi vardır. Bir kütüphane, içinde birden çok kitap barındırır.
MainClass, bu sınıflardan nesneler oluşturarak sistemi çalıştıran sürücü (driver) sınıftır.
Taş-Kağıt-Makas Oyunu UML Diyagramı:Bu diyagram, oyunun nesne yönelimli yapısını
görselleştirir:
HandSign bir enumeration (enum) tipidir ve ROCK, SCISSOR, PAPER gibi sabit değerleri
içerir.
Gamer sınıfı, oyuncunun adını, seçimini (HandSign) ve static olarak tutulan
kazanma/kaybetme/beraberlik istatistiklerini barındırır.
StatMaker sınıfı, oyunun mantığını (processMoves) ve skorları gösterme (displayScores) gibi
static yardımcı metotlar içerir. Bu, bu sınıfın bir nesneye ihtiyaç duymadan kullanılabilen bir
araç kutusu olduğunu gösterir.
Arena sınıfı, iki Gamer nesnesini yönetir ve startGame metoduyla oyun döngüsünü kontrol
eder.
RockPaperScissorGame, main metodunu içeren ve oyunu başlatan ana sınıftır.

4.5. Sınav Dikkat Noktası

Sınav pratiği olarak, this() kullanarak kurucu metotları zincirleme (constructor chaining) tekniğini
uygulamalısınız. Kod tekrarı içeren birden fazla kurucu metoda sahip bir sınıf örneği alıp, bu sınıfı
this() kullanarak daha verimli ve bakımı kolay bir hale getirmeyi deneyin. Ayrıca, "this() çağrısı
neden kurucudaki ilk ifade olmalıdır?" sorusunun mantığını tam olarak anladığınızdan emin olun.
Cevap şudur: Çünkü bir nesnenin temel kurulum işlemleri (üst sınıf kurucusunun çağrılması ve
niteliklerin atanması) diğer tüm işlemlerden önce tamamlanmalıdır ve başka bir kurucuyu çağırmak bu
temel kurulumun ayrılmaz bir parçasıdır.

4.6. Örnekler

Rectangle sınıfı, this() kullanarak kurucu metotların birbirini nasıl çağırabildiğini ve kod tekrarını nasıl
önlediğini gösterir.

public class Rectangle {

private int x, y;

private int width, height;

// Parametresiz kurucu, en detaylı kurucuyu varsayılan değerlerle (0,0,1,1) çağırır.

public Rectangle() {

this(0, 0, 1, 1);

// İki parametreli kurucu, yine en detaylı kurucuyu çağırır.

public Rectangle(int width, int height) {


this(0, 0, width, height);

// Tüm nitelikleri atayan ana kurucu. Asıl iş burada yapılır.

public Rectangle(int x, int y, int width, int height) {

this.x = x;

this.y = y;

[Link] = width;

[Link] = height;

Aşağıdaki Student sınıfı, Object sınıfından gelen toString() metodunun nasıl ezildiğini (override) ve bir
nesne [Link] ile yazdırıldığında nasıl anlamlı bir çıktı ürettiğini göstermektedir.

class Student {

int rollno;

String name;

String city;

Student(int rollno, String name, String city) {

[Link] = rollno;

[Link] = name;

[Link] = city;

// Object sınıfından gelen toString() metodunu eziyoruz (override).

// Nesnenin anlamlı bir metin temsilini döndürüyoruz.

@Override

public String toString() {


return rollno + " " + name + " " + city;

public static void main(String args[]) {

Student s1 = new Student(101, "Jack", "Sparrow");

Student s2 = new Student(102, "Johnny", "Cash");

// [Link], [Link]() metodunu otomatik olarak çağırır.

[Link](s1);

// [Link], [Link]() metodunu otomatik olarak çağırır.

[Link](s2);

// BEKLENEN ÇIKTI:

// 101 Jack Sparrow

// 102 Johnny Cash

Taş-Kağıt-Makas oyununun StatMaker sınıfındaki bu metot, oyunun temel mantığını içerir ve static
olduğu için nesne oluşturulmadan doğrudan çağrılabilir.

// Oyun hamlelerini işleyen ve kazananı belirleyen static metot.

static void processMoves(Gamer g1, Gamer g2) {

if ([Link] == null || [Link] == null || [Link]) return;

// Beraberlik durumu

else if ([Link] == [Link]) {

[Link]++;

// Oyuncu 1'in kazanma durumları

else if (([Link] == [Link] && [Link] == [Link]) ||


([Link] == [Link] && [Link] == [Link]) ||

([Link] == [Link] && [Link] == [Link])) {

[Link]++;

// Diğer tüm durumlarda oyuncu 2 kazanır

else {

[Link]++;

[Link]++; // Toplam deneme sayısı artırılır.

--------------------------------------------------------------------------------

5. HAFTA 10: Soyutlama - Arayüzler (Interfaces)


5.1. Kapsamlı Konu Anlatımı

Bu hafta, Nesne Yönelimli Programlamanın temel taşlarından biri olan Soyutlama (Abstraction)
ilkesinin Java'daki en güçlü uygulamalarından biri olan Arayüzler (Interfaces) konusunu ele
alacağız. Bir arayüz, en temel anlamıyla, bir sınıfın sahip olması gereken metotları tanımlayan, ancak
bu metotların nasıl çalışacağını (gövdelerini) belirtmeyen bir "sözleşme" veya "taslak" olarak
düşünülebilir. Bir sınıf, bir arayüzü implements anahtar kelimesi ile uyguladığında, o arayüzde
tanımlanmış olan tüm metotları kendi içinde somut bir şekilde (gövdeleriyle birlikte) yazmayı taahhüt
etmiş olur.

Arayüzlerin temel özellikleri şunlardır:

Kendilerinden new anahtar kelimesi ile doğrudan nesne oluşturulamazlar.


Kurucu metotları (constructor) yoktur.
İçerdikleri metotlar, belirtilmesine gerek olmaksızın, varsayılan olarak public abstract (yani
gövdesiz) kabul edilir.
İçerdikleri değişkenler, varsayılan olarak public static final (yani sabit) kabul edilir.

Arayüzlerin en önemli avantajlarından biri, bir sınıfın birden fazla arayüzü aynı anda
uygulayabilmesidir. Java'da bir sınıf sadece tek bir üst sınıftan kalıtım alabilirken (extends), birden çok
arayüzü implements edebilir. Bu özellik, Java'nın çoklu kalıtım (multiple inheritance) kavramını
güvenli bir şekilde desteklemesini sağlar.

Arayüzler sık sık soyut sınıflar (abstract classes) ile karıştırılır. İkisi arasındaki temel farklar
şunlardır:
Metotlar: Soyut sınıflar hem soyut (gövdesiz) hem de somut (gövdesi olan) metotlar içerebilir.
Arayüzler ise (Java 8 öncesi) sadece soyut metotlar içerebilirdi.
Değişkenler: Soyut sınıflar normal (static olmayan, final olmayan) değişkenlere sahip olabilirken,
arayüzler sadece public static final sabitlere sahip olabilir.
Kalıtım: En kritik fark budur. Bir sınıf sadece bir tane soyut sınıfı extend edebilirken, birden çok
arayüzü implement edebilir.

5.2. Kritik Bilgi Maddeleri

Arayüz (Interface) Tanımı: Bir sınıfın hangi metotlara sahip olacağını zorunlu kılan bir
sözleşmedir. Metotların gövdelerini (nasıl çalışacaklarını) içermez.
Uygulama: Bir sınıf, bir arayüzü implements anahtar kelimesi ile uygular ve arayüzdeki tüm
soyut metotları gövdeleriyle birlikte yazmak zorundadır.
Temel Özellikler:
Arayüzlerden new ile nesne oluşturulamaz (cannot be instantiated).
Kurucu metotları yoktur.
Bir sınıf, birden fazla arayüzü implement edebilir (bu yolla çoklu kalıtım desteklenir).
Bir arayüz, başka bir arayüzü veya arayüzleri extends anahtar kelimesi ile genişletebilir.
İçerdiği değişkenler otomatik olarak public static final (sabit) kabul edilir.
İçerdiği metotlar otomatik olarak public abstract (gövdesiz) kabul edilir.
Arayüz vs. Soyut Sınıf:
Değişkenler: Arayüzler yalnızca sabitler (constants) içerir. Soyut sınıflar hem sabit hem de
normal değişkenler içerebilir.
Metotlar: Arayüzlerde metotların gövdesi yoktur (Java 8 ile gelen default ve static metotlar bu
kuralın istisnalarıdır). Soyut sınıflar hem gövdeli hem de gövdesiz metotlar içerebilir.
Kalıtım: Bir sınıf birden fazla arayüzü implement edebilir, ancak yalnızca bir tane soyut sınıfı
extend edebilir.

5.3. Teknik Terimler ve Tanımlar

Interface (Arayüz): Sadece metot imzaları ve sabitler içeren, sınıflar için bir davranış sözleşmesi
görevi gören tamamen soyut bir yapıdır.
implements: Bir sınıfın bir veya daha fazla arayüzü uygulayacağını ve o arayüzlerin
sözleşmesine uyacağını belirten anahtar kelimedir.
Abstract Method (Soyut Metot): Metot imzası olan ancak gövdesi (uygulama kodu) olmayan
metottur.
Multiple Inheritance (Çoklu Kalıtım): Bir sınıfın birden fazla üst sınıftan kalıtım alarak özellik ve
davranışlarını miras alması yeteneğidir. Java, sınıflar arası çoklu kalıtımı desteklemez, ancak
arayüzler aracılığıyla bu yeteneği dolaylı olarak sağlar.
Abstract Class (Soyut Sınıf): Hem soyut hem de somut metotlar içerebilen ve kendisinden
doğrudan nesne üretilemeyen sınıftır.

5.4. Şekil/Tablo Yorumları

Arayüz ve Soyut Sınıf Karşılaştırma Tablosu:Bu tablo, iki yapı arasındaki temel farkları net bir
şekilde özetler:
Değişkenler (Variable): Arayüzler yalnızca sabitler (Only constants) barındırabilirken, soyut
sınıflar hem sabitler hem de normal değişkenler (Constants and variable data) barındırabilir.
Metotlar (Methods): Arayüzlerdeki metotların uygulaması, yani gövdesi yoktur (No
implementation allowed). Buna karşın soyut sınıflarda hem gövdeli (can include concrete
methods) hem de gövdesiz (can include abstract methods) metotlar bir arada bulunabilir.

5.5. Sınav Dikkat Noktası

Sınavda başarılı olmak için, Arayüz (Interface) ile Soyut Sınıf (Abstract Class) arasındaki farkları
ve hangisinin ne zaman kullanılacağını çok iyi bilmelisiniz. Size verilecek farklı senaryolarda
abstract class mı yoksa interface mi kullanmanız gerektiğini gerekçelendirme alıştırması yapın. Şu
temel ayrımı kullanın:

Eğer sınıflar arasında güçlü bir "bir türüdür" (is-a) ilişkisi varsa ve bu sınıfların ortak kodları
(gövdeli metotları) paylaşması gerekiyorsa abstract class tercih edilir (Örn: Kedi ve Köpek,
Hayvan sınıfının bir türüdür ve ortak bir beslen() metodu olabilir).
Eğer birbiriyle ilgisiz farklı sınıflara belirli bir "yeteneği" (can-do) kazandırmak istiyorsanız
interface kullanılır (Örn: Hem Kuş hem de Uçak sınıfı, Uçabilir yeteneğine sahiptir, bu yetenek bir
arayüz ile tanımlanabilir). Bir sınıfın birden fazla arayüzü uygulayabilmesinin, Java'da çoklu
kalıtım sorununu çözmenin temel yolu olduğu bilgisini aklınızdan çıkarmayın.

5.6. Örnekler

Bir arayüz, interface anahtar kelimesiyle aşağıdaki gibi tanımlanır:

modifier interface InterfaceID {

// Değişkenler (otomatik olarak public static final olur)

// Soyut metotlar (otomatik olarak public abstract olur)

Bir arayüz, başka bir veya birden fazla arayüzü extends anahtar kelimesiyle genişletebilir:

// Bir arayüz, birden fazla arayüzü genişletebilir.

modifier interface InterfaceID extends interface1, interface2, ..., interfaceN {

// Yeni sabitler

// Yeni soyut metotlar

--------------------------------------------------------------------------------

6. HAFTA 11: Java Collections Framework


6.1. Kapsamlı Konu Anlatımı

Bu hafta, Java'da veri gruplarını verimli bir şekilde saklamak, yönetmek ve işlemek için kullanılan
standart ve güçlü bir yapı olan Java Collections Framework konusunu ele alacağız. Joshua Bloch
tarafından geliştirilen bu framework, bir "koleksiyonun" (birden çok elemanı tek bir birimde toplayan
nesne) ne olduğunu ve nasıl yönetileceğini standart bir mimari altında birleştirir.

Framework üç ana bileşenden oluşur:

1. Arayüzler (Interfaces): List, Set, Map gibi soyut veri tiplerini tanımlarlar. Bir koleksiyonun ne gibi
davranışlara sahip olması gerektiğini belirten sözleşmelerdir.
2. Uygulamalar (Implementations): ArrayList, HashSet, HashMap gibi bu arayüzlerin somut,
kullanılabilir veri yapılarıdır. Her biri farklı performans özelliklerine sahip yeniden kullanılabilir
sınıflardır.
3. Algoritmalar (Algorithms): Collections ve Arrays gibi yardımcı sınıflarda bulunan, bu
koleksiyonlar üzerinde sıralama (sort), arama (search) gibi yaygın işlemleri gerçekleştiren static
metotlardır.

Koleksiyonlar hiyerarşisinin kökünde Collection arayüzü yer alır. Bu arayüz, bir koleksiyonun size()
(boyutunu öğrenme), isEmpty() (boş olup olmadığını kontrol etme), add() (eleman ekleme) gibi en
temel metotlarını tanımlar. Koleksiyon içindeki elemanlar arasında gezinmek için ise Iterator arayüzü
kullanılır. Iterator, hasNext() (sırada eleman var mı?), next() (sıradaki elemanı getir) ve remove() (son
getirilen elemanı sil) metotlarıyla koleksiyonu adım adım dolaşma imkanı sunar.

Bu hafta özellikle, sıralı ve tekrarlı elemanlara izin veren List arayüzüne odaklanacağız. List
arayüzünün en popüler iki uygulaması ArrayList ve LinkedList'tir ve aralarında önemli performans
farkları vardır:

ArrayList: Arka planda dinamik olarak yeniden boyutlandırılan bir dizi (array) yapısı kullanır. Bu
yapı, elemanlara indeksiyle rastgele erişimi (get(index)) çok hızlı (O(1)) kılar. Ancak, listenin
ortasına bir eleman eklemek veya silmek, sonraki tüm elemanların kaydırılmasını gerektirdiği için
yavaştır.
LinkedList: Birbirine çift yönlü olarak bağlı düğümlerden (nodes) oluşan bir liste yapısı kullanır.
Bu yapı, araya eleman eklemeyi veya silmeyi çok hızlı hale getirir, çünkü sadece komşu
düğümlerin bağlantılarının güncellenmesi yeterlidir. Ancak, belirli bir indeksteki elemana erişmek
için listenin başından veya sonundan başlayarak o indekse kadar saymak gerektiği için yavaştır.

Son olarak, koleksiyonları kullanırken "ham tipler" (raw types, örn: List list = new ArrayList())
kullanmak tehlikelidir, çünkü derleyici tip kontrolü yapamaz ve bu durum çalışma zamanında
ClassCastException gibi hatalara yol açabilir. Bunun yerine, koleksiyonları her zaman Jenerikler
(Generics) ile (List<String> gibi) kullanmak, derleme zamanında tip güvenliği (type-safety) sağlar ve
programı daha güvenli hale getirir.

6.2. Kritik Bilgi Maddeleri

Java Collections Framework Bileşenleri:


Arayüzler (Interfaces): Koleksiyonların soyut veri tiplerini tanımlar (örn: Collection, List, Set,
Map).
Uygulamalar (Implementations): Arayüzlerin somut, yeniden kullanılabilir veri yapılarıdır
(örn: ArrayList, LinkedList, HashSet).
Algoritmalar (Algorithms): Koleksiyonlar üzerinde arama, sıralama gibi işlemleri
gerçekleştiren metotlardır (Collections ve Arrays yardımcı sınıflarında bulunur).
Collection Arayüzü Temel Metotları: int size(), boolean isEmpty(), boolean contains(Object
element), boolean add(Object element), boolean remove(Object element), Iterator iterator().
Iterator Arayüzü Metotları:
boolean hasNext(): İterasyonda bir sonraki eleman olup olmadığını kontrol eder. true veya
false döndürür.
Object next(): Koleksiyondaki sıradaki elemanı döndürür ve imleci bir sonraki pozisyona
ilerletir.
void remove(): next() metodu tarafından en son döndürülen elemanı koleksiyondan güvenli bir
şekilde siler.
List Arayüzü Özellikleri:
Elemanları eklenme sırasına göre tutar (ordered).
Tekrarlı (duplicate) elemanlara izin verir.
İndeks tabanlı erişim sağlar.
ArrayList vs. LinkedList:
ArrayList: Rastgele erişim (get(index)) çok hızlıdır. Listenin ortasına/başına eleman
ekleme/silme (add(index, ...) veya remove(index)) yavaştır çünkü dizideki elemanların
kaydırılması gerekir.
LinkedList: Sıralı erişim kullanılır. Listenin ortasına/başına eleman ekleme/silme çok hızlıdır
çünkü sadece komşu düğümlerin referansları güncellenir. Rastgele erişim yavaştır.
Jenerikler (Generics): Koleksiyonlarda List<String> list = new ArrayList<>(); gibi kullanılarak tip
güvenliği sağlanır. Bu, koleksiyona yanlış tipte bir veri eklenmesini daha kod yazılırken, derleme
aşamasında engeller.

6.3. Teknik Terimler ve Tanımlar

Collection (Koleksiyon): Birden fazla elemanı (nesneyi) tek bir birim olarak gruplayan bir
nesnedir.
Framework (Çatı): Bir yazılım geliştirmek için standart bir yapı, mimari ve işlevsellik sunan
kütüphaneler ve araçlar bütünüdür.
Iterator (Yineleyici): Bir koleksiyonun elemanları arasında sırayla gezinmek (iterasyon yapmak)
için kullanılan bir nesnedir.
ListIterator: Sadece List arayüzünü uygulayan sınıflar için geçerli olan, hem ileri hem de geri
yönde gezinme ve eleman ekleme imkanı sunan daha gelişmiş bir Iterator türüdür.
Generics (Jenerikler): Sınıfların, arayüzlerin ve metotların farklı veri tipleriyle güvenli bir şekilde
çalışabilmesini sağlayan, derleme zamanında tip kontrolü sunan bir Java özelliğidir.
Type-Safety (Tip Güvenliği): Bir değişkene, metoda veya koleksiyona yalnızca belirtilen tipte
verilerin atanabilmesini ve kullanılabilmesini sağlama durumudur. Jenerikler bunu sağlar.
Random Access (Rastgele Erişim): Bir veri yapısındaki herhangi bir elemana, konumundan
bağımsız olarak sabit bir sürede (O(1)) erişebilme yeteneğidir. ArrayList bu özelliğe sahiptir.
Sequential Access (Sıralı Erişim): Bir veri yapısındaki bir elemana ulaşmak için baştan
başlayarak elemanları sırayla takip etme zorunluluğudur. LinkedList bu şekilde çalışır.

6.4. Şekil/Tablo Yorumları

Collections Framework Diyagramı: Bu diyagram, Java Collections Framework'ün hiyerarşik


yapısını gösterir. En üstte Collection ve Map arayüzleri bulunur. Collection arayüzünden List, Set
gibi daha özelleşmiş arayüzler türer. List arayüzünü ArrayList ve LinkedList sınıfları uygular. Set
arayüzünü HashSet ve TreeSet uygular. Map arayüzünü ise HashMap ve TreeMap uygular.
Ayrıca, elemanlar arasında gezinmek için kullanılan Iterator ve ListIterator arayüzleri de bu
yapıyla yakından ilişkilidir.
ArrayList'e Ekleme Şeması: Bu şema, dolu bir ArrayList'in ortasına (örneğin 2. indekse) yeni bir
eleman (13) eklenmesini görselleştirir. Yeni elemana yer açmak için, 2. indeksten sonraki tüm
elemanların (14, 15, 16) birer pozisyon sağa kaydırılması gerekir. Bu kaydırma işlemi, ArrayList'te
araya eleman eklemenin neden maliyetli (yavaş) olduğunu açıklar.
LinkedList'e Ekleme Şeması: Bu şema, bir LinkedList'in arasına yeni bir düğüm (2) eklenmesini
gösterir. Bu işlem için diziyi kaydırmak yerine sadece komşu düğümlerin (1 ve 3) işaretçileri
(referansları) güncellenir. 1'in sonraki işareti yeni düğüm 2'yi, 3'ün önceki işareti yine 2'yi
gösterecek şekilde ayarlanır. Listenin geri kalanına dokunulmadığı için bu ekleme işlemi çok
hızlıdır.
get() ve set() Metot Şemaları (ArrayList):
get(1) şeması, ArrayList'in 1. indeksindeki "Black" değerine, dizinin doğası gereği, doğrudan
ve anında erişilip döndürüldüğünü gösterir. Bu işlem çok hızlıdır (rastgele erişim).
set(2, "Violet") şeması, 2. indeksteki "Red" değerinin doğrudan "Violet" değeri ile
değiştirildiğini gösterir. Bu işlem de yine indeks tabanlı olduğu için çok hızlıdır.

6.5. Sınav Dikkat Noktası

Sınava hazırlanırken, ArrayList ile LinkedList arasındaki performans farklarını ve kullanım


senaryolarını analiz etme pratiği yapın. Farklı senaryolar düşünün (örneğin, "bir sözlük
uygulamasında kelimeleri sıralı tutmak" veya "bir müzik çalar listesinde şarkıları sürekli araya
eklemek") ve her senaryo için ArrayList ile LinkedList arasındaki performans farklarını analiz ederek
hangisinin daha uygun olacağını belirleyin. Bu iki veri yapısının temel çalışma mantığını (dizi tabanlı
vs. bağlı liste) ve bunun getirdiği performans avantaj/dezavantajlarını anlamak esastır. Iterator'ın, bir
koleksiyon üzerinde döngü yaparken güvenli bir şekilde eleman silmek ([Link]()) için neden
kullanılması gerektiği de bir diğer önemli noktadır.

6.6. Örnekler

Bu örnek, ham tip (raw type) kullanımının tehlikesini göstermektedir. Koleksiyona farklı tiplerde veriler
eklenebilmekte, bu da çalışma zamanı hatalarına yol açabilmektedir. Bu nedenle jenerik kullanımı
önerilir.

public class SimpleCollection {

public static void main(String[] args) {

// Ham tip (raw type) kullanımı - önerilmez! Tip güvenliği yoktur.

Collection c = new ArrayList();

// Koleksiyona farklı tiplerde veriler eklenebiliyor, bu da hatalara yol açabilir.

[Link](5); // Integer
[Link]("hello world"); // String

[Link](new FullTimeEmployee("jack", 10)); // FullTimeEmployee nesnesi

// Iterator ile elemanları yazdırma

Iterator iter = [Link]();

while ([Link]()) {

[Link]([Link]());

// Bu kullanım çalışma zamanında ClassCastException gibi hatalara neden olabilir.

// Bunun yerine jenerikler kullanılmalıdır: Collection<String> c = new ArrayList<>();

Bu örnek, ListIterator arayüzünün List üzerinde hem ileri hem de geri yönde nasıl gezinme imkanı
sunduğunu göstermektedir.

public class ListIteratorExample {

public static void main(String[] args) {

LinkedList<String> linkedlist = new LinkedList<String>();

[Link]("Abant");

[Link]("Mengen");

[Link]("Gerede");

// ListIterator nesnesi alınıyor.

ListIterator<String> listIt = [Link]();

[Link]("Forward iteration (İleri Yönlü Gezinme):");

while ([Link]()) {

[Link]([Link]());

}
[Link]("\nBackward iteration (Geri Yönlü Gezinme):");

// İmleç listenin sonunda olduğu için şimdi geri doğru gezinebiliriz.

while ([Link]()) {

[Link]([Link]());

--------------------------------------------------------------------------------

7. HAFTA 14: Sınıf Tasarımı Analizi (UML Örneği)


7.1. Kapsamlı Konu Anlatımı

Bu haftanın materyali, nesne yönelimli bir sistemde sınıf tasarımının nasıl standart bir dille
modellendiğini gösteren pratik bir UML diyagramı analizidir. Verilen materyal, belirli bir tasarım
prensibinden ziyade, gerçek dünya senaryosuna dayalı bir Order (Sipariş) sınıfının yapısına
odaklanmaktadır. Bu UML diyagramı, bir sınıfın sorumluluklarını, sahip olduğu verileri (nitelikler) ve
gerçekleştirebildiği eylemleri (metotlar) nasıl gösterdiğini anlamak için mükemmel bir örnektir.

Diyagramı incelediğimizde, Order sınıfının niteliklerinin (orderID, items, discountable) private (-


sembolü) olarak tanımlandığını görüyoruz. Bu, kapsülleme (encapsulation) ilkesinin doğru bir
şekilde uygulandığını ve sınıfın iç durumunun dış müdahalelerden korunduğunu gösterir.

Metotların erişim belirleyicileri ise farklı amaçlara hizmet eder. getItems, calculateTotalPrice gibi
metotların public (+ sembolü) olması, bu metotların sınıfın dış dünyaya sunduğu arayüzün (API) bir
parçası olduğunu ve diğer sınıflar tarafından serbestçe çağrılabileceğini belirtir. Buna karşın, addItem
metodunun default (~ sembolü) erişim seviyesine sahip olması, bu metodun muhtemelen sadece aynı
paket içindeki diğer sınıflar (örneğin bir OrderManager sınıfı) tarafından kullanılması amaçlandığını
düşündürür. Bu, tasarımcının sınıfın kullanımını nasıl kontrol etmek istediğine dair önemli bir ipucu
verir.

7.2. Kritik Bilgi Maddeleri

Bu bölümde, Order sınıfı UML diyagramından çıkarılan temel bilgiler listelenmiştir:

Sınıf Adı: Order


Nitelikler (Attributes):
- String orderID: Siparişin benzersiz kimliği, private olarak tanımlanmıştır.
- List<Product> items: Siparişteki ürünlerin bir listesi, private. Bu, Order ve Product sınıfları
arasında bir "içerme" ilişkisi olduğunu gösterir.
- Discountable discountable: Siparişe uygulanacak indirimi temsil eden bir nesne, private.
Discountable tipinin bir arayüz olması muhtemeldir. Bu, farklı indirim stratejilerinin (örneğin,
yüzdelik indirim, sabit indirim) Order sınıfını değiştirmeden uygulanmasına olanak tanır ve
Strateji Tasarım Deseni'ne bir gönderme olabilir. Bu tasarım, sisteme yeni indirim türleri
(örneğin sezon sonu indirimi, kupon indirimi) eklemeyi Order sınıfını hiç değiştirmeden
mümkün kıldığı için oldukça esnek ve genişletilebilirdir.
Metotlar (Behaviors / Operations):
~ void addItem(Product): Siparişe yeni bir ürün ekler. default (package-private) erişim
seviyesindedir.
+ List<Product> getItems(): Siparişteki ürün listesini döndürür. public erişimlidir.
+ double calculateTotalPrice(): Siparişin indirimler dahil toplam tutarını hesaplar. public
erişimlidir.
+ void print(): Sipariş bilgilerini (fatura vb.) yazdırır. public erişimlidir.
+ void email(String): Siparişle ilgili bir e-posta gönderir. public erişimlidir.

7.3. Teknik Terimler ve Tanımlar

UML Class Diagram (UML Sınıf Diyagramı): Bir sistemdeki sınıfları, bu sınıfların niteliklerini
(değişkenlerini), metotlarını (operasyonlarını) ve aralarındaki ilişkileri (kalıtım, ilişki, içerme vb.)
görselleştirmek için kullanılan standart bir diyagram türüdür.
Access Specifier Symbols (Erişim Belirteci Sembolleri):UML sınıf diyagramlarında üyelerin
görünürlüğünü (erişim seviyesini) belirtmek için kullanılan sembollerdir:
+: public (her yerden erişilebilir)
-: private (sadece sınıf içinden erişilebilir)
~: default / package-private (sadece aynı paketten erişilebilir)
#: protected (aynı paket veya alt sınıflardan erişilebilir)

7.4. Şekil/Tablo Yorumları

Order Sınıfı UML Diyagramı Analizi: Diyagram, bir e-ticaret sistemindeki siparişleri temsil eden
Order isimli bir sınıfı göstermektedir. Sınıfın üç adet private (- işareti) niteliği vardır: orderID
(String), items (List<Product>) ve discountable (Discountable tipinde). Bu yapı, her sipariş
nesnesinin bir kimliğe, bir ürün listesine ve bir indirim mekanizmasına sahip olduğunu açıkça
belirtir. Sınıfın beş adet metodu bulunmaktadır. addItem metodu, pakete özel (~ işareti) erişime
sahiptir, bu da ürün ekleme işleminin kontrollü bir şekilde yapıldığını ima eder. Diğer dört metot
(getItems, calculateTotalPrice, print, email) ise public (+ işareti) erişime sahiptir ve bu sınıfın dış
dünyaya açılan temel işlevlerini temsil eder: ürün listesini alma, toplam fiyatı hesaplama, siparişi
yazdırma ve e-posta gönderme.

7.5. Sınav Dikkat Noktası

Bu konuyla ilgili en iyi sınav hazırlığı, verilen bir UML sınıf diyagramını Java koduna dönüştürme
ve tam tersi, verilen bir Java sınıfı için UML diyagramı çizme pratiği yapmaktır. Order
diyagramındaki - List<Product> items gibi ifadelerin sınıflar arası ilişkileri gösterdiğini unutmayın.
Özellikle erişim belirleyicilerini (+, -, ~) ve metot imzalarını (parametreler, geri dönüş tipleri) doğru bir
şekilde yorumlayıp koda aktarabildiğinizden emin olun.

7.6. Örnekler
--------------------------------------------------------------------------------

HAFTA 8: Nesne Yönelimli Programlama


Kavramları: Kalıtım (Inheritance)
Giriş Paragrafı

Kalıtım (Inheritance), nesne yönelimli programlamanın temel direklerinden biridir. Sınıflar arasında
hiyerarşik ilişkiler kurarak kodun yeniden kullanılabilirliğini ve mantıksal yapısını güçlendirir. Bu
mekanizma, mevcut bir sınıftan yeni sınıflar türetilmesine olanak tanır; böylece alt sınıflar
(subclasses), üst sınıfın (superclass) özelliklerini ve davranışlarını miras alır. Bu hafta, kalıtımın temel
kavramlarını, kullanım amaçlarını ve Java dilindeki uygulamalarını detaylı bir şekilde ele alacağız.

Kapsamlı Konu Anlatımı

Nesne yönelimli programlamada, benzer özelliklere ve davranışlara sahip farklı sınıflar oluşturmak
yaygın bir durumdur. Örneğin, bir Dog ve bir Cat sınıfı düşünelim. Her ikisi de color (renk) gibi ortak
bir özelliğe ve eat() (yemek ye) gibi ortak bir davranışa sahiptir. Bu ortak özellikleri her iki sınıfta da
ayrı ayrı tanımlamak, kod tekrarına yol açar. Kalıtım, bu sorunu ortak özellikleri ve davranışları bir üst
sınıfta, örneğin bir Animal sınıfında toplayarak çözer. Dog ve Cat sınıfları daha sonra bu Animal
sınıfından türetilir ve ortak kodları miras alır.

Kalıtım, temel olarak sınıflar arasında bir "ebeveyn-çocuk" ilişkisi kurar. Bu ilişkideki terminoloji
şöyledir:

Superclass (Üst Sınıf): Kendi özelliklerini ve metotlarını paylaşan ana sınıftır. Base class veya
parent class olarak da adlandırılır.
Subclass (Alt Sınıf): Üst sınıftan özellik ve metotları miras alan türetilmiş sınıftır. Derived class
veya child class olarak da bilinir.

Java'da kalıtımın iki temel kullanım amacı vardır:

1. Kodun Yeniden Kullanılabilirliği (Code Reusability): Alt sınıflar, üst sınıfta tanımlanan alanları
ve metotları yeniden yazmaya gerek kalmadan kullanabilir. Bu, kod tekrarını azaltır ve bakımı
kolaylaştırır.
2. Metot Geçersiz Kılma (Method Overriding): Alt sınıflar, üst sınıftan miras aldıkları bir metodun
kendi ihtiyaçlarına özel bir versiyonunu oluşturabilir. Bu, bir alt sınıfın üst sınıftan aldığı bir
metodu kendi ihtiyaçlarına göre yeniden şekillendirmesine olanak tanır ve bir sonraki hafta
inceleyeceğimiz Polimorfizm (çok biçimlilik) kavramının temelini oluşturur.

Java'da desteklenen kalıtım türleri şunlardır:

Tekli Kalıtım (Single Inheritance): Bir sınıfın yalnızca bir üst sınıftan türemesi.
Çok Seviyeli Kalıtım (Multilevel Inheritance): Bir sınıfın başka bir türetilmiş sınıftan türemesi (A
-> B -> C şeklinde bir zincir).
Hiyerarşik Kalıtım (Hierarchical Inheritance): Birçok alt sınıfın aynı üst sınıftan türemesi.

Not: Java, Çoklu Kalıtım (Multiple Inheritance), yani bir sınıfın birden fazla üst sınıftan türemesini
doğrudan desteklemez. Bu işlevsellik, ilerleyen haftalarda göreceğimiz arayüzler (interfaces)
aracılığıyla sağlanır.

Kalıtım hiyerarşisinde yapıcı metotlar (constructors) özel bir rol oynar. Bir alt sınıfın nesnesi
oluşturulduğunda, üst sınıfın yapıcı metodu da dolaylı olarak çağrılır. Eğer üst sınıfın varsayılan
(parametresiz) bir yapıcısı yoksa, alt sınıfın yapıcısı içinde super() anahtar kelimesi kullanılarak üst
sınıfın yapıcısı açıkça çağrılmalıdır. Bu çağrı, alt sınıf yapıcısının ilk satırı olmak zorundadır.

super anahtar kelimesi, yalnızca yapıcı metotları çağırmak için değil, aynı zamanda üst sınıfın
metotlarına ve değişkenlerine erişmek için de kullanılır. Özellikle alt sınıf ve üst sınıfta aynı isimde bir
değişken veya metot bulunduğunda, [Link]() veya [Link] syntax'ı ile üst
sınıftaki üyeye erişim sağlanır.

Son olarak, kalıtımda erişim belirleyicileri büyük önem taşır. protected belirleyici, bir üyenin kendi
sınıfı, aynı paketteki diğer sınıflar ve farklı paketlerdeki alt sınıfları tarafından erişilebilir olmasını
sağlar. Bu, alt sınıflara kontrollü bir erişim imkanı tanırken, üyenin tamamen public olmasını engeller.
Miras alınan metotların erişim seviyeleri daraltılamaz; örneğin, üst sınıfta public olan bir metot, alt
sınıfta protected veya private yapılamaz.

Kritik Bilgi Maddeleri

Temel NYP Kavramları: Sınıf (Class), Nesne (Object), Kapsülleme (Encapsulation), Kalıtım
(Inheritance), Çok Biçimlilik (Polymorphism), Soyutlama (Abstraction).
Kalıtımın Kullanım Nedenleri: Kodun Yeniden Kullanılabilirliği ve Metot Geçersiz Kılma (Method
Overriding).
super Anahtar Kelimesi Kuralları:
super(argümanlar): Üst sınıfın yapıcı metodunu çağırmak için kullanılır ve yapıcı metodun ilk
satırı olmalıdır.
[Link]ı: Üst sınıfın bir değişkenine erişmek için kullanılır.
[Link]ı(argümanlar): Üst sınıfın bir metodunu çağırmak için kullanılır.
Miras Alınan Metotlarda Erişim Kontrolü:
public metotlar public kalmalıdır.
protected metotlar protected veya public olabilir, ancak private olamaz.
private metotlar miras alınmaz.
Kalıtımın Avantajları: Yeniden kullanılabilirlik, artan güvenilirlik, daha düşük maliyet, standart
arayüz sağlama, kodun genişletilebilirliği.
Kalıtımın Dezavantajları: Normal fonksiyon çağrılarına göre daha yavaş çalışma, yanlış
kullanım potansiyeli, bellek israfı, üst ve alt sınıflar arasında sıkı bir bağ (coupling) oluşturması.

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

"Erişim Belirleyicileri" (Access modifiers) tablosu, Java'daki dört temel erişim seviyesinin
görünürlüğünü özetlemektedir.

public: En geniş erişim seviyesidir ve her yerden (aynı sınıf, aynı paket, farklı paket) erişime izin
verir.
protected: Üyeyi, tanımlandığı sınıfa, aynı paketteki diğer sınıflara ve farklı paketlerdeki alt
sınıflarına görünür kılar. Ancak, farklı bir paketteki alt sınıf olmayan bir sınıftan erişilemez.
default (belirtilmemiş): Üyeyi yalnızca aynı paket içindeki sınıflara görünür kılar. Farklı
paketlerden erişilemez.
private: En kısıtlı erişim seviyesidir ve üyeyi yalnızca tanımlandığı sınıf içinde görünür kılar. Alt
sınıflar bile bu üyelere doğrudan erişemez.

"CommunityMember" sınıf hiyerarşisi diyagramı, hiyerarşik kalıtıma güzel bir örnektir.


CommunityMember en tepedeki üst sınıftır. Employee, Student ve Alumnus sınıfları doğrudan
CommunityMember sınıfından türemiştir. Hiyerarşi daha da derinleşir: Faculty ve Staff sınıfları
Employee sınıfından, Administrator ve Teacher sınıfları ise Faculty sınıfından türemiştir. Bu yapı,
ortak özelliklerin üst sınıflarda toplanarak kodun nasıl mantıksal bir düzende organize edilebileceğini
gösterir.

Vehicle (Araç) üst sınıfı ile Bicycle (Bisiklet) ve Car (Araba) alt sınıflarını gösteren UML sınıf
diyagramı, bir "is a" (bir ...dır) ilişkisini temsil eder. Örneğin, "bir Bisiklet bir Araç'tır" ve "bir Araba bir
Araç'tır". UML'de kalıtım, alt sınıftan üst sınıfa doğru yönelen içi boş bir ok ile gösterilir. Bu, alt sınıfın
üst sınıfın bir uzmanlaşmış hali olduğunu ifade eder.

Sınav Dikkat Noktası

Bu haftanın en kritik sınav konusu, kalıtım bağlamında private ve protected üyeler arasındaki temel
ayrımdır. Bu konu sıklıkla kafa karışıklığına yol açtığı için potansiyel bir sınav sorusudur.
Unutulmamalıdır ki, bir üst sınıfın private üyelerine bir alt sınıf tarafından kesinlikle erişilemez. Bu
üyeler tamamen sınıfın içine özeldir. Buna karşılık, protected üyeler tam da bu amaç için
tasarlanmıştır: alt sınıfların, üst sınıfın iç işleyişine kontrollü bir şekilde erişmesini sağlamak, ancak bu
üyeleri tamamen halka açık (public) yapmamak.
Örnekler

1. Kalıtım Öncesi Dog ve Cat Sınıfları (Kod Tekrarı)

class Dog {

String color;

String breed;

public void bark() {}

public void eat() {}

class Cat {

String color;

int age;

public void meow() {}

public void eat() {}

2. Kalıtım Sonrası Animal Süper Sınıfı ile Yeniden Düzenlenmiş Kod

class Animal {

String color;

public void eat() {}

class Dog extends Animal {

String breed;

public void bark() {}

}
class Cat extends Animal {

int age;

public void meow() {}

3. Yapıcı Metotlar ve super() Kullanımı ile Kalıtım

public class Animal {

public String color;

public Animal(String color) {

[Link] = color;

public void eat() {};

public class Cat extends Animal {

public int age;

public Cat(int age, String color) {

super(color); // Üst sınıfın yapıcısını çağırır

[Link] = age;

public void meow() {};

4. Metot Geçersiz Kılma (Method Overriding) Örneği


public class Company {

public void address() {

[Link]("this is default address");

public class GoogleCompany extends Company {

@Override

public void address() {

[Link]("THIS IS ADDRESS OF GOOGLE");

public class MicrosoftCompany extends Company {

@Override

public void address() {

[Link]("THIS IS ADDRESS OF MICROSOFT");

5. Erişim Belirleyici Problemi (Alıştırma)

public class Parent {

private int attribute1;

protected int attribute2;

public int attribute3;

protected final static int attribute5 = 1;

private void method1() {}


public void method2() {}

protected void setAttribute1(int value){

this.attribute1 = value;

public class Child extends Parent {

public int attribute4;

public Child() { // Hangileri geçerlidir?

attribute4 = 0; // Geçerli

// attribute1++; // Hatalı: attribute1 'private' olduğu için erişilemez.

attribute2++; // Geçerli

attribute3++; // Geçerli

// attribute5++; // Hatalı: attribute5 'final' olduğu için değeri değiştirilemez.

// super.method1(); // Hatalı: method1 'private' olduğu için erişilemez.

method2(); // Geçerli

setAttribute1(attribute4); // Geçerli

Geçiş Cümlesi

Sınıfların birbirinden nasıl miras alabildiğini öğrendiğimize göre, şimdi farklı sınıflara ait nesnelerin
ortak bir üst sınıf nesnesi gibi işlem görmesine olanak tanıyan Çok Biçimlilik (Polymorphism) ilkesini
inceleyeceğiz.

--------------------------------------------------------------------------------

HAFTA 9: Çok Biçimlilik (Polymorphism)


Giriş Paragrafı

Çok biçimlilik (Polymorphism), Nesne Yönelimli Programlamanın temel kavramlarından biridir ve


kelime anlamı olarak "birçok şekle sahip olma" anlamına gelir. Programlamada bu, tek bir arayüzün
(bir değişken, metot veya nesne gibi) farklı temel formları (veri tipleri) temsil etmesine olanak tanır. Bu
sayede, kodda esneklik ve dinamik davranışlar elde edilir, bu da programların daha modüler ve
genişletilebilir olmasını sağlar.

Kapsamlı Konu Anlatımı

Çok biçimlilik, bir değişkenin, metodun veya nesnenin birden fazla form alabilme yeteneğidir. Pratikte
en yaygın kullanımı, bir üst sınıf referansının, o üst sınıftan türetilmiş alt sınıf nesnelerini
tutabilmesidir.

Bu kavramın en güçlü uygulamalarından biri dizilerle çalışırken ortaya çıkar. Ortak bir Animal üst
sınıfından türeyen Cat, Dog ve Horse gibi farklı alt sınıflarımız olduğunu varsayalım. Çok biçimlilik
sayesinde, bu farklı türdeki nesnelerin hepsini Animal türünde bir dizide (Animal[]) saklayabiliriz. Bu,
farklı nesne türlerini tek bir döngü içinde uniform bir şekilde işlememize olanak tanır. Örneğin, dizi
üzerindeki her bir Animal için eat() metodunu çağırabiliriz ve Java, çalışma zamanında nesnenin
gerçek türüne (kedi, köpek veya at) göre doğru eat() metodunu (eğer override edilmişse) yürütecektir.

Çok biçimliliği etkili bir şekilde kullanmak için Referans Türü Dönüşümü (Reference Type Casting)
kavramını anlamak gerekir. İki temel tür dönüşümü vardır:

1. Upcasting (Yukarı Dönüşüm): Bir alt sınıf referansını, üst sınıf referansına dönüştürme işlemidir
(örneğin, Animal animal = new Cat();). Bu genellikle güvenlidir ve Java tarafından otomatik
(örtük) olarak yapılır. Upcasting yapıldığında, nesneye erişim üst sınıfın tanımladığı metot ve
değişkenlerle sınırlanır. Yani, animal referansı üzerinden Cat sınıfına özgü meow() metoduna
doğrudan erişemeyiz.
2. Downcasting (Aşağı Dönüşüm): Bir üst sınıf referansını, alt sınıf referansına dönüştürme
işlemidir (örneğin, Cat cat = (Cat) animal;). Bu işlem, alt sınıfa özgü metotlara ve değişkenlere
erişmek için gereklidir. Ancak downcasting risklidir ve açık bir tür dönüşümü (Cat) gerektirir. Eğer
üst sınıf referansı (animal) aslında dönüştürülmek istenen alt sınıf türünde bir nesne tutmuyorsa
(örneğin, bir Dog nesnesi tutuyorsa ve biz onu Cat'e dönüştürmeye çalışıyorsak), program
çalışma zamanında bir ClassCastException hatası fırlatır.

Özetle, upcasting nesnenin arayüzünü daraltırken, downcasting ise genişletir.

Kritik Bilgi Maddeleri

Çok Biçimlilik (Polymorphism): Bir değişkenin, metodun veya nesnenin birden fazla forma
sahip olabilmesini sağlar.
Dizilerle Çok Biçimlilik: Farklı alt sınıflar, ortak üst sınıfları gibi ele alınabilir, bu da onların tek
bir üst sınıf türündeki dizide saklanmasına olanak tanır (örn. Animal[] animals = {dog, cat,
horse};).
Upcasting (Yukarı Dönüşüm): Bir alt sınıftan üst sınıfa tür dönüşümüdür (örn. Animal animal =
(Animal) cat;). Genellikle güvenlidir ve örtük olarak gerçekleşir. Alt sınıfa özgü üyelere erişimi
kısıtlar.
Downcasting (Aşağı Dönüşüm): Bir üst sınıf referansından alt sınıf türüne tür dönüşümüdür
(örn. ((Cat) animal).meow();). Alt sınıfa özgü üyelere erişimi genişletir ancak açık bir dönüşüm
gerektirir ve eğer nesne hedef türde değilse çalışma zamanında ClassCastException hatasına
neden olabilir.
Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

Ders notlarındaki Animal, Cat ve Dog sınıf ilişkilerini gösteren diyagram, upcasting ve downcasting'in
etkilerini göstermektedir. Bir Cat nesnesini tutan Animal a referansı üzerinden [Link]() çağrısı
geçerlidir, çünkü eat() metodu Animal sınıfında tanımlanmıştır. Ancak [Link]() çağrısı geçersizdir ve
derleme hatası verir, çünkü derleyici a referansının Animal türünde olduğunu bilir ve Animal sınıfında
meow adında bir metot yoktur. Bu metoda erişmek için a referansını (Cat) türüne açıkça
dönüştürmek, yani downcasting yapmak gerekir: ((Cat)a).meow(). Diyagramda ayrıca, eğer a
referansı bir Cat nesnesi tutuyorsa ((Dog)a).bark() gibi bir çağrının neden çalışma zamanı hatasına
(runtime error) yol açacağı gösterilmektedir. Bu işlem derlenir çünkü Dog, Animal'ın bir alt sınıfıdır,
ancak çalışma zamanında Cat nesnesi Dog'a dönüştürülemeyeceği için ClassCastException fırlatılır.

Sınav Dikkat Noktası

Bu haftanın en kritik ve test edilebilir kavramı, tür dönüşümüyle ilgili derleme zamanı (compile-time)
ve çalışma zamanı (run-time) hataları arasındaki farkı anlamaktır. Bir Cat nesnesi tutan Animal a
referansı üzerinde ((Dog)a).bark() gibi yasa dışı bir dönüşüm denemesi, Dog Animal'ın bir alt sınıfı
olduğu için başarıyla derlenir, ancak çalışma zamanında bir ClassCastException fırlatır. Tersine,
[Link]() gibi tür dönüşümü yapmadan alt sınıfa özgü bir metodu çağırmaya çalışmak bir derleme
zamanı hatası ile sonuçlanır, çünkü derleyici yalnızca Animal referans türünde mevcut olan metotları
bilir.

Örnekler

1. Çok Biçimlilik ve Diziler

public static void main(String[] args) {

Dog dog = new Dog();

Cat cat = new Cat();

Horse horse = new Horse();

Animal[] animals = new Animal[]{dog, cat, horse};

//dog, cat, and horse are animals, so we can put them in Animal list

for(Animal animal: animals)

[Link]();

2. Animal ve Cat Sınıfları


public class Animal {

protected String color;

void eat() {

[Link]("animal eating...");

public class Cat extends Animal {

int age;

@Override

void eat() {

[Link]("cat is eating");

void meow() {}

3. Tür Dönüşümü Alıştırmaları

Aşağıdaki ifadelerin derlenip derlenmeyeceğini ve çalışıp çalışmayacağını gözden geçirin:

Cat cat = new Animal(); //??


Animal a = new Cat();
[Link](); //??
((Cat)a).meow(); //??
Object o = new Animal();
[Link](); //??
((Animal)o).eat(); //??
((Cat)o).eat(); //??
((Cat)o).meow(); //??

Geçiş Cümlesi

Kalıtım ve çok biçimlilik "is-a" (bir ...dır) ilişkilerini tanımlarken, bir sonraki konu olan Arayüzler
(Interfaces), "can-do" (yapabilir) ilişkilerini tanımlamak ve tam soyutlamaya ulaşmak için güçlü bir
mekanizma sunacaktır.
--------------------------------------------------------------------------------

HAFTA 10: Arayüzler (Interfaces): Soyutlama,


Çoklu Kalıtım
Giriş Paragrafı

Java'da arayüzler (interfaces), bir sınıf gibi davranan ancak bir sözleşme (contract) niteliği taşıyan
referans tipleridir. Bir arayüz, bir sınıfın uygulaması gereken bir dizi soyut metot tanımlar. Bu sayede,
tam soyutlamaya ulaşmayı sağlar ve Java'nın tür (type) bazında çoklu kalıtımı desteklemesi için bir
mekanizma sunar. Arayüzler, nesnelerin ne yapabileceğini tanımlar, ancak nasıl yapacaklarını
belirtmezler.

Kapsamlı Konu Anlatımı

Bir arayüz, onu uygulayan (implement) bir sınıfın uymak zorunda olduğu bir sözleşmedir. Bu
sözleşme, varsayılan olarak public olan davranışları (metotları) içerir. Arayüzlerin kullanılmasının üç
temel nedeni vardır:

1. Soyutlamaya Ulaşmak: Karmaşık iç uygulama detaylarını gizleyerek sadece nesnenin temel


işlevlerini dış dünyaya sunar.
2. Çoklu Kalıtım İşlevselliğini Desteklemek: Java'da bir sınıf birden fazla üst sınıftan doğrudan
miras alamazken, birden fazla arayüzü uygulayabilir. Bu, bir sınıfın farklı kaynaklardan
davranışlar "miras almasına" olanak tanır.
3. Gevşek Bağlılığa (Loose Coupling) Ulaşmak: Sistemdeki bileşenlerin birbirine olan
bağımlılığını azaltır.

Bir arayüz interface InterfaceID { ... } sözdizimi ile oluşturulur ve interface interfaceID extends
interface1, ... sözdizimi ile diğer arayüzleri genişletebilir.

Arayüzlerin temel özellikleri şunlardır:

Tam Soyutlama: Bir arayüzdeki tüm metotlar gövdesizdir (örtük olarak public ve abstract'tır) ve
tüm değişkenler sabittir (örtük olarak public, static ve final'dır).
Uygulanma: Sınıflar, arayüzleri extends anahtar kelimesiyle değil, implements anahtar
kelimesiyle uygular.
Nesne Oluşturulamama: Arayüzlerden doğrudan new anahtar kelimesiyle nesne oluşturulamaz.
Çoklu Uygulama: Bir sınıf, istediği kadar çok sayıda arayüzü uygulayabilir.

Arayüzler ve Soyut Sınıflar (Abstract Classes) sıkça karşılaştırılır. Temel farklar şunlardır: Soyut
sınıflar hem soyut hem de somut metotlar ve örnek değişkenleri içerebilirken, arayüzler (Java 8
öncesi) yalnızca soyut metotlar ve sabitler içerebilir. Bir sınıf yalnızca bir soyut sınıfı genişletebilirken,
birden fazla arayüzü uygulayabilir.

Soyutlama (Abstraction), yalnızca ilgili verileri gösterip gereksiz ayrıntıları gizleme sürecidir. Hem
soyut sınıflar hem de arayüzler bu amaca hizmet eder.
Gevşek Bağlılık (Loose Coupling), bir sistemin bileşenlerinin birbirlerinin iç işleyişi hakkında çok az
bilgiye sahip olduğu bir tasarım ilkesidir. "Vücudun üzerindeki şapka" analojisinde olduğu gibi, bir
şapkayı vücutta herhangi bir değişiklik yapmadan kolayca çıkarabilirsiniz; bu gevşek bağlılıktır. Eğer
vücudun derisini değiştirmek isteseydiniz, bu vücudun kendisinde de değişiklik gerektirirdi; bu ise sıkı
bağlılıktır (tight coupling). Gevşek bağlılık, kodun değiştirilmesini ve bakımını kolaylaştırır.

Model-View-Controller (MVC) deseni, gevşek bağlı bir mimarinin en iyi örneklerinden biridir. Bu
desende, bileşenler şu şekilde ayrılmıştır:

Model: Uygulamanın verilerini ve iş mantığını içerir.


View (Görünüm): Modeldeki veriyi kullanıcıya gösteren arayüzdür.
Controller (Kontrolcü): Kullanıcıdan gelen girdileri alır, Modeli günceller ve hangi Görünümün
gösterileceğini seçer.

Kritik Bilgi Maddeleri

Sözleşme (Contract): Bir arayüz, bir sınıfın onu uygulayarak kabul ettiği bir sözleşmedir.
Tam Soyutlama: Tüm metotlar örtük olarak public ve abstract; tüm nitelikler örtük olarak public,
static ve final'dır.
Çoklu Uygulama: Bir sınıf, istediği kadar arayüzü implement edebilir.
Nesne Oluşturulamama: Arayüzler doğrudan somutlaştırılamaz (new MyInterface() yasa
dışıdır).
Yapıcı Metot Yoktur: Arayüzler yapıcı metot içermez.
Arayüz vs. Soyut Sınıf: Arayüzler yalnızca sabitlere sahip olabilirken, soyut sınıflar değişkenlere
sahip olabilir. Bir sınıf birçok arayüzü uygulayabilirken, yalnızca bir sınıfı genişletebilir.
Temel Kullanım Alanları: Üç ana kullanım amacı; soyutlamaya ulaşmak, çoklu kalıtımı
desteklemek ve gevşek bağlılığa ulaşmaktır.

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

"Sınıflar ve arayüzler arasındaki ilişki" diyagramı üç temel ilişkiyi göstermektedir:

1. Bir sınıf başka bir sınıfı extends (genişletir).


2. Bir sınıf bir arayüzü implements (uygular).
3. Bir arayüz başka bir arayüzü extends (genişletir).

"Arayüz vs. soyut sınıf" karşılaştırma tablosu, iki yapı arasındaki temel farkları özetlemektedir.
Arayüzler yalnızca sabitler içerebilir ve metotlarının gövdesi olamazken, soyut sınıflar hem sabitlere
hem de değişkenlere, hem soyut hem de somut metotlara sahip olabilir. En önemli farklardan biri, bir
sınıfın yalnızca bir soyut sınıfı genişletebilmesine karşın, birden fazla arayüzü uygulayabilmesidir.

Moveable, Flyable, Swimmable arayüzleriyle çoklu kalıtımı gösteren UML diyagramı, bu yaklaşımın
esnekliğini sergilemektedir. Duck (Ördek) sınıfı her üç arayüzü de uygulayarak hem hareket edebilir,
hem uçabilir hem de yüzebilir. Cat (Kedi) sınıfı yalnızca Swimmable arayüzünü, Eagle (Kartal) ise
yalnızca Flyable arayüzünü uygular. Bu, sınıflara ihtiyaç duydukları yetenekleri modüler bir şekilde
eklemeyi sağlar.
MVC diyagramı, sistemdeki akışı açıklar: Kullanıcı (User), Kontrolcüyü (Controller) kullanır (uses).
Kontrolcü, Model'i yönetir (manipulates). Model güncellendiğinde, bu değişiklikler Görünümü (View)
günceller (updates). Son olarak, Kullanıcı güncellenmiş Görünümü görür (sees).

Sınav Dikkat Noktası

Sınavlarda sıkça karşılaşılan bir soru, Arayüzler (Interfaces) ve Soyut Sınıflar (Abstract Classes)
arasındaki farkları karşılaştırmanızı istemektir. Öğrencinin temel farkları bilmesi gerekir: Bir soyut sınıf
örnek değişkenlerine ve somut metotlara sahip olabilirken, bir arayüz (Java 8'deki default metotlar
öncesi) bunu yapamaz. Bir sınıf yalnızca bir soyut sınıfı genişletebilir, ancak birden fazla arayüzü
uygulayabilir. Bu ayrım, Java'nın soyutlama ve çok biçimlilik elde etme konusundaki tasarım
felsefesinin temelidir.

Örnekler

public interface A {

void a();

public interface B {

void b();

public class Test implements A, B {

public void a() {

// a() metodunun implementasyonu

public void b() {

// b() metodunun implementasyonu

Geçiş Cümlesi

Arayüzler kullanarak nesneler için sözleşmeler tanımlamayı anladığımıza göre, şimdi List ve
Collection gibi arayüzleri yoğun bir şekilde kullanarak nesne gruplarını verimli bir şekilde yöneten
Java Koleksiyonlar Çerçevesi'ni (Java Collections Framework) inceleyeceğiz.
--------------------------------------------------------------------------------

HAFTA 11: Java Koleksiyonları (Collections)


Giriş Paragrafı

Java Koleksiyonlar Çerçevesi (Java Collections Framework), nesne gruplarını temsil etmek ve
işlemek için birleşik bir mimari sunar. Bu çerçeve, geliştiricilerin verileri verimli bir şekilde
depolamasına ve işlemesine yardımcı olmak için bir dizi arayüz, bu arayüzlerin somut
implementasyonları (veri yapıları) ve algoritmalar sağlar. Bu sayede programlama çabasını azaltır ve
performansı artırır.

Kapsamlı Konu Anlatımı

Bir koleksiyon (collection), birden çok öğeyi veri depolama, geri çağırma ve işleme amacıyla tek bir
birimde gruplayan bir nesnedir. Koleksiyonlar Çerçevesi üç ana bileşenden oluşur:

1. Arayüzler (Interfaces): Collection, List, Set gibi koleksiyonların soyut veri tiplerini tanımlar.
2. İmplementasyonlar (Implementations): ArrayList, LinkedList, HashSet gibi arayüzlerin somut
veri yapısı uygulamalarıdır.
3. Algoritmalar (Algorithms): Koleksiyonlar üzerinde arama (searching) ve sıralama (sorting) gibi
işlemleri gerçekleştiren metotlardır.

Hiyerarşinin kökünde, temel davranışları tanımlayan Collection arayüzü bulunur. Bu arayüz, size(),
isEmpty(), contains(), add(), remove() ve iterator() gibi temel metotları içerir.

iterator() metodu, koleksiyon üzerinde gezinmek için bir Iterator nesnesi döndürür. Iterator, bir
koleksiyondaki öğeler arasında dolaşmayı sağlayan bir nesnedir. Üç temel metodu vardır:

hasNext(): Koleksiyonda gezinilecek başka öğe olup olmadığını kontrol eder.


next(): Sıradaki öğeyi döndürür ve imleci bir ileri taşır.
remove(): next() tarafından en son döndürülen öğeyi koleksiyondan siler.

Peki, Iterator ne zaman kullanılmalıdır? Eğer sadece koleksiyondaki öğeleri taramak istiyorsanız, for-
each döngüsü yeterlidir. Ancak, koleksiyon üzerinde gezinirken öğeleri değiştirmek (özellikle
silmek) gerekiyorsa, Iterator kullanmak zorunludur. For-each döngüsü içinde koleksiyonu
değiştirmeye çalışmak ConcurrentModificationException hatasına yol açabilir.

Joshua Bloch'un da belirttiği gibi, koleksiyonlarla çalışırken "ham tipler" (raw types, örn. List) yerine
Jenerikler (Generics) (örn. List<String>) kullanmak, tür güvenliği (type-safety) açısından çok
önemlidir. Jenerikler, koleksiyona yalnızca belirtilen türde nesnelerin eklenmesine izin vererek,
çalışma zamanında ortaya çıkabilecek ClassCastException hatalarını derleme zamanında önler.

Collection arayüzünü genişleten List arayüzü, sıralı bir dizi kavramını ekler. List
implementasyonlarında, kullanıcı öğelerin ekleneceği konum üzerinde tam kontrole sahiptir ve
genellikle yinelenen (duplicate) öğelere izin verilir.

List'ler için Iterator'dan daha güçlü olan ListIterator bulunur. Bu arayüz, add(), hasPrevious() ve
previous() gibi ek metotlar sunarak listenin her iki yönde de (ileri ve geri) taranmasına ve gezinme
sırasında öğe eklenmesine olanak tanır.

En yaygın iki List implementasyonu ArrayList ve LinkedList'tir ve aralarında önemli performans farkları
vardır:

ArrayList: Arka planda bir dizi (array) tarafından desteklenir. Rastgele erişim (get(index)) çok
hızlıdır (O(1) karmaşıklığında). Ancak, listenin ortasına öğe eklemek veya silmek maliyetlidir,
çünkü sonraki tüm öğelerin kaydırılması gerekir.
LinkedList: Birbirine bağlı düğümlerden (nodes) oluşur. Listenin başına, sonuna veya ortasına
öğe eklemek/silmek çok hızlıdır, çünkü sadece komşu düğümlerin bağlantılarının güncellenmesi
yeterlidir. Ancak, belirli bir indeksteki öğeye erişmek (rastgele erişim) yavaştır, çünkü listenin
başından veya sonundan başlayarak o indekse kadar sırayla ilerlemek gerekir.

Kritik Bilgi Maddeleri

Çerçeve Bileşenleri: Arayüzler, İmplementasyonlar, Algoritmalar.


Collection Arayüzü Metotları: size(), isEmpty(), add(), remove(), iterator().
Iterator Arayüzü Metotları: hasNext(), next(), remove().
ListIterator Ek Metotları: hasPrevious(), previous(), add(o).
Tür Güvenliği: Çalışma zamanı hatalarını önlemek için ham tipler (List) yerine daima jenerikleri
(List<String>) kullanın.
ArrayList: Sık rastgele erişim gerektiren durumlar için en iyisidir. Arka planda bir dizi kullanır.
LinkedList: Listenin ortasında sık sık ekleme ve silme işlemi yapılan durumlar için en iyisidir.
Bağlantılı düğümler kullanır.

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

Ana "Koleksiyonlar Çerçevesi Diyagramı" hiyerarşiyi göstermektedir. Collection ve Map en üst düzey
arayüzlerdir. List ve Set arayüzleri Collection arayüzünü genişletir. ArrayList ve LinkedList, List
arayüzünü; HashSet ve TreeSet ise Set arayüzünü uygular. Diyagram ayrıca Iterator ve ListIterator
arasındaki ilişkiyi de gösterir.

"Insertion in Array List" diyagramı, bir ArrayList'in 2. indeksine 13 değeri eklendiğinde, sonraki tüm
öğelerin (14, 15, 16) sağa doğru kaydırılması gerektiğini görselleştirir. Bu, özellikle büyük listelerde
maliyetli bir işlemdir.

"Inserting into doubly linked list" diyagramı ise, bağlı bir listeye yeni bir düğüm (2) eklemenin yalnızca
çevresindeki düğümlerin (1 ve 3) işaretçilerini güncellemeyi gerektirdiğini gösterir. Bu, listenin
boyutundan bağımsız olarak hızlı ve verimli bir işlemdir.

ArrayList'teki set(), get(), add() ve remove() metotlarına ilişkin diyagramlar, bu operasyonların altta
yatan dizi yapısını nasıl etkilediğini görsel olarak açıklar. get() işlemi doğrudan indekse eriştiği için
hızlıdır, ancak add() ve remove() işlemleri dizide kaydırma gerektirebilir.

Sınav Dikkat Noktası


Klasik bir sınav sorusu, ArrayList yerine ne zaman LinkedList seçilmesi gerektiğini (veya tam
tersini) sormaktır. Öğrenci, performans farklılıklarını net bir şekilde ifade edebilmelidir: ArrayList,
get(index) operasyonlarının (rastgele erişim) baskın olduğu senaryolar için üstündür. LinkedList ise,
özellikle listenin ortasında çok sayıda ekleme veya silme işlemi yapılan uygulamalar için daha iyi bir
seçimdir. Bu zaman karmaşıklığı farkını anlamak hayati önem taşır.

Örnekler

1. Basit Koleksiyon ve Iterator Kullanımı

public class SimpleCollection {

public static void main(String[] args) {

Collection c = new ArrayList(); // creating a new array list

[Link](5); //add elements to list

[Link]("hello world"); //more

[Link](new FullTimeEmployee("jack",10)); //and more

Iterator iter = [Link](); // get the iterator from the collection

while ([Link]()) //check if there is a next element

[Link]([Link]()); //write the next element.

Uyarı: Bu örnekte farklı türde nesneler (Integer, String, FullTimeEmployee) aynı koleksiyona
eklenmiştir. Bu, Jenerikler (Generics) olmadan mümkündür ancak Joshua Bloch'un da belirttiği gibi
'ham tipler' (raw types) kullanmak tehlikelidir ve çalışma zamanında ClassCastException hatalarına
yol açabilir. Tür güvenliği için her zaman Collection<String> veya List<Ogrenci> gibi jenerik yapılar
kullanılmalıdır.

2. ListIterator ile İleri ve Geri Yönlü Gezinme

public class ListIteratorExample {

public static void main(String[] args) {

// Create a LinkedList

LinkedList<String> linkedlist = new LinkedList<String>();


[Link]("Abant"); // Add elements to LinkedList

[Link]("Mengen");

[Link]("Gerede");

ListIterator listIt = [Link](); // Obtaining ListIterator

[Link]("Forward iteration:");

// Iterating the list in forward direction

while([Link]())

[Link]([Link]());

// Iterating the list in backward direction

[Link]("\nBackward iteration:");

while([Link]())

[Link]([Link]());

3. ArrayList ve LinkedList Oluşturma

import [Link];

import [Link];

import [Link];

public class ListExample {

public static void main(String[] args) {

//create ArrayList

List<String> list = new ArrayList<>();

//add element to ArrayList

[Link]("jack");
[Link]("mike");

[Link]("hulk");

//create LinkedList

List<String> list2 = new LinkedList<>();

//add elements to LinkedList

[Link]("jade");

[Link]("June");

[Link]("April");

Geçiş Cümlesi

Nesne koleksiyonlarını nasıl saklayacağımızı ve yöneteceğimizi öğrendikten sonra, bir sonraki


mantıksal adım, bu nesneleri nasıl sıralayacağımızı ve karşılaştıracağımızı anlamaktır; bunu da
Comparable ve Comparator arayüzleri aracılığıyla keşfedeceğiz.

--------------------------------------------------------------------------------

HAFTA 12: Comparable & Comparator ve Dosya


İşlemleri
Giriş Paragrafı

Koleksiyonlar nesneleri gruplamamıza olanak tanırken, çoğu zaman bu nesneleri belirli kriterlere göre
sıralamamız veya durumlarını daha sonra kullanmak üzere bir dosyaya kaydetmemiz gerekir. Bu
hafta, sıralama için Comparable ve Comparator arayüzlerini ve dosya işlemleri için nesne
serileştirmesini (object serialization) ele alacağız.

Kapsamlı Konu Anlatımı

Nesneleri karşılaştırmanın farklı yolları vardır. == operatörü, iki referansın aynı nesneyi işaret edip
etmediğini kontrol eder. Buna karşılık, .equals(Object obj) metodu, iki nesnenin iç durumlarına
(değerlerine) göre anlamsal olarak eşdeğer olup olmadığını kontrol etmek için her sınıfta yeniden
yazılmalıdır (override).

Koleksiyonları sıralamak için Java iki temel mekanizma sunar:


1. Comparable Arayüzü: Bir sınıf için doğal bir sıralama tanımlamanın yoludur. Bir sınıf
Comparable arayüzünü uyguladığında, compareTo(Object obj) metodunu sağlamalıdır.
compareTometodunun sözleşmesi şöyledir:
Mevcut nesne, belirtilen nesneden küçükse negatif bir tamsayı,
Eşitse sıfır,
Büyükse pozitif bir tamsayı döndürmelidir. Bu, sınıfın varsayılan sıralama mantığını belirler
(örneğin, bir Student sınıfı için yaşa göre sıralama).
2. Comparator Arayüzü: Bir sınıf için harici, alternatif sıralamalar tanımlamanın yoludur. Bu,
nesneleri farklı şekillerde (örneğin, önce isme, sonra yaşa göre) sıralamak istediğinizde veya
Comparable uygulamak için sınıfın kaynak kodunu değiştiremediğinizde kullanılır. Comparator
arayüzünü uygulayan ayrı bir sınıf oluşturulur ve bu sınıf compare(Object o1, Object o2)
metodunu içerir. Bu metodun geri dönüş değeri sözleşmesi compareTo ile aynıdır.

Nesnelerin durumunu bir dosyaya kaydetme ve daha sonra dosyadan geri yükleme işlemine sırasıyla
serileştirme (serialization) ve seri dışılaştırma (deserialization) denir. Serileştirme sırasında, bir
nesne bir ByteStream'e (bayt akışı) dönüştürülerek bir dosyaya, belleğe veya veritabanına yazılır.
Seri dışılaştırma ise bu bayt akışını okuyup tekrar orijinal nesneye dönüştürme işlemidir.

Java'da bu süreç için kullanılan temel sınıflar şunlardır:

Kaydetme (Serialization): FileOutputStream ve ObjectOutputStream kullanılır. writeObject()


metodu nesneyi bayt akışına çevirir.
Yükleme (Deserialization): FileInputStream ve ObjectInputStream kullanılır. readObject()
metodu bayt akışından nesneyi yeniden oluşturur.

Kritik Bilgi Maddeleri

== vs. .equals(): == nesne referanslarını (bellek adresi) karşılaştırırken, .equals() nesne


durumunu (niteliklerin değerleri) karşılaştırmak için yeniden yazılmalıdır.
Comparable Arayüzü: Bir sınıf tarafından, kendi tekil, doğal sıralamasını tanımlamak için
uygulanır. compareTo(T o) metodunu kullanır.
Comparator Arayüzü: Çoklu, harici sıralamalar tanımlamak için ayrı bir sınıfta uygulanır.
compare(T o1, T o2) metodunu kullanır.
[Link](): Bu metot bir listeyi sıralayabilir. Eğer elemanlar Comparable ise, doğal
sıralarını kullanır. Aksi takdirde, bir Comparator argüman olarak geçirilebilir.
Serileştirme (Serialization): Bir nesnenin durumunu bir bayt akışına dönüştürme işlemi.
[Link]() kullanılır.
Seri Dışılaştırma (Deserialization): Bir bayt akışından bir nesneyi yeniden oluşturma işlemi.
[Link]() kullanılır.

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

"Serialization/De-Serialization" diyagramı süreci açıkça göstermektedir. Bir Object (Nesne),


ByteStream'e (Bayt Akışı) dönüştürülür (Serialization). Bu bayt akışı bir File (Dosya), Memory (Bellek)
veya Database'e (Veritabanı) gönderilebilir. Süreç, depolama alanından ByteStream'i okuyup tekrar
bir Object'e dönüştürerek tersine çevrilir (De-Serialization).
Sınav Dikkat Noktası

Bu haftanın en olası sınav sorusu Comparable ve Comparator arasındaki farktır. Öğrenci,


Comparable'ın varsayılan veya doğal sıralama düzenini tanımlamak için kullanıldığını ve doğrudan
sınıfın kendisi tarafından uygulandığını, bu nedenle tek bir sıralama mantığıyla sınırlı olduğunu
açıklayabilmelidir. Buna karşılık, Comparator'ın özel veya çoklu sıralama düzenleri için kullanıldığını,
ayrı bir sınıfta uygulandığını ve [Link]() gibi sıralama metotlarına parametre olarak
geçirildiğini belirtmelidir. Bu "sorumlulukların ayrılması" ilkesi, önemli bir tasarım prensibidir.

Örnekler

1. equals() Metodunun Yeniden Yazılması

class Worker {

private String name;

private int age;

private int weight;

public Worker(String name, int age, int weight) {

[Link] = name;

[Link] = age;

[Link] = weight;

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != [Link]()) return false;

Worker worker = (Worker) o;

return age == [Link] && weight == [Link] && [Link]([Link]);

}
// Kullanım

Worker w1 = new Worker("Homer", 35, 120);

Worker w2 = new Worker("Homer", 35, 120);

[Link](w1 == w2); // false

[Link]([Link](w2)); // true

2. Comparable ile Doğal Sıralama

class Student implements Comparable<Student> {

int rollno;

String name;

int age;

Student(int rollno, String name, int age) {

[Link] = rollno;

[Link] = name;

[Link] = age;

// Yaşa göre doğal sıralama

public int compareTo(Student st) {

return [Link] - [Link];

public String toString() {

return [Link] + " " + [Link] + " " + [Link];

3. Comparator ile Alternatif Sıralama


import [Link];

class AgeComparator implements Comparator<Student> {

public int compare(Student s1, Student s2) {

if ([Link] > [Link]) return 1;

else if ([Link] < [Link]) return -1;

else return 0;

class NameComparator implements Comparator<Student> {

public int compare(Student s1, Student s2) {

return [Link]([Link]);

4. [Link]() Kullanımı

import [Link];

import [Link];

public class Main {

public static void main(String args[]) {

ArrayList<Student> students = new ArrayList<Student>();

[Link](new Student(101, "Vijay", 23));

[Link](new Student(106, "Ajay", 27));

[Link](new Student(105, "Jai", 21));

// Doğal sıralama (Comparable - yaşa göre)


[Link](students);

[Link]("Yaşa Göre Sıralama:");

for (Student st : students) { [Link](st); }

// Comparator ile sıralama (isme göre)

[Link](students, new NameComparator());

[Link]("\nİsme Göre Sıralama:");

for (Student st : students) { [Link](st); }

5. Serileştirme ve Seri Dışılaştırma Örneği

import [Link].*;

public class FileManager {

static void Save(Object object, String filePath) throws IOException {

FileOutputStream fileOutputStream = new FileOutputStream(filePath);

ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

[Link](object);

static Object Load(String filePath) throws IOException, ClassNotFoundException {

FileInputStream fileInputStream = new FileInputStream(filePath);

ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

return [Link]();

}
// Kullanım

// Student student = new Student(101,"Vijay",23);

// [Link](student, "[Link]");

// Student loaded = (Student) [Link]("[Link]");

Geçiş Cümlesi

Kalıtımdan koleksiyonlara ve veri kalıcılığına kadar nesne yönelimli kavramlarda ustalaştıktan sonra,
şimdi de sürdürülebilir, ölçeklenebilir ve sağlam NYP yazılımları yazmak için bir dizi temel tasarım
kılavuzu olan SOLID ilkelerini inceleyerek anlayışımızı bir üst seviyeye taşıyacağız.

--------------------------------------------------------------------------------

HAFTA 13: SOLID Prensipleri - Bölüm 1


Giriş Paragrafı

SOLID prensipleri, nesne yönelimli programlamada yazılım tasarımlarını daha anlaşılır, esnek ve
sürdürülebilir kılmayı amaçlayan beş temel tasarım ilkesidir. Bu ilkeler, projenin büyümesiyle ortaya
çıkabilecek bakım ve genişletme zorluklarını en aza indirmek için en iyi uygulamaları ortaya koyar. Bu
hafta ilk üç ilkeyi ele alacağız: Tek Sorumluluk (Single Responsibility), Açık-Kapalı (Open-Closed) ve
Liskov Yerine Koyma (Liskov Substitution).

Kapsamlı Konu Anlatımı

1. Tek Sorumluluk Prensibi (Single-responsibility Principle - SRP)

Tanım: Her sınıfın değişmek için yalnızca bir nedeni olmalıdır. Başka bir deyişle, bir sınıfın
yalnızca tek bir işi veya sorumluluğu olmalıdır.
Problem: Problemli kod örneğinde, Employee sınıfı hem çalışan verilerini yönetme (veritabanına
kaydetme) hem de raporlama (maaş raporu oluşturma) olmak üzere iki farklı sorumluluğa
sahiptir. Veritabanı mantığı değiştiğinde veya rapor formatı güncellendiğinde, aynı sınıfın
alakasız nedenlerle değiştirilmesi gerekir. Bu, hata riskini artırır ve bakımı zorlaştırır.
Çözüm: Sorumlulukları farklı sınıflara ayırırız. Çözüm kodunda, Employee sınıfı yalnızca veri
tutmaktan sorumludur. Veritabanı işlemleri EmployeeRepository sınıfına, raporlama işlemleri ise
EmployeeReportGenerator sınıfına taşınmıştır. Artık bir sorumluluktaki değişiklik (örneğin
veritabanını değiştirmek) diğerlerini etkilemez.

2. Açık-Kapalı Prensibi (Open-closed Principle - OCP)

Tanım: Yazılım varlıkları (sınıflar, modüller vb.) genişletmeye açık, ancak değiştirmeye kapalı
olmalıdır.
Problem: Problemli PaymentProcessor sınıfında, her yeni ödeme türü eklendiğinde (credit_card,
paypal vb.) processPayment metodunun içine yeni bir if-else bloğu eklenmesi gerekmektedir. Bu,
mevcut ve test edilmiş kodu değiştirmeyi gerektirir, bu da prensibi ihlal eder ve mevcut ödeme
mantığında hatalara yol açabilir.
Çözüm: Davranışı değiştirmek yerine genişletmek için soyutlama ve çok biçimlilik kullanılır.
Çözümde, soyut bir Payment sınıfı ve her ödeme türü için bu sınıftan türeyen alt sınıflar
(CreditCardPayment, PayPalPayment) oluşturulur. Yeni PaymentProcessor, Payment tipinde bir
nesne alır ve bu nesnenin process() metodunu çağırır. Yeni bir ödeme türü eklemek için sadece
yeni bir alt sınıf oluşturmak yeterlidir; PaymentProcessor sınıfına dokunulmaz.

3. Liskov Yerine Koyma Prensibi (Liskov Substitution Principle - LSP)

Tanım: Bir üst sınıfın nesneleri, programın doğruluğunu etkilemeden alt sınıfının nesneleriyle
değiştirilebilir olmalıdır.
Problem: Problemli Bird (Kuş) örneğinde, Ostrich (Devekuşu) alt sınıfı, Bird üst sınıfının fly()
metodunu, bir istisna fırlatacak şekilde geçersiz kılar. Bu, "bütün kuşlar uçar" varsayımını bozar.
Bir Ostrich nesnesi, Bird nesnesi beklenen bir yere konulduğunda programın çökmesine neden
olur, bu da LSP'yi ihlal eder.
Çözüm: Hiyerarşi yeniden tasarlanır. Uçma davranışı, FlyBehaviour adında ayrı bir arayüzle
soyutlanır. Bird soyut sınıfı artık fly() metodunu içermez. Yalnızca uçabilen kuşlar (örneğin
Sparrow) bu arayüzü uygular. Ostrich ise bu arayüzü uygulamaz. Bu sayede, alt sınıflar üst
sınıflarının sözleşmelerine sıkı sıkıya bağlı kalır ve beklenmedik davranışlar sergilemezler.

Kritik Bilgi Maddeleri

S - Tek Sorumluluk Prensibi (SRP):


Bir sınıfın yalnızca bir işi veya sorumluluğu olmalıdır.
İhlal: Bir sınıfın, iş mantığı ve veritabanı kalıcılığı gibi birbiriyle ilgisiz birden çok görevi yerine
getirmesi.
Çözüm: Sorumlulukları ayrı ve farklı sınıflara bölmek.
O - Açık-Kapalı Prensibi (OCP):
Yazılım varlıkları genişletmeye açık, ancak değiştirmeye kapalı olmalıdır.
İhlal: Yeni bir işlevsellik eklemenin, mevcut ve test edilmiş kodu değiştirmeyi gerektirmesi
(örneğin, if-else blokları eklemek).
Çözüm: Yeni işlevselliğin yeni sınıflar aracılığıyla eklenmesine olanak tanımak için soyutlama
(arayüzler veya soyut sınıflar) ve çok biçimlilik kullanmak.
L - Liskov Yerine Koyma Prensibi (LSP):
Bir üst sınıfın nesneleri, programın doğruluğunu etkilemeden bir alt sınıfın nesneleriyle
değiştirilebilir olmalıdır.
İhlal: Bir alt sınıfın, bir üst sınıf metodunu beklenen davranışı bozacak şekilde değiştirmesi
(örneğin, beklenmedik bir istisna fırlatması veya hiçbir şey yapmaması).
Çözüm: Alt sınıfların, üst sınıflarının sözleşmelerine sıkı sıkıya uymasını sağlamak. Gerekirse
hiyerarşileri yeniden tasarlamak (örneğin, kompozisyon veya daha spesifik arayüzler
kullanarak).

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

Bu haftanın ders notlarında ana fikirler kod örnekleri üzerinden aktarılmıştır. Yorumlanacak ek şekil
veya tablo bulunmamaktadır.
Sınav Dikkat Noktası

SOLID prensipleri için yaygın bir sınav sorusu formatı "kod kokusu" (code smell) tespit etme görevidir.
Öğrenciye slaytlardaki örnekler gibi sorunlu bir kod parçası gösterilir ve hangi SOLID ilkesinin ihlal
edildiğini belirlemesi ve ilkeye uyması için nasıl yeniden düzenleneceğini (refactor) açıklaması istenir.
Bu kalıpları tanıyabilmek anahtardır. Örneğin, bir nesnenin türünü kontrol eden uzun bir if-else if
zinciri genellikle bir OCP ihlaline işaret eder.

Örnekler

1. Tek Sorumluluk Prensibi (SRP) Örneği

Problemli Kod:
Çözüm Kodu:

2. Açık-Kapalı Prensibi (OCP) Örneği

Problemli Kod:
Çözüm Kodu:

3. Liskov Yerine Koyma Prensibi (LSP) Örneği

Problemli Kod:
Çözüm Kodu:

Geçiş Cümlesi

S, O ve L ilkelerinde sağlam bir temel oluşturduktan sonra, gelecek hafta SOLID çalışmamızı, temiz
arayüzler tasarlamaya ve bağımlılıkları etkili bir şekilde yönetmeye odaklanan son iki ilke olan Arayüz
Ayırma Prensibi ve Bağımlılığın Ters Çevrilmesi Prensibi'ni inceleyerek tamamlayacağız.

--------------------------------------------------------------------------------

HAFTA 14: SOLID Prensipleri - Bölüm 2


Giriş Paragrafı

Bu son ders, son iki ilkeyi ele alarak SOLID prensipleri keşfini tamamlamaktadır: Arayüz Ayırma
Prensibi (Interface Segregation Principle - ISP) ve Bağımlılığın Ters Çevrilmesi Prensibi (Dependency
Inversion Principle - DIP). Bu ilkeler, arayüzlerin tasarımına ve modüller arasındaki bağımlılıkların
yönetimine rehberlik ederek daha az bağımlı ve daha esnek sistemler oluşturulmasını sağlar.

Kapsamlı Konu Anlatımı

4. Arayüz Ayırma Prensibi (Interface Segregation Principle - ISP)

Tanım: Bir sınıf, kullanmadığı metotları uygulamaya zorlanmamalıdır.


Problem: "Şişman" (fat) olarak adlandırılan Machine arayüzü, Printer sınıfını, desteklememesine
rağmen scan() ve fax() metotlarını uygulamaya zorlar. Bu, Printer sınıfının bu metotları boş
bırakmasına veya bir istisna fırlatmasına neden olur. Bu durum, gereksiz ve potansiyel olarak
hatalı koda yol açar.
Çözüm: Büyük Machine arayüzü, daha küçük ve role özgü arayüzlere (PrinterInterface,
ScannerInterface, FaxInterface) bölünür. Artık SimplePrinter gibi bir sınıf yalnızca
PrinterInterface'i uygularken, MultiFunctionMachine gibi çok işlevli bir cihaz birden çok arayüzü
uygulayabilir. Bu, sınıfların yalnızca ihtiyaç duydukları metotları uygulamalarını sağlar.

5. Bağımlılığın Ters Çevrilmesi Prensibi (Dependency Inversion Principle - DIP)

Tanım:
1. Yüksek seviyeli modüller, düşük seviyeli modüllere bağımlı olmamalıdır. Her ikisi de
soyutlamalara bağımlı olmalıdır.
2. Soyutlamalar, detaylara bağlı olmamalıdır. Detaylar, soyutlamalara bağlı olmalıdır.
Problem: Yüksek seviyeli UserService sınıfı, düşük seviyeli MySQLDatabase somut (concrete)
sınıfına sıkı bir şekilde bağlıdır. UserService içinde doğrudan new MySQLDatabase() ifadesi
kullanıldığından, veritabanını PostgreSQL gibi başka bir teknolojiyle değiştirmek, UserService
sınıfının kodunu değiştirmeyi gerektirir. Bu, sistemi katı ve esnek olmayan hale getirir.
Çözüm: Sisteme bir soyutlama, yani DatabaseInterface arayüzü eklenir. MySQLDatabase ve
PostgreSQLDatabase gibi düşük seviyeli sınıflar bu arayüzü uygular. Yüksek seviyeli
UserService sınıfı artık somut sınıflara değil, bu soyut arayüze bağımlı olur. Somut veritabanı
nesnesi, Bağımlılık Enjeksiyonu (Dependency Injection) yoluyla (örneğin yapıcı metot
aracılığıyla) UserService'e dışarıdan verilir. Bu, modüller arasındaki bağımlılığı ortadan kaldırır ve
esnekliği artırır.

Kritik Bilgi Maddeleri

I - Arayüz Ayırma Prensibi (ISP):


İstemciler, kullanmadıkları arayüzlere bağımlı olmaya zorlanmamalıdır.
İhlal: Tek bir büyük, genel amaçlı ("şişman") arayüzün, uygulayan sınıfları ihtiyaç
duymadıkları metotlar için boş veya istisna fırlatan implementasyonlar sağlamaya zorlaması.
Çözüm: Büyük arayüzleri daha küçük, daha spesifik, istemci odaklı arayüzlere bölmek.
D - Bağımlılığın Ters Çevrilmesi Prensibi (DIP):
Yüksek seviyeli modüller düşük seviyeli modüllere bağlı olmamalıdır. Her ikisi de
soyutlamalara bağlı olmalıdır.
Soyutlamalar detaylara, detaylar ise soyutlamalara bağlı olmalıdır.
İhlal: Yüksek seviyeli bir sınıfın, düşük seviyeli somut bir sınıfı doğrudan örnekleyip
kullanması, bu da sıkı bir bağlılık yaratır.
Çözüm: Düşük seviyeli sınıfın uyguladığı bir arayüz (soyutlama) getirmek. Yüksek seviyeli
sınıf daha sonra yalnızca arayüze bağımlı olur ve somut implementasyonu bağımlılık
enjeksiyonu yoluyla (örneğin, yapıcısı aracılığıyla) alır.

Teknik Terimler ve Tanımlar

Şekil/Tablo Yorumları

"DEMO INCLUDES ALL SOLID PRINCIPLES" etiketli kapsamlı UML diyagramı, beş ilkenin bir e-
ticaret sisteminde nasıl bir arada çalıştığını göstermektedir:
SRP: ProductRepository sınıfının yalnızca ürün verilerini yönetmesi ve OrderService sınıfının
sipariş işleme mantığını ele alması, sorumlulukların ayrıldığını gösterir.
OCP: Discountable arayüzü ve NoDiscount, FixedDiscount gibi implementasyonları, Order
sınıfını değiştirmeden yeni indirim türleri eklemeye olanak tanır. Aynı durum PaymentProcessor
için de geçerlidir.
LSP: CreditCardPayment ve PaypalPayment sınıflarının her ikisi de soyut PaymentProcessor
sınıfından türediği için, bu alt sınıf nesneleri üst sınıf referansı beklenen her yerde programı
bozmadan kullanılabilir.
ISP: Order sınıfı, tek bir büyük "Actionable" arayüzü yerine Printable ve Emailable gibi spesifik
arayüzleri uygular.
DIP: OrderService, somut bir ödeme sınıfına değil, PaymentProcessor soyutlamasına bağlıdır.
Benzer şekilde, Order sınıfı da Discountable soyutlamasına bağlıdır.

Sınav Dikkat Noktası

Final sınavı için öğrenci, sunulan UML diyagramı gibi daha büyük bir sistem tasarımına bakıp birden
çok SOLID ilkesinin birlikte nasıl çalıştığını tespit etmeye hazırlıklı olmalıdır. Unutulmaması
gereken en önemli nokta, bu ilkelerin izole olmadığı, birbiriyle bağlantılı olduğudur. Örneğin, OCP'yi
uygulamak genellikle DIP'nin merkezinde yer alan soyutlamaların kullanılmasına yol açar. İyi bir
tasarım, uyumlu ve sürdürülebilir bir sistem oluşturmak için beş ilkeyi de doğru bir şekilde uygular.

Örnekler

1. Arayüz Ayırma Prensibi (ISP) Örneği

Problemli Kod:
Çözüm Kodu:

2. Bağımlılığın Ters Çevrilmesi Prensibi (DIP) Örneği

Problemli Kod:
Çözüm Kodu:

Geçiş Cümlesi

Bu beş SOLID ilkesini içselleştirerek, temel NYP kavramlarından profesyonel, yüksek kaliteli yazılım
geliştirmenin temelini oluşturan ileri düzey tasarım desenlerine uzanan yolculuğu tamamlamış
bulunuyorsunuz.

You might also like