Roadmap’e dön
Ders 1 · Java’nın yapı taşları

Object, reference, ==, equals() ve hashCode()

Java’da nesnelerin nasıl tutulduğunu, ne zaman eşit sayıldığını ve hash tabanlı koleksiyonlarda neden doğru davranmaları gerektiğini tek bir zihinsel model üzerinden öğren.

Java Core1 Eylül 2026~18 dk okuma

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.

Önce ana fikir: Java’da bir nesne değişkeni çoğunlukla nesnenin kendisini değil, o nesneye ulaşmamızı sağlayan bir referans değerini tutar. Bu ayrımı anlarsan diğer dört kavram büyük ölçüde yerine oturur.

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();
// ①      ②          ③
  1. Student: değişkenin referans türü
  2. student: referans değişkeni
  3. new 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); // true

Ancak 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); // 50

Neden 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); // 99
first  ───► 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"; // NullPointerException

null 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

Java her zaman pass-by-value kullanır.

Primitive değişkende primitive değer kopyalanır:

JAVAint a = 10;
int b = a;

b = 20;

System.out.println(a); // 10

Referans 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); // 100

Metoda 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); // 10

replace 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); // true

Referans 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); // true
Referanslar için first == 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ı isteyebiliriz

Object.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)); // false

Alanları 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 == null durumunda false döndürür.
  • Farklı türde nesneleri reddeder.
  • Değeri güvenli biçimde Student olarak 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"); // true

Doğ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:

Reflexivex.equals(x) == true

Nesne kendine eşittir.

Symmetricx.equals(y) == y.equals(x)

Eşitlik iki yönde de aynıdır.

Transitivex=y, y=z → x=z

Eşitlik geçişlidir.

Consistent

İlgili bilgiler değişmedikçe sonuç değişmez.

null kuralıx.equals(null) == false

Nesne 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?”

Eğer 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 olabilir

first 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.

equals() metodunu override ediyorsan hashCode() metodunu da override et.İkisinde de eşitliği belirleyen aynı alanları kullan.
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ç çıkabilir

Nesne “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.

HashMap anahtarları ve HashSet elemanları için eşitlik ve hash hesabında kullanılan alanlar, nesne koleksiyondayken değişmemelidir. Immutable nesnelerin önemli olmasının nedenlerinden biri budur.

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)); // true

equals() 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.

Konunun kısa özeti

Beş kavram, tek zihinsel model

Object

Bütün Java sınıflarının kök sınıfı.

Reference

Nesnenin kendisini değil, ona ulaşan değeri tutar.

==

Primitive’de değer; referansta aynı nesne mi?

equals()

Mantıksal veya değer eşitliği.

hashCode()

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.
Kendini test et

Çı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.