Sınıflar ve nesneler: tekrar
Alan, constructor, this, kapsülleme, static ve instance farkı, toString ve equals. Dönemin geri kalanı bu temelin üstüne kurulur.
Bu ders SEN1001’in devamı. İlk hafta, nesne yönelimli programlamanın (object oriented programming) temel taşlarını toparlıyoruz. Kalıtım, çok biçimlilik ve arayüzler bu sayfadaki her kavramı tekrar kullanır. Sınavda bu konudan üç tip soru gelir: “What is printed?” (özellikle static sayaçlar, referanslar ve constructor zinciri), “Find the compile error” (private erişim, eksik constructor, static bağlam) ve “Write a class”.
Altın kurallar:
- Değişken nesneyi tutmaz, nesnenin adresini (reference) tutar.
b = ayeni nesne yaratmaz. - Bir constructor yazdığın an, parametresiz default constructor kaybolur.
- Alan ile parametre aynı addaysa
this.yazmazsan alan değişmez. - static olan sınıfa aittir, tek kopyadır. static metot instance alana doğrudan erişemez.
==adres karşılaştırır,equalsiçerik. (equals override edildiyse.)
Sınıf, nesne, alan
Sınıf (class) bir kalıptır: hangi verileri tutacağını (alanlar, fields / instance variables) ve neler yapabileceğini (metotlar) tanımlar. Nesne (object) bu kalıptan new ile üretilen somut örnektir. Her nesnenin alanları kendine aittir.
public class Account {
private String owner; // instance variable
private double balance;
private static int count = 0; // sınıfa ait, tek kopya
public Account(String owner, double balance) {
this.owner = owner;
setBalance(balance); // kontrol tek yerde
count++;
}
public Account(String owner) {
this(owner, 0.0); // diğer constructor'a devret
}
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public double getBalance() { return balance; }
public void setBalance(double b) {
if (b >= 0) balance = b;
}
public static int getCount() { return count; }
@Override
public String toString() {
return owner + " (" + balance + " TL)";
}
}
Account a = new Account("Ece", 100);
Account b = new Account("Mert");
a.deposit(50);
a.deposit(-20); // reddedilir
b.setBalance(-5); // reddedilir
System.out.println(a);
System.out.println(b);
System.out.println(Account.getCount());
Ece (150.0 TL)
Mert (0.0 TL)
2
System.out.println(a) yazınca Java otomatik olarak a.toString() çağırır. toString’i override etmeseydin Account@1b6d3586 gibi sınıf adı ve hash kodu basılırdı; bu sayı her çalıştırmada değişebilir.
Varsayılan değerler
Alanlara değer atamazsan Java varsayılan değer verir. Yerel değişkenlere (metot içindekiler) vermez; atamadan okumak derleme hatasıdır.
| Alan tipi | Varsayılan |
|---|---|
int, long, short, byte | 0 |
double, float | 0.0 |
boolean | false |
char | '\u0000' (kod 0) |
Her referans (String, dizi, nesne) | null |
Constructor
Constructor nesne yaratılırken bir kez çalışır, alanlara başlangıç değeri verir. Adı sınıfla aynıdır ve dönüş tipi yoktur (void bile yazılmaz; yazarsan sıradan bir metot olur).
- Hiç constructor yazmazsan derleyici parametresiz, boş bir default constructor ekler.
- Tek bir constructor bile yazarsan bu ekleme yapılmaz.
new Car()artık derlenmez. - Aynı sınıfta parametre listesi farklı birden fazla constructor olabilir (overloading).
this anahtar kelimesi
this o an üzerinde çalışılan nesnenin kendisidir. İki kullanımı var:
- Gölgelemeyi çözmek (shadowing). Parametre adı alan adıyla aynıysa, metot içinde düz
nameparametreyi gösterir. Alanı göstermek içinthis.nameyazarsın. - Constructor zinciri.
this(...)aynı sınıfın başka bir constructor’ını çağırır. Constructor’ın ilk satırı olmak zorundadır. (Java 25 ile gelen flexible constructor bodies özelliği, öncesinethiskullanmayan satırlara izin verir. Ders kitabı ve sınav eski kuralı esas alır: ilk satır.)
class Box {
int w, h;
Box() { this(1); System.out.println("Box()"); }
Box(int w) { this(w, w); System.out.println("Box(int)"); }
Box(int w, int h) { this.w = w; this.h = h;
System.out.println("Box(int,int) " + w + "x" + h); }
}
// main:
Box bx = new Box();
System.out.println(bx.w * bx.h);
Box(int,int) 1x1
Box(int)
Box()
1
new Box()
└─ this(1) → Box(int) çalışmaya başlar
└─ this(1, 1) → Box(int,int) çalışır, BASAR, biter
Box(int) kalan satırı basar
Box() kalan satırı basar
TUZAK · zincirde sıra. Çağrı sırası dıştan içe, çıktı sırası içten dışa. En son çağrılan constructor ilk biter. Kalıtımda
super(...)zinciri de tam olarak böyle çalışır (2. hafta).
Kapsülleme (encapsulation)
Kapsülleme, nesnenin verisini dışarıdan doğrudan değiştirilemez yapmaktır. Kural basit: alanlar private, erişim public metotlarla. Okuyan metoda getter (getBalance), değiştirene setter (setBalance) denir. Setter’ın asıl işi kontrol etmektir: negatif bakiye, boş isim, geçersiz not gibi değerleri reddeder. Böylece nesne hiçbir zaman bozuk duruma düşmez.
| Erişim belirleyici | Aynı sınıf | Aynı paket | Alt sınıf | Her yer |
|---|---|---|---|---|
private | evet | hayır | hayır | hayır |
| (yazılmamış, package-private) | evet | evet | aynı paketteyse | hayır |
protected | evet | evet | evet | hayır |
public | evet | evet | evet | evet |
İPUCU · kontrolü tek yere koy. Constructor da setter’ı çağırsın (
setBalance(balance)). Aynı kontrolü iki yere yazarsan biri güncellenir, öteki unutulur.
static ve instance
| instance (static değil) | static | |
|---|---|---|
| Kime ait? | Her nesneye ayrı kopya | Sınıfa, tek kopya |
| Nasıl çağrılır? | nesne.metot() | Sinif.metot() |
this var mı? | Var | Yok |
| Neye erişir? | Hem instance hem static üyelere | Sadece static üyelere (doğrudan) |
| Tipik örnek | balance, getBalance() | sayaç count, Math.sqrt, main |
class Counter {
static int total = 0;
int mine = 0;
void hit() { total++; mine++; }
}
Counter c1 = new Counter(), c2 = new Counter();
c1.hit(); c1.hit(); c2.hit();
System.out.println(c1.mine + " " + c2.mine + " " + Counter.total);
2 1 3
main static olduğu için, main içinden aynı sınıftaki instance alanı ya da instance metodu doğrudan çağıramazsın. Önce bir nesne yaratman gerekir. Bu, ilk haftaların en sık derleme hatasıdır: non-static variable score cannot be referenced from a static context.
Referanslar ve parametre geçişi
Java’da her şey değer olarak geçer (pass by value). Fark, kopyalanan şeyin ne olduğu:
int,doublegibi ilkel tiplerde sayının kopyası gider. Metot içindeki değişiklik dışarıyı etkilemez.- Nesnelerde adresin kopyası gider. Metot o adresteki nesnenin alanlarını değiştirebilir, dışarıdan görünür. Ama parametreye
newile başka nesne atarsa sadece kendi kopyasını çevirmiş olur.
static void fill(Cup c) { c.ml = 250; } // nesneyi değiştirir
static void swapCup(Cup c) { c = new Cup(999); c.ml = 1; } // sadece yerel kopya
static void bump(int n) { n = n + 10; } // sadece yerel kopya
Cup cup = new Cup(100);
int n = 5;
fill(cup);
bump(n);
System.out.println(cup.ml + " " + n);
swapCup(cup);
System.out.println(cup.ml);
250 5
250
toString ve equals
Her sınıf, yazmasan da Object sınıfından türer ve ondan toString() ile equals(Object) metotlarını alır. İkisinin de varsayılan hali çoğu zaman işine yaramaz:
- Varsayılan
toString():SinifAdi@hashkodu. - Varsayılan
equals(o):this == o, yani adres karşılaştırması.
İçerik karşılaştırmak için equals’ı override edersin. Parametre tipi Object olmalı; equals(Point p) yazarsan override değil overload olur ve beklediğin yerde çağrılmaz.
class Point {
private final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // aynı nesne
if (!(o instanceof Point)) return false; // tip yanlış ya da null
Point p = (Point) o;
return x == p.x && y == p.y;
}
@Override
public String toString() { return "(" + x + ", " + y + ")"; }
}
Point p1 = new Point(2, 3), p2 = new Point(2, 3);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
System.out.println(p1);
false
true
(2, 3)
String’de de aynı mantık var. "java" == new String("java") false verir, equals true. String literal’leri ortak bir havuzda (string pool) tutulduğu için iki literal arasında == bazen true çıkar; bu bir şans eseridir, buna güvenme.
TUZAK ·
@Overrideyaz. Metodun başına@Overridekoyarsan, imzayı yanlış yazdığında (örneğinequals(Point o)ya datostring()) derleyici seni uyarır. Koymazsan sessizce yeni bir metot yaratmış olursun.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
name = name; alanı doldurur | Parametreyi kendine atar, alan null kalır. this.name = name; |
Constructor yazdım, new Car() yine çalışır | Bir constructor yazınca default constructor eklenmez |
Constructor’a void yazmak sorun değil | void yazarsan sıradan metot olur, constructor değil |
b = a; kopya nesne yaratır | Aynı nesneye ikinci referans; biri değişince öteki de görür |
| static metottan instance alana erişirim | Önce nesne yarat: obj.alan |
| static sayaç her nesnede ayrı | Tek kopya, bütün nesneler paylaşır |
String’i == ile karşılaştırmak | equals kullan |
equals(Point p) override eder | Parametre Object olmalı, yoksa overload |
| Metoda int verdim, metot değiştirdi | İlkel tip kopyalanır, dışarısı değişmez |
this(...) istediğin satırda olabilir | Sınavın esas aldığı kural: constructor’ın ilk satırı |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed?
class Counter {
static int total = 0;
int mine = 0;
void hit() { total++; mine++; }
}
// in main:
Counter c1 = new Counter(), c2 = new Counter();
c1.hit(); c1.hit(); c2.hit();
System.out.println(c1.mine + " " + c2.mine + " " + Counter.total);Cevabı göster
mine instance alanı: her nesnenin kendi kopyası var. c1 iki kez, c2 bir kez arttı: 2 ve 1. total static: tek kopya, üç çağrının hepsi aynı sayacı artırdı: 3.
What is printed?
class Student {
private String name;
public Student(String name) { name = name; }
public String getName() { return name; }
}
// in main:
System.out.println(new Student("Ali").getName());Cevabı göster
Constructor içinde name parametresi alanı gölgeler (shadowing). name = name; parametreyi kendisine atar, alana hiç dokunmaz. Alan varsayılan değeri null ile kalır. Düzeltme: this.name = name;.
What is printed?
Account x = new Account("Can", 10); // deposit adds to balance
Account y = x;
y.deposit(5);
System.out.println(x.getBalance());
System.out.println(x == y);Cevabı göster
y = x yeni nesne yaratmaz, aynı nesnenin adresini kopyalar (aliasing). y üzerinden yapılan deposit, x'in gördüğü nesneyi değiştirir: 15.0 (balance double). İki referans aynı nesneyi gösterdiği için == true.
What is printed?
String s1 = "java";
String s2 = new String("java");
String s3 = "java";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s1 == s3);Cevabı göster
== referansları karşılaştırır. new String her zaman yeni nesne yaratır, s1 ile aynı adres değil: false. equals içeriğe bakar: true. s1 ve s3 aynı literal, Java literal'leri string pool'da tek kopya tutar: true. Sınavda String karşılaştırmayı her zaman equals ile yaz.
Find the compile error.
class Car {
private String model;
public Car(String model) { this.model = model; }
}
public class Test {
public static void main(String[] args) {
Car c = new Car();
}
}Cevabı göster
Sınıfa tek bir constructor bile yazdığın an derleyici varsayılan (default) constructor'ı eklemez. Car sadece Car(String) biliyor. javac: constructor Car in class Car cannot be applied to given types; required: String, found: no arguments. Çözüm: new Car("Clio") ya da sınıfa public Car() { ... } ekle.
Find the compile error.
public class Test {
private int score = 10;
public static void main(String[] args) {
System.out.println(score);
}
}Cevabı göster
main static: çalışırken ortada bir Test nesnesi yok, score ise her nesnenin kendi alanı. javac: non-static variable score cannot be referenced from a static context. Çözüm: Test t = new Test(); System.out.println(t.score); ya da alanı static yap.
What is printed?
class Defaults { int i; double d; boolean b; String s; }
// in main:
Defaults df = new Defaults();
System.out.println(df.i + " " + df.d + " " + df.b + " " + df.s);Cevabı göster
Alanlar (instance variables) atanmasa da varsayılan değer alır: sayılar 0 / 0.0, boolean false, referanslar null. Yerel değişkenler ise varsayılan almaz; atamadan okursan derleme hatası.
What is printed?
class Box {
int w, h;
Box() { this(1); System.out.println("Box()"); }
Box(int w) { this(w, w); System.out.println("Box(int)"); }
Box(int w, int h) { this.w = w; this.h = h;
System.out.println("Box(int,int) " + w + "x" + h); }
}
// in main:
Box bx = new Box();
System.out.println(bx.w * bx.h);Cevabı göster
this(...) constructor'ın ilk satırı ve önce o çalışır. Box() → Box(1) → Box(1, 1). En içteki önce biter ve basar, sonra geri dönerken Box(int), en son Box(). Alanlar 1 ve 1: 1 * 1 = 1.
What is printed?
class Cup { int ml; Cup(int ml) { this.ml = ml; } }
static void fill(Cup c) { c.ml = 250; }
static void swapCup(Cup c) { c = new Cup(999); c.ml = 1; }
static void bump(int n) { n = n + 10; }
// in main:
Cup cup = new Cup(100);
int n = 5;
fill(cup);
bump(n);
System.out.println(cup.ml + " " + n);
swapCup(cup);
System.out.println(cup.ml);Cevabı göster
Java her zaman değer kopyalar (pass by value). int için sayının kopyası gider: bump dışarıdaki n'yi değiştirmez, 5 kalır. Nesne için adresin kopyası gider: fill aynı nesnenin alanını değiştirir, 250. swapCup parametreye yeni nesne atar; bu sadece yerel kopyayı başka yere çevirir, main'deki cup hâlâ eski nesneyi gösterir: 250.
What is printed?
class P { int x, y; P(int x, int y) { this.x = x; this.y = y; } }
// in main:
P a = new P(2, 3), b = new P(2, 3);
System.out.println(a.equals(b));Cevabı göster
P sınıfı equals'ı override etmedi, Object'ten gelen equals çalışır. Object'in equals'ı == ile aynı şeyi yapar: iki ayrı nesne, false. İçerik karşılaştırması istiyorsan equals'ı kendin yazmalısın.
Find the compile error.
class Wallet {
private double money = 50;
}
public class Test {
public static void main(String[] args) {
Wallet w = new Wallet();
w.money = 100;
}
}Cevabı göster
private alan sadece kendi sınıfının içinden görülür. javac: money has private access in Wallet. Kapsüllemenin amacı bu: dışarıdan değiştirmek için Wallet bir setter (örneğin setMoney) sunmalı ve değeri kontrol etmeli.
Write a class Rectangle with private width and height (double), a constructor that rejects non-positive values with IllegalArgumentException, a getArea() method and a toString() that returns Rectangle[3.0 x 4.5] style text.
Cevabı göster
Alanlar private, kontrol setter'da, constructor setter'ları çağırıyor (kontrol tek yerde):
public class Rectangle {
private double width, height; public Rectangle(double width, double height) {
setWidth(width);
setHeight(height);
}
public void setWidth(double width) {
if (width <= 0) throw new IllegalArgumentException("width must be > 0");
this.width = width;
}
public void setHeight(double height) {
if (height <= 0) throw new IllegalArgumentException("height must be > 0");
this.height = height;
}
public double getArea() { return width * height; } @Override
public String toString() {
return "Rectangle[" + width + " x " + height + "]";
}
}new Rectangle(3, 4.5) basılınca Rectangle[3.0 x 4.5] area=13.5 (alan ile birlikte); new Rectangle(-1, 2) istisna fırlatır, mesajı width must be > 0.Doğrulama: Tüm kod örnekleri ve cevaplar JDK 26 ile çalıştırıldı (java verify/SEN1002/W01.java); derleme hatası soruları javac ile denendi (verify/SEN1002/err/E01a-d.java; E01d flexible constructor kuralı için --release 21 ile de).