Java'da equals() nasıl yazılır

Feb 22 2023
Bu makalenin sonunda, Java programlama dilinde best equals() yöntemini yazabilmek için gerekli tüm bilgiler ele alınacaktır. Eşitliğin detayları, equals() yönteminin en iyi uygulamasını kullanmak için gerekli olan temel bilgileri kapsayacak şekilde, matematiksel tanım açısından ve Java programlama dili tarzında ele alınacaktır.

Bu makalenin sonunda, Java programlama dilinde best equals() yöntemini yazabilmek için gerekli tüm bilgiler ele alınacaktır. Eşitliğin detayları , equals() yönteminin en iyi uygulamasını kullanmak için gerekli olan temel bilgileri kapsayacak şekilde, matematiksel tanım açısından ve Java programlama dili tarzında ele alınacaktır .

Eşitliğin Matematiksel Tanımı

Wikipedia'ya göre matematiksel eşitliğin tanımı

Matematikte eşitlik, iki nicelik arasındaki ilişki veya daha genel olarak niceliklerin aynı değere sahip olduğunu veya ifadelerin aynı matematiksel nesneyi temsil ettiğini ileri süren iki matematiksel ifadedir. A ve B arasındaki eşitlik A = B olarak yazılır ve A eşittir B olarak telaffuz edilir .

Java'da Eşitliğin Tanımı

Java programlama dilinde eşitlik, basitçe aynı değere sahip olmak anlamına gelir. Bazen kelimenin tam anlamıyla aynı değere sahip olması, bazen de nesnenin özellikleri açısından aynı yapıya sahip olması kastedilmektedir.

İki ilkel tip değerinin eşitliğini kontrol etmek gerektiğinde, aşağıdaki örnekte olduğu gibi doğrudan eşittir işareti kullanılır;

int a = 5;
int b = 6;
int c = 5;

// Prints "false" since a and b do not have the same value.
System.out.println(a == b);

// Prints "true" since a and c have the same value.
System.out.println(a == c);

Öte yandan, bazen iki nesnenin eşitliğini kontrol etmek gerekir. Nesneler açısından, iki nesnenin eşitliğinin kontrol edilmesi sürecinin farklı aşamalarında kullanılan iki farklı yaklaşım vardır. Ayrıntılara geçelim.

Referans Eşitliği

Java programlama dilinde, nesnelerin gerçek değerleri hafızada bir yerde saklanır ve yine hafızada bir yerde saklanan nesne referansları ile işaretlenir. Aşağıdaki kod parçasını inceleyelim;

// Create an object with "new" operator.
// Point to object with object reference "a".
Object a = new Object();

// Create another object reference.
// Point to the same object as the reference "a" is pointing.
Object b = a;

// Output is true.
System.out.println(a == b);

// Create another object with "new" operator.
// Point to object with object reference "b".
b = new Object();

// Output is false.
System.out.println(a == b);

Böyle bir senaryoda “a” ve “b” aslında aynı nesneyi işaret eden iki referanstır, bu nedenle referans eşitliği elde edildiğinden “a” nın “b” ye eşit olduğu doğrudan söylenebilir.

Ancak bunun tersi senaryo her zaman geçerli değildir. İki farklı referans, farklı hafıza konumlarını işaret edebilir, ancak referansların işaret ettiği nesneler yine de eşit olabilir.

Normal hayatta bazen miktarlar karşılaştırılır. Örneğin, sizin iki elmanız varsa ve arkadaşınızın iki elması varsa, o zaman sizin ve arkadaşınızın sahip olduğu elma sayısı eşittir.

Öte yandan, bazen şeyler, özellikleri veya yapısı açısından da karşılaştırılabilir. Bu senaryonun detaylarına geçelim.

Yapısal Eşitlik

Yapısal eşitlik, nesnelerin özelliklerinin durumlarına göre karşılaştırıldıkları türdür. Bu tür bir eşitliği kontrol etmek için yalnızca “==” operatörü yerine daha karmaşık bir araca ihtiyaç vardır. equals() yöntemidir. Java programlama dilinde sınıf hiyerarşisinin en üst noktasında yer alan Object sınıfından gelir. Yaklaşan Java kod parçacığını kontrol edelim;

