SEN1001 · 13. hafta · Classes and Objects: A Deeper Look II

Composition, enum ve referansların metoda geçişi

Bir sınıfın alanı olarak başka bir sınıfın nesnesi (has-a), sabit değer kümeleri için enum, ve nesne referanslarının metoda kopya olarak gitmesinin sonuçları: metot nesneyi değiştirebilir ama çağıranın değişkenini başka nesneye çeviremez.

compositionhas-a relationshipenumenum constantvalues()ordinal()valueOfenum constructorpass-by-valuereferencealiasingequals== vs equals

Bu hafta üç konu var ve üçü de nesnelerin birbirleriyle ilişkisi hakkında. Composition, bir nesnenin içinde başka nesneler olmasıdır (çalışanın doğum tarihi, arabanın motoru). enum, sabit ve sınırlı bir değer kümesini (günler, seviyeler, gezegenler) güvenli şekilde temsil eder. Son olarak, nesneleri metoda verince neyin değişip neyin değişmediğini kesin olarak öğreneceksin. Bu üçüncü konu, final sınavındaki “what is printed?” sorularının en sinsi kaynağıdır.

Altın kurallar:

  1. Composition = has-a. Bir sınıfın alanı başka bir sınıfın nesnesi olabilir.
  2. enum sabitleri new ile üretilmez, Day.WED diye kullanılır ve == ile karşılaştırılır.
  3. Java her zaman pass-by-value. Nesnelerde kopyalanan şey referanstır.
  4. Metot nesnenin içini değiştirebilir; çağıranın değişkenini başka nesneye çeviremez.
  5. == aynı nesne mi diye bakar, equals içeriğe (override edildiyse).

Composition: has-a ilişkisi

class Date {
    private int day, month, year;
    public Date(int day, int month, int year) {
        this.day = day; this.month = month; this.year = year;
    }
    public int getYear() { return year; }
    public String toString() { return day + "/" + month + "/" + year; }
    ...
}
class Employee {
    private String name;
    private Date birthDate;                  // başka bir sınıfın nesnesi
    public Employee(String name, Date birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }
    public Date getBirthDate() { return birthDate; }
    public String toString() { return name + ", born " + birthDate; }
}
// main içinde:
Date birth = new Date(14, 3, 2005);
Employee e = new Employee("Deniz", birth);
System.out.println(e);
System.out.println(e.getBirthDate().getYear());
Deniz, born 14/3/2005
2005
  • Employee’nin toString’i birthDate’i bir String ile birleştirince Date’in toString’i otomatik çağrıldı.
  • e.getBirthDate().getYear() bir zincir: önce Date nesnesini al, sonra onun metodunu çağır.
  • “Bir çalışan bir tarih sahibidir” cümlesi doğru geliyorsa composition doğru seçimdir.

İkinci örnek, işi iç nesneye devretmek (delegation):

class Engine {
    private int cc;
    public Engine(int cc) { this.cc = cc; }
    public int getCc() { return cc; }
    public void setCc(int cc) { this.cc = cc; }
    public void run() { System.out.println("Engine " + cc + "cc running"); }
}
// Car: private String model; private Engine engine; start() { print(model + ": "); engine.run(); }
Car car = new Car("Egea", new Engine(1400));
car.start();
System.out.println(car.getEngine().getCc());
Egea: Engine 1400cc running
1400

TUZAK · getter iç nesneyi dışarı verir. getBirthDate() Date’in kopyasını değil, referansını döndürür. Ayrıca kurucuya verilen Date nesnesi de kopyalanmadı: aynı referans saklandı. Dışarıda dd.setYear(1990) yapınca çalışanın doğum yılı da 1990 oldu (doğrulamada e2.getBirthDate().getYear() → 1990). Alan private olsa bile, değiştirilebilir bir iç nesneyi paylaşırsan içini koruyamazsın.

enum

Bazı değişkenler sadece birkaç değerden birini alabilir: haftanın günü, trafik ışığı, zorluk seviyesi. Bunları int ile (0 = Pazartesi) ya da String ile ("Mon") tutmak hataya açıktır: day = 9 ya da "mon" yazmak kimseyi durdurmaz. enum bu kümeyi bir tip olarak tanımlar:

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

