1

I tried to perform a comparison between two objects of the same class. Actually, I wanted to compare the content of the both the objects. Here, the objects are of class Student. In class Student I have overridden the equals() method as shown below. By doing so, will my intention be accomplished (compare the names and birthdays of both students)? If not what is happening here? The problem is that I don't get the answer I expect. The output is false even though it must be true.

public class Main {

    public static void main(String[] args) {
        Student a = new Student("John", "Johnny");
        Student b = new Student("John", "Johnny");
        a.setBirthDate(10, 10, 10);
        b.setBirthDate(10, 10, 10);
        boolean ans = Student.equals(a, b);
        System.out.println(ans);
    }

}
public class Date {
    public int day;
    public int month;
    public int year;
    
    public Date(int d, int m, int y) {
        this.day = d;
        this.month = m;
        this.year = y;
    }
}
public class Student{
    private String firstName;
    private String lastName;
    private Date birthDate;
    
    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    public void setBirthDate(int day, int month, int year) {
        Date b_day = new Date(day, month, year);
        birthDate = b_day;
    }
}


@Override
    public boolean equals(Object student) {
        System.out.println(this.firstName);
        System.out.println(this.lastName);
        System.out.println(this.birthDate);
        
        System.out.println(((Student)student).firstName);
        System.out.println(((Student)student).lastName);
        System.out.println(((Student)student).birthDate);
        return super.equals(student);
        
    }

I have overridden the equals method as follows. But still I face the same issue. I suspect that there's something wrong with the Date class. But the problem is that I'm not quite sure of it. Also, I don't understand how to remedy the problem. Can someone please tell me what's wrong here. Thanks in advance.

  • 2
    The issue is you overwrote `equals`, but didn't add any additional checks. Currently, it just prints the values of each object. The line `return super.equals(student);` is equivalent to not overriding the method in the first place. – Locke Feb 01 '21 at 18:34

2 Answers2

2

You can structure your equals method similarly to an if statement. You just need to cast the other argument first before your can compare their fields.

@Override
public boolean equals(Object o) {
    // Check if the other object is also a Student
    if (o instanceof Student) {
        // Now that we know o is a student, we can safely cast it
        Student other = (Student) o;

        // Similarly to how you would write an if statement you can compare each individual field.
        // Thanks to inheritance, we defer the equality check of each field to its own implementation
        return this.firstName.equals(other.firstName)
            && this.lastName.equals(other.lastName)
            && this.birthDate.equals(other.birthDate);
    }

    // Other object was not a student
    return false;
}

You then need to go write something similar in Date so that when you compare birthDate, it will know what to do.

You can also take this one step farther by using Objects.equals(a, b) instead of a.equals(b). This will ensure you do not get a nullPointerException if a happens to be null when comparing them. However, since this looks to be a school project I imagine you may be expected to either check manually or assume the values will not be null instead of using the standard library.

Locke
  • 7,626
  • 2
  • 21
  • 41
  • Actually, its better to use `Objects.equals` incase of `null` possibility. Further `hashCode` is needed for `equals` to work. `hashCode` is uber requirement and `equals` as the granular requirement. `hashCode` identifies the `bucket` and `equals` checks for equality within the identified bucket. – Thiyanesh Feb 01 '21 at 19:01
  • @HorseAlso while `hashCode` is important, it is in no way needed for `equals` to function. Reference: https://stackoverflow.com/a/4179023/5987669 It sounds like you are describing `hashCode`'s usage in a `HashMap`. While it is necessary for a hashmap to function correctly, it is not necessary for direct comparisons. Also, I mentioned `Objects.equals` in the second paragraph, but left it out of the code sample to make it easier to read for a beginner and highlight the usage of inheritance. – Locke Feb 01 '21 at 19:09
  • Thank you for pointing about `Objet.equals` in your explanation. Apologies, i missed it. Yes, i was talking about `HashMap` and it's derivative like `ConcurrentHashMap`. It is recommended to override `hashCode` as we can't control the usage of our class by others. So, it is better to seal the class with possibly known good practices to avoid future issues. – Thiyanesh Feb 01 '21 at 19:21
0

For equals to work, you need to override hashCode also. Further equality check needs actual comparison of your objects.

Also, any related objects also has to implement hashCode and equals method. In this case Date.

Possible code

import java.util.Objects;

class Date {

    public int day;
    public int month;
    public int year;

    public Date(int d, int m, int y) {
        this.day = d;
        this.month = m;
        this.year = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Date date = (Date) o;
        return day == date.day &&
            month == date.month &&
            year == date.year;
    }

    @Override
    public int hashCode() {
        return Objects.hash(day, month, year);
    }
}

public class Student {

    private final String firstName;
    private final String lastName;
    private Date birthDate;

    public Student(String firstName, String lastName, Date birthDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthDate = birthDate;
    }

    public static void main(String[] args) {
        System.out.println(new Student("John", "Johnny", new Date(10, 10, 10))
            .equals(new Student("John", "Johnny", new Date(10, 10, 10))));
        System.out.println(new Student("John", "Johnny", new Date(11, 10, 10))
            .equals(new Student("John", "Johnny", new Date(10, 10, 10))));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return Objects.equals(firstName, student.firstName) &&
            Objects.equals(lastName, student.lastName) &&
            Objects.equals(birthDate, student.birthDate);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, birthDate);
    }
}
Thiyanesh
  • 2,360
  • 1
  • 4
  • 11
  • 1
    Implementing `hashCode` will have no effect on the functionality of `equals`. It defaults to comparing the object address (Ex: `this == other`). Implementing `hashCode` would only work if it compared hashes, but it would still be unreliable due to potential hash collisions. – Locke Feb 01 '21 at 18:39
  • Just edit your `equals` method to use getters, as the variables are private. – AP11 Feb 01 '21 at 18:42
  • Since it is a local method it is able to access private members directly. I would recommend avoiding the getters unless there is some functionality you might miss out on. – Locke Feb 01 '21 at 18:44
  • @AP11, actually this is will be an interesting discussion to have. Generally i prefer using `private` fields inside `hashCode` and `equals` as it resembles the `actual state` rather than a `view`. getters provide a `view`. But if `view` is the expectation, then even the `hashCode` should use the `view`. Otherwise there will be discrepancy in some use cases(say map). As @Locke mentioned, i will also recommend to use attributes directly always (with no exception :) ). – Thiyanesh Feb 01 '21 at 19:33