public class Car {

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;
    
}

Car car1 = new Car("Audi","R8", 2023);
Car car2 = new Car("Audi","R8", 2023);
Car car3 = new Car("Audi","R8", 2021);

System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));

public boolean equals(Object obj) {
    return (this == obj);
}

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
    
}

İkinci satırda nesnelerin kimliğini kontrol etmeye başlıyoruz. Nesne referansı o boşsa veya her iki nesne referansı farklı sınıflara ait nesneleri gösteriyorsa, doğrudan bu iki nesnenin aynı kimliğe sahip olmadığı ve eşit olmadığı söylenebilir. Eğer durum böyle değilse, nihayet nesnelerin gerçek yapılarını kontrol edebiliyoruz.

Yöntemin üçüncü satırında, yöntem Object sınıfından geçersiz kılındığından ve artık Car sınıfına ait olduğunu bildiğimiz için o referansını karşılık gelen sınıf türüne atıyoruz .

Son olarak, yöntemin son satırında iki nesnenin yapısı kontrol edilir ve bu, kimliğin tanımlandığı asıl satırdır. Basitçe, eğer markaların modelleri ve model yılları aynıysa, bu iki nesne eşittir. Diğer bir deyişle yapısal eşitlik budur.

Matematiksel eşitliğin özelliklerini ilkokulda öğrendik. Bu özelliklerden bazılarının equals() yöntemi için de geçerli olması gerekir. Bu, bu özelliklerin equals() yönteminin bileşenlerine eklenmesi gerektiği anlamına gelir . Bu özelliklere matematiksel eşitlik açısından ve Java'da eşitlik açısından bakalım;

Yansıma Özelliği

Yansıma özelliğinin matematiksel tanımında, tüm x gerçek sayıları için ;

x = x

Bir sayı kendisine eşittir.

Java eşitliği açısından, bir nesnenin kendisine eşit olması gerekir;

Car car1 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car1));

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return mileage < car.mileage && brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }

Simetrik Özellik

Simetrik özelliğin matematiksel tanımı açısından, tüm x ve y gerçek sayıları için ;

x = y ise , y = x

Eşitlik sırası önemli değil.

Java eşitliği açısından, eğer a nesnesi b nesnesine eşitse, o zaman b nesnesi a nesnesine eşit olmalıdır;

Car car1 = new Car("Audi","R8",2013);
Car car2 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Car)) return false;
        Car car = (Car) o;
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
}

public class SportCar extends Car {
    
    private String engineType;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof SportCar)) return false;
        SportCar sportCar = (SportCar) o;
        return engineType.equals(sportCar.engineType);
    }
}

Car car1 = new Car("Audi", "R8", 2012, 1000);
SportCar car2 = new SportCar("Audi", "R8", 2012, 1000, "4.0");
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

Geçişli Özellik

Geçişli özelliğin matematiksel tanımında, tüm x , y ve z gerçek sayıları için ;

x = y ve y = z ise , x = z

Aynı sayıya eşit iki sayı birbirine eşittir.

Java eşitliği açısından, a nesnesi b nesnesine ve b nesnesi c nesnesine eşitse, o zaman a nesnesi c nesnesine eşit olmalıdır;

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

public class SportCar extends Car {
    
    private Turbo turbo;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Car)) return false;
        if (!(o instanceof SportCar)) return o.equals(this);
        return super.equals(o) && ((SportCar) o).turbo == turbo;
    }
}

public class Turbo {
    private String power;

    public Turbo(String power) {
        this.power = power;
    }
}

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

Tutarlı Özellik

Matematiksel olarak tam karşılığı olmadığı için bu farklı bir özelliktir. Bunun için equals() yönteminin art arda yürütülmesi için aynı sonucu almamız gerekiyor.

Aşağıdaki kod parçasını inceleyelim;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = new Car("Audi", "R8", 2012, 1000);

for (int i = 0; i < 100000; i++) {
    System.out.println(car1.equals(car2));
}

@Override
public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Car)) return false;
    Car car = (Car) o;
    Random random = new Random();
    return brand.equals(car.brand) && model.equals(car.model) && (random.nextInt(10) + 2013) == car.modelYear;
}