Day d = Day.WED;
System.out.println(d + " " + d.ordinal() + " " + d.name());
System.out.println(d == Day.WED);
for (Day x : Day.values()) System.out.print(x + " ");
System.out.println();
System.out.println(Day.values().length);
System.out.println(Day.valueOf("FRI").ordinal());
WED 2 WED
true
MON TUE WED THU FRI SAT SUN 
7
4
İfadeNe verir
Day.WEDsabitin kendisi
d.ordinal()tanım sırası, 0’dan
d.name(), println(d)adı: "WED"
Day.values()bütün sabitler, sırayla, dizi olarak
Day.valueOf("FRI")String’den sabit; yoksa IllegalArgumentException
d == Day.WEDkarşılaştırma (enum’da == güvenli)

new Day() yazılamaz: enum classes may not be instantiated. Sabitler programın başında bir kez oluşturulur, o kadar.

enum ile switch

static String advice(Level lv) {
    switch (lv) {
        case LOW: return "Devam";       // Level.LOW değil, sadece LOW
        case MEDIUM: return "Dikkat";
        case HIGH: return "Dur";
        default: return "?";
    }
}
// main: advice(Level.LOW) + " " + advice(Level.HIGH)
Devam Dur

Alanlı ve kuruculu enum

enum aslında özel bir sınıftır; alanı, kurucusu ve metodu olabilir. Her sabit kurucuyu kendi argümanıyla çağırır:

enum Coin {
    QUARTER(25), HALF(50), LIRA(100);     // sabitlerden sonra ;
    private final int kurus;
    Coin(int k) { kurus = k; }            // kurucu public yazılmaz
    public int getKurus() { return kurus; }
}
// main içinde:
for (Coin c : Coin.values()) System.out.print(c + "=" + c.getKurus() + " ");
System.out.println();
int total = Coin.HALF.getKurus() * 2 + Coin.QUARTER.getKurus();
System.out.println(total);
QUARTER=25 HALF=50 LIRA=100 
125

Referansların metoda geçişi

Java’da her şey pass-by-value: metoda bir değerin kopyası gider. Primitive için kopyalanan şey sayının kendisidir. Nesne için kopyalanan şey referanstır (nesnenin adresi). Bu tek kuralın üç sonucu var:

static void rename(Pet p) { p.setName("Boncuk"); }                        // 1) içini değiştir
static void replace(Pet p) { p = new Pet("Yeni"); p.setName("Degisti"); }  // 2) yeni nesneye çevir
static void swap(Pet a, Pet b) { Pet t = a; a = b; b = t; }               // 3) takas
// main içinde:
Pet pet = new Pet("Pamuk");
rename(pet);
System.out.println(pet.getName());
replace(pet);
System.out.println(pet.getName());
Pet a = new Pet("A"), b = new Pet("B");
swap(a, b);
System.out.println(a.getName() + " " + b.getName());
Boncuk
Boncuk
A B
rename çağrısı:
  main.pet ──┐
             ├──► [Pet: "Pamuk" → "Boncuk"]     aynı nesne, içi değişti
  rename.p ──┘

replace çağrısı:
  main.pet ─────► [Pet: "Boncuk"]               dokunulmadı
  replace.p ────► [Pet: "Degisti"]              yeni nesne, metot bitince kaybolur
Metot ne yapıyor?Çağıran görür mü?
Parametre üzerinden nesnenin metodunu çağırıp içini değiştiriyorEvet
Parametreye yeni bir nesne atıyor (p = new ...)Hayır
İki parametreyi takas ediyorHayır
Primitive parametreyi değiştiriyorHayır
Yeni nesne oluşturup return ediyorEvet, dönen değeri atarsan

Metottan yeni bir nesne almak istiyorsan onu döndür: Pet m = make("Minnos");.

Sınavda bu tip bir soru görünce her metot çağrısı için kendine şunu sor: metot parametre üzerinden bir setter ya da eleman ataması mı yapıyor (çağıran görür), yoksa parametrenin kendisine mi atama yapıyor (çağıran görmez)? Cevabı bu tek soru belirler.

== ve equals

Pet x1 = new Pet("Tom"), x2 = new Pet("Tom");
System.out.println((x1 == x2) + " " + x1.equals(x2));
Point p1 = new Point(1, 2), p2 = new Point(1, 2);
System.out.println((p1 == p2) + " " + p1.equals(p2));
false false
false true

