Roadmap’taki “Java’nın yapı taşları” bölümünün ilk konusu tam olarak bu beş kavramdan oluşuyor. Bunlar özellikle HashMap, HashSet, koleksiyonlar ve nesne modellemenin temelidir.
01 Nesne nedir?
Bir sınıf, oluşturulacak nesnelerin şablonudur:
JAVAclass Student {
int id;
String name;
}Henüz bir Student nesnesi oluşturmadık. Yalnızca nesnenin hangi verilere ve davranışlara sahip olacağını tanımladık. Nesne şu şekilde oluşturulur:
JAVAStudent student = new Student();
// ① ② ③Student: değişkenin referans türüstudent: referans değişkeninew Student(): yeni nesne oluşturulması
student │ │ referans ▼ Student nesnesi id = 0 name = null
student, nesnenin kendisi değildir. Nesneye erişmek için kullanılan bir referans değeri tutar. Java bize nesnenin gerçek bellek adresini vermez; dolayısıyla referansı C dilindeki doğrudan pointer gibi düşünmek tam doğru değildir. Referans, JVM tarafından yönetilen bir erişim değeridir.
02 Bütün sınıfların kökü: Object
Java’daki bütün sınıflar doğrudan veya dolaylı olarak java.lang.Object sınıfından türemiştir.
JAVAclass Student {
}
// Kavramsal olarak:
class Student extends Object {
}extends Object yazmamıza gerek yoktur; Java bunu örtülü olarak kabul eder. Bu nedenle oluşturduğumuz her nesne aşağıdaki metotlara sahiptir:
equals(Object other)hashCode()toString()getClass()
JAVAStudent student = new Student();
System.out.println(student.toString());
System.out.println(student.hashCode());
System.out.println(student.getClass());Diziler de nesnedir:
JAVAint[] numbers = {1, 2, 3};
System.out.println(numbers instanceof Object); // trueAncak primitive değerler nesne değildir. int number = 10;satırındaki 10, bir Integer nesnesi değil, primitive int değeridir.
Object türündeki değişken
Bütün nesneler Object olduğundan şunu yazabiliriz:
JAVAObject value1 = new Student();
Object value2 = "Merhaba";
Object value3 = new int[]{1, 2, 3};Bildirilen tür Object olduğu için derleyici yalnızca Object üzerinden erişilebilen metotları garanti eder. Nesnenin çalışma zamanındaki gerçek türü ise hâlâ Studenttır.
JAVAObject value = new Student();
// value.name; // Derleme hatası
System.out.println(value.getClass()); // class Student- Derleme zamanındaki referans türü:
Object - Çalışma zamanındaki nesne türü:
Student
Bu ayrım ileride polymorphism öğrenirken çok önemli olacak. Daha fazlası için Oracle Object dokümantasyonuna bakabilirsin.
03 Referans nasıl çalışır?
JAVAclass Box {
int value;
Box(int value) {
this.value = value;
}
}
Box first = new Box(10);
Box second = first;
second.value = 50;
System.out.println(first.value); // 50Neden first.value da değişti? Çünkü Box second = first; satırında nesneyi kopyalamadık. Yalnızca first içindeki referans değerini second değişkenine kopyaladık.
first ──────┐
▼
Box
value = 50
▲
second ─────┘Referansı değiştirmek, nesneyi değiştirmek değildir
JAVABox first = new Box(10);
Box second = first;
second = new Box(99);
System.out.println(first.value); // 10
System.out.println(second.value); // 99first ───► Box(value=10) second ───► Box(value=99)
second.value = 99;Aynı nesnenin içeriğini değiştirir.
second = new Box(99);Değişkenin hangi nesneye baktığını değiştirir.
null nedir?
Bir referans değişkeni hiçbir nesneye referans vermeyebilir:
JAVAStudent student = null;
student.name = "Ali"; // NullPointerExceptionnull bir nesne değildir. “Bu değişken şu anda hiçbir nesneye referans vermiyor” anlamına gelen özel bir referans değeridir. Ortada alanına erişilecek bir nesne olmadığı için ikinci satır çalışma zamanında NullPointerException üretir.
04 Java’da atama ve metot çağrıları değeri kopyalar
Primitive değişkende primitive değer kopyalanır:
JAVAint a = 10;
int b = a;
b = 20;
System.out.println(a); // 10Referans değişkeninde ise referans değeri kopyalanır. Nesne kopyalanmaz; iki değişken aynı nesneye yönelir.
JAVABox a = new Box(10);
Box b = a;Metotlarda da aynı kural geçerlidir:
JAVAstatic void change(Box box) {
box.value = 100;
}
Box box = new Box(10);
change(box);
System.out.println(box.value); // 100Metoda nesnenin kendisi değil, referans değerinin bir kopyası gönderildi. Kopyalanan referans da aynı nesneye ulaştığı için nesne değiştirilebilir. Ancak aşağıdaki metot çağıranın referansını değiştiremez:
JAVAstatic void replace(Box box) {
box = new Box(500);
}
Box box = new Box(10);
replace(box);
System.out.println(box.value); // 10replace içindeki box, çağıranın değişkeninden ayrı bir yerel değişkendir. Ona yeni referans atanması dışarıdaki değişkeni değiştirmez.
05 == operatörü
== iki farklı şekilde çalışır.
Primitive türlerde değer karşılaştırır
JAVAint a = 10;
int b = 10;
System.out.println(a == b); // true
boolean first = true;
boolean second = true;
System.out.println(first == second); // trueReferans türlerinde nesne kimliğini karşılaştırır
JAVAStudent first = new Student();
Student second = new Student();
System.out.println(first == second); // falseİki nesne aynı sınıftan oluşturulmuş olsa bile birbirinden farklı nesnelerdir. Ama second = first ise sonuç true olur; çünkü iki referans aynı nesneye ulaşır.
JAVAStudent first = new Student();
Student second = first;
System.out.println(first == second); // truefirst == second, “Bu iki referans aynı nesneyi mi gösteriyor?” sorusunu sorar. “İçerikleri aynı mı?” sorusunu sormaz.null ile karşılaştırma
JAVAStudent student = null;
System.out.println(student == null); // true
if (student == null) {
System.out.println("Öğrenci bulunamadı");
}Java Dil Belirtimi’ne göre referanslar üzerinde ==, iki değerin aynı nesneye veya ikisinin de null değerine referans verip vermediğini kontrol eder. Ayrıntıyı JLS equality operators bölümünde bulabilirsin.
06 equals() metodu
equals() şu soruyu cevaplamak için tasarlanmıştır: “Bu iki nesne, programımızın tanımladığı anlam bakımından eşit mi?”İki farklı nesne aynı öğrenciyi temsil edebilir:
JAVAStudent first = new Student(1, "Ali");
Student second = new Student(1, "Ali");
first == second // false
first.equals(second) // true olmasını isteyebilirizObject.equals() varsayılan olarak ne yapar?
Bir sınıfta equals() metodunu ezmezsek Object.equals() kullanılır. Varsayılan davranışı temelde şöyledir:
JAVApublic boolean equals(Object other) {
return this == other;
}JAVAclass Course {
int id;
Course(int id) {
this.id = id;
}
}
Course first = new Course(1);
Course second = new Course(1);
System.out.println(first == second); // false
System.out.println(first.equals(second)); // falseAlanları aynı olsa bile varsayılan Object eşitliği nesne kimliğini karşılaştırır.
07 Doğru bir equals() yazmak
JAVAimport java.util.Objects;
final class Student {
private final long id;
private final String name;
Student(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Student student)) {
return false;
}
return id == student.id
&& Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}Aynı referans optimizasyonu
JAVAif (this == other) {
return true;
}Bir nesne kendisiyle mutlaka eşittir. Ayrıca alanları karşılaştırmaya gerek kalmaz.
Tür kontrolü
JAVAif (!(other instanceof Student student)) {
return false;
}Bu kontrol üç işi birden yapar:
other == nulldurumunda false döndürür.- Farklı türde nesneleri reddeder.
- Değeri güvenli biçimde
Studentolarak kullanmamızı sağlar.
Alan karşılaştırmaları
id primitive long olduğu için == ile değeri karşılaştırılır. name ise String referansıdır; içerik eşitliği için equals()gerekir. Objects.equals() ayrıca null güvenlidir:
JAVAObjects.equals(null, null); // true
Objects.equals(null, "Ali"); // false
Objects.equals("Ali", "Ali"); // trueDoğrudan name.equals(student.name) yazmak, name değeri null olduğunda NullPointerException oluşturabilir.
08 equals() sözleşmesi
Doğru bir equals() şu beş kurala uymalıdır:
x.equals(x) == trueNesne kendine eşittir.
x.equals(y) == y.equals(x)Eşitlik iki yönde de aynıdır.
x=y, y=z → x=zEşitlik geçişlidir.
İlgili bilgiler değişmedikçe sonuç değişmez.
x.equals(null) == falseNesne null’a eşit değildir.
Bu kurallar Object.equals() sözleşmesinin bir parçasıdır ve Oracle equals() dokümantasyonunda tanımlanır.
09 hashCode() nedir?
hashCode(), nesne için bir int değeri üretir:
JAVAint hash = student.hashCode();Bu sayı:
- nesnenin gerçek bellek adresi değildir;
- nesne için evrensel olarak benzersiz değildir;
- iki farklı nesnede aynı olabilir;
- HashMap ve HashSet gibi yapılarda aramayı daraltır.
Nesne │ ▼ hashCode() │ ▼ Muhtemel bölüm / bucket │ ▼ equals() ile kesin eşitlik kontrolü
hashCode()“Nereye bakmalıyım?”
equals()“Aradığım nesne gerçekten bu mu?”
a.equals(b) == true ise mutlaka a.hashCode() == b.hashCode() olmalıdır.Tersi doğru değildir. Aynı hash kodu nesnelerin eşit olduğunu garanti etmez. Farklı nesnelerin aynı hash değerini üretmesine hash collision denir ve bu normaldir; int değerlerinin sayısı sınırlı, oluşturulabilecek nesnelerin sayısı çok daha fazladır. İlgili sözleşme Oracle hashCode() dokümantasyonunda yer alır.
10 equals() yazıp hashCode() yazmazsak ne olur?
Şu sınıf hatalıdır:
JAVAfinal class Student {
private final long id;
Student(long id) {
this.id = id;
}
@Override
public boolean equals(Object other) {
return other instanceof Student student
&& id == student.id;
}
// hashCode() unutuldu
}JAVAStudent first = new Student(1);
Student second = new Student(1);
System.out.println(first.equals(second)); // true
Set<Student> students = new HashSet<>();
students.add(first);
System.out.println(students.contains(second)); // false olabilirfirst ve second, equals bakımından eşit olsa bile Object sınıfından gelen farklı hash kodlarını kullanabilir. HashSet önce farklı bir bölüme bakıp eşitlik kontrolüne hiç ulaşmayabilir.
JAVA@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Student student)) return false;
return id == student.id;
}
@Override
public int hashCode() {
return Long.hashCode(id);
}11 Tam çalışan örnek
JAVAimport java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Student first = new Student(1, "Ali");
Student second = new Student(1, "Ali");
Student third = first;
System.out.println(first == second); // false
System.out.println(first.equals(second)); // true
System.out.println(first == third); // true
System.out.println(first.equals(third)); // true
System.out.println(
first.hashCode() == second.hashCode()
); // true
Set<Student> students = new HashSet<>();
students.add(first);
students.add(second);
System.out.println(students.size()); // 1
}
}
final class Student {
private final long id;
private final String name;
Student(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Student student)) {
return false;
}
return id == student.id
&& Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}HashSet boyutu 1 olur çünkü first ve second farklı nesneler olmalarına rağmen değer bakımından eşittir ve aynı hash kodunu üretir. Küme bu nedenle onları aynı eleman olarak değerlendirir.
12 Çok önemli hata: Değişken hash anahtarı
JAVAclass User {
String username;
User(String username) {
this.username = username;
}
@Override
public boolean equals(Object other) {
return other instanceof User user
&& Objects.equals(username, user.username);
}
@Override
public int hashCode() {
return Objects.hash(username);
}
}JAVAUser user = new User("ali");
Set<User> users = new HashSet<>();
users.add(user);
user.username = "veli";
System.out.println(users.contains(user)); // Beklenmedik sonuç çıkabilirNesne “ali” değerinden üretilen hash ile koleksiyona yerleştirildi. Eşitlikte kullanılan alan daha sonra “veli” oldu. Koleksiyon artık nesneyi başka bir bucket’ta arayabilir.
13 Sık yapılan hatalar
String içeriğini == ile karşılaştırmak
JAVAString first = new String("Java");
String second = new String("Java");
System.out.println(first == second); // false
System.out.println(first.equals(second)); // true== aynı nesne olup olmadığını, equals()metin içeriğini karşılaştırır.
Dizileri doğrudan equals() ile karşılaştırmak
JAVAint[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(a.equals(b)); // false
System.out.println(Arrays.equals(a, b)); // trueequals() metodunu yanlış imzayla yazmak
JAVA// Yanlış: override değil, overload eder
public boolean equals(Student other) {
return id == other.id;
}
// Doğru
@Override
public boolean equals(Object other) {
// ...
}@Override kullanmak bu tür hataları derleme aşamasında yakalar.
Beş kavram, tek zihinsel model
Bütün Java sınıflarının kök sınıfı.
Nesnenin kendisini değil, ona ulaşan değeri tutar.
Primitive’de değer; referansta aynı nesne mi?
Mantıksal veya değer eşitliği.
Hash tabanlı yapılarda aranacak alanı daraltır.
a.equals(b) == true ise mutlaka a.hashCode() == b.hashCode() olmalıdır. Aynı hash kodu, nesnelerin kesinlikle eşit olduğu anlamına gelmez.Çıktıyı çalıştırmadan tahmin et
JAVAStudent a = new Student(10, "Ece");
Student b = new Student(10, "Ece");
Student c = a;
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a == c);
System.out.println(a.equals(c));
System.out.println(a.equals(null));Beklenen çıktıyı göster
false true true true false
Bu beş sonucun nedenini rahatça açıklayabiliyorsan dersin ana zihinsel modelini kavramışsın demektir.