Not: Bu örnek beni pek tatmin etmedi. Kapsamlı bir araştırmadan sonra daha iyisiyle değiştireceğim. Equals() yönteminin Java belgelerinde " sürekli geri dönen veya sürekli geri dönen birden çok çağrı " olduğu açıkça söylenir. Bu ifade için somut bir ihlal örneği olmalıdır.x.equals(y)truefalse

Boş Parametre Özelliği

Bu mülkü böyle çağırmayı seçiyorum.

Lütfen aşağıdaki kod parçasına bakın;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = null;
System.out.println(car1.equals(car2));

Eşitliğin doğru bir şekilde hesaplanmasını sağlamak için bir equals() yöntemi uygulamasının hangi özelliklere sahip olması gerektiği artık açıktır. Ayrıca, olası ihlal senaryolarını gördük, dolayısıyla artık kod kalitesi ve iş mantığı adına bunlardan kaçınabiliriz.

Bugün birçok IDE, mümkün olan en iyi equals() yöntemi uygulamalarını oluşturmak için üretim mekanizmaları sağlar. equals() yöntemini uygulamak yerine çoğu zaman bunlara bağlı kalmak iyi bir fikirdir . Intellij IDEA'dan bir örnek görelim ;

@Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Car car = (Car) o;
       return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear) && mileage.equals(car.mileage);
   }

İlk satırda, referans eşitliği kontrol edilir, çünkü iki nesne referansı aynı nesneyi gösterebilir.

İkinci satırda Null Parameter Özelliğinin doğrulanması sağlanır ve ayrıca Geçişli Özellik ve Simetrik Özellik ihlalini önlemek için referanslarla işaret edilen nesnelerin sınıf tipini kontrol ederek aynı if koşulu bir guard if yan tümcesi görevi görür. .

Üçüncü satırda, Car nesnesinin özelliklerine ulaşmak için nesne referansı, Car nesnesinin özelliklerine ulaşmak için örneğimizde Car olan karşılık gelen sınıf tipine dönüştürülür.

Son olarak dördüncü satırda yapısal eşitlik açısından eşitlik kontrol edilir.

equals() yöntemi oluşturma için başka şablonlar da vardır . Ayrıca gidip kontrol edebilirsiniz.

Uygulamayı kendi başınıza yazmak isterseniz, uygulamanın tüm equals() yöntem özelliği için geçerliliği sağlaması gerektiğini lütfen unutmayın.

Makalenin sonuna geldiğimizde, hala kapatılması gereken çok önemli bir boşluk var. IDE'nizi açın ve equals() yöntemini oluşturmaya çalışın. hashCode() yöntemi olmadan sadece equals() yöntemi oluşturma seçeneğinin olmadığını göreceksiniz . Java programlama dilindeki bazı koleksiyon türleri nedeniyle bu iki yöntemi birbirinden ayırmamak çok iyi bir uygulamadır. Resmi Java belgelerinde , equals() yöntemi geçersiz kılındığında genellikle hashCode() yöntemini geçersiz kılmanın gerekli olduğu da söylenir . Diğer yazımda hashing ve hashCode() yöntemi ile ilgili bilgileri vermek istiyorum .

Ayrıca, JPA'nın varlık sınıfları gibi bazı istisnai durumlar için bazen equals() yöntemi özelliklerine bağlı kalmamamız gerekebilir . Ayrıca kendi makalesini hak ediyor.

Sonuç olarak

Doğru equals() yöntemini uygulamak için tüm önemli noktalar bu makalede ele alınmaktadır. Ayrıca equals() yöntemi özelliklerinin geçerliliğini test etmek için ilgili birim testlerini yazmanızı tavsiye ederim . Web üzerinde kurulabilecek diğer kaynaklara bakmayı unutmayın.

İlk etapta nesne yönelimli tasarımın temellerinden olduğu için çok kolay ve ilkel bir konu gibi görünüyor. Ancak detaylardan da görüldüğü üzere bu temel kavramları atlamamak ve tam olarak anlamak oldukça önemlidir.