Pet equals’ı override etmedi; Object’ten gelen varsayılan equals == ile aynı şeyi yapar. Point ise equals’ı kendisi tanımladı ve x, y’yi karşılaştırıyor:

@Override
public boolean equals(Object o) {
    if (!(o instanceof Point)) return false;
    Point other = (Point) o;
    return x == other.x && y == other.y;
}

String’in equals’ı da böyle override edilmiş bir metot; "abc".equals(s) bu yüzden içeriğe bakar.

Sık yapılan hatalar

Yanlış düşünceDoğrusu
Composition için alanı public yapprivate alan + kurucu + gerekirse getter
Getter iç nesnenin kopyasını verirReferansını verir
Day d = new Day();Day d = Day.MON;
case Day.MON:case MON:
ordinal() 1’den başlar0’dan
valueOf("mon") çalışırBüyük/küçük harf duyarlı; yoksa exception
Java nesneleri pass-by-reference geçirirPass-by-value; kopyalanan şey referans
Metot p = new Pet(...) deyince main’deki nesne değişirDeğişmez
swap metodu nesneleri takas ederEtmez
x1 == x2 içerik karşılaştırırAynı nesne mi; içerik için equals

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

Employee has a Date birthDate field; Date(day, month, year) has getters. What is printed?

Employee q = new Employee("Ece", new Date(5, 9, 2004));
System.out.println(q.getBirthDate().getMonth() + q.getBirthDate().getDay());
Cevabı göster
14

getBirthDate() bir Date nesnesi döndürür, üzerinden hemen başka bir metot çağrılabilir (zincir). Ay 9, gün 5; ikisi de int, toplama: 14.

Soru 2orta

Given enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }, what is printed?

System.out.println(Day.MON.ordinal() + Day.SUN.ordinal() + " " + Day.values()[2]);
Cevabı göster
6 WED

ordinal() sabitin sırası, 0'dan başlar: MON 0, SUN 6. 0 + 6 = 6 (int toplama). values() sabitleri tanım sırasıyla bir dizi olarak verir; 2. indeks WED. enum println'da adıyla basılır.

Soru 3orta

Given enum Level { LOW, MEDIUM, HIGH }, what is printed?

Level lv = Level.MEDIUM;
switch (lv) {
    case LOW: System.out.print("L");
    case MEDIUM: System.out.print("M");
    case HIGH: System.out.print("H");
}
Cevabı göster
MH

enum ile switch yazılabilir; case'lerde sınıf adı yazılmaz (case LOW, Level.LOW değil). MEDIUM'dan girilir, break olmadığı için HIGH'a düşer: MH.

Soru 4orta

rename(Pet p) calls p.setName("Boncuk"). What is printed?

Pet q = new Pet("Zeytin");
Pet q2 = q;
rename(q2);
System.out.println(q.getName());
Cevabı göster
Boncuk

q ve q2 aynı nesneyi gösterir. rename'e o nesnenin referansının kopyası gider; metot aynı nesnenin adını değiştirir. Üç referans da tek bir nesneye bakıyor.

Soru 5zor

replace(Pet p) does p = new Pet("Yeni"); p.setName("Degisti"); and rename sets the name to Boncuk. What is printed?

Pet q = new Pet("Fistik");
replace(q);
rename(q);
replace(q);
System.out.println(q.getName());
Cevabı göster
Boncuk

replace içinde p = new Pet(...) sadece metodun yerel kopyasını yeni nesneye çevirir; main'deki q hâlâ eski nesnede. replace'in değiştirdiği nesne metot bitince kaybolur. Sadece rename'in etkisi kalır.

Soru 6orta

What is printed?

static void modify(int[] arr, int v) { arr[0] = 100; v = 100; }
// in main:
int[] nums = {1, 2, 3};
int k = 5;
modify(nums, k);
System.out.println(nums[0] + " " + k);
Cevabı göster
100 5

İkisi de pass-by-value. Dizi için kopyalanan şey referans; metot aynı dizinin elemanını değiştirir. int için kopyalanan şey değerin kendisi; v'yi değiştirmek k'ya dokunmaz.

Soru 7zor

Car has a private Engine engine field and getEngine() returns it; Engine has getCc() and setCc(int). What is printed?

Car c = new Car("Clio", new Engine(1000));
Engine en = c.getEngine();
en.setCc(1600);
System.out.println(c.getEngine().getCc());
Cevabı göster
1600

Getter nesnenin kopyasını değil, referansını döndürür. en, arabanın içindeki motorun ta kendisi; onu değiştirmek arabanın motorunu değiştirir. Alan private olsa bile değiştirilebilir bir nesne dışarı verilirse içi korunmaz.

Soru 8orta

enum Coin { QUARTER(25), HALF(50), LIRA(100); ... } has getKurus(). What is printed?

System.out.println(Coin.valueOf("QUARTER").getKurus() + Coin.values().length);
Cevabı göster
28

valueOf("QUARTER") String'den sabiti bulur; kuruş değeri 25. values().length sabit sayısı: 3. 25 + 3 = 28.

Soru 9orta

Point overrides equals to compare x and y. What is printed?

Point a = new Point(3, 4), b = new Point(3, 4), c = a;
System.out.println((a == b) + " " + a.equals(b) + " " + (a == c));
Cevabı göster
false true true

== aynı nesne mi diye bakar: a ve b iki ayrı nesne → false. equals override edildiği için içeriği karşılaştırır → true. c = a aynı nesne → true.

Soru 10orta

Given enum Level { LOW, HIGH }: (a) does Level x = new Level(); compile? (b) what happens at runtime with Level.valueOf("MID")?

Cevabı göster
(a) No: enum classes may not be instantiated · (b) IllegalArgumentException

enum sabitleri sabittir; new ile yenisi üretilemez, derleyici enum classes may not be instantiated der. valueOf olmayan bir isimle çağrılırsa derlenir ama çalışırken No enum constant Level.MID ile çöker.

Soru 11zor

Write an enum Planet with constants EARTH(9.81) and MARS(3.71), a private final double field gravity, a constructor and a getter.

Cevabı göster
EARTH(9.81), MARS(3.71); private final double gravity; Planet(double g) { gravity = g; }

Sabitlerden sonra ; konur, alanlar ve metotlar ondan sonra gelir. enum kurucusu public yazılmaz (otomatik private).enum Planet { EARTH(9.81), MARS(3.71); private final double gravity; Planet(double g) { gravity = g; } public double getGravity() { return gravity; }}Planet.MARS + " " + Planet.MARS.getGravity() → MARS 3.71.

Soru 12orta

Write a class Car that has a model (String) and an Engine (composition), a constructor taking both, and a start() method that prints the model followed by the result of the engine's run().

Cevabı göster
private Engine engine; ... public void start() { System.out.print(model + ": "); engine.run(); }

Car bir Engine'e sahip (has-a). Car işi kendisi yapmaz, motora devreder (delegation).class Car { private String model; private Engine engine; public Car(String model, Engine engine) { this.model = model; this.engine = engine; } public void start() { System.out.print(model + ": "); engine.run(); }}new Car("Egea", new Engine(1400)).start() → Egea: Engine 1400cc running.

Doğrulama: Tüm çıktılar JDK 26 ile çalıştırıldı (verify/SEN1001/W13.java; Date, Employee, Engine, Car, Pet, Point sınıfları ve Day, Level, Planet, Coin enum'ları aynı dosyada). enum hataları verify/SEN1001/err/en/ altında javac ve java ile alındı.

Deftere not kartı

Composition (has-a): bir alan başka bir sınıfın nesnesi Employee has-a Date, Car has-a Engine Zincir: e.getBirthDate().getYear() iç nesnenin toString'i dıştakinin toString'inde otomatik Getter iç nesnenin REFERANSINI verir → dışarıdan değiştirilebilir enum Day { MON, TUE, WED } Day d = Day.WED; new Day() YOK d.ordinal() 0'dan sıra d.name() / println(d) → "WED" Day.values() dizi Day.valueOf("WED") valueOf yanlış isim → IllegalArgumentException (runtime) enum karşılaştırma == switch(d) { case MON: ... } (Day. yazılmaz) enum alan+kurucu: EARTH(9.81), MARS(3.71); private final double g; Planet(double g) {...} Java HER ZAMAN pass-by-value: nesnede kopyalanan şey REFERANS Metot nesnenin içini değiştirebilir (p.setName) → çağıran görür Metot parametreyi yeni nesneye çevirirse (p = new ...) → çağıran görmez swap(a, b) nesnelerde de çalışmaz == aynı nesne mi equals içerik (override edilmediyse == ile aynı)