In the collection test, I create a class named Name and override the equals method, like following,
class Name implements Comparable<Name>{
private String firstName, lastName;
Name(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
public String toString(){
return firstName + " "+lastName;
}
public boolean equals(Name name){
return firstName.equals(name.firstName) && lastName.equals(name.lastName);
}
public int hashCode(){
return firstName.hashCode();
}
But When I test the remove() function in collection, it was false and the Name("Andy","Light") is still in the collection. What is the wrong with my code?
public static void main(String[] args){
Collection c = new HashSet();
c.add("hello");
c.add(new Name("Andy","Light"));
c.add(new Integer(100));
c.remove("hello");
c.remove(new Integer(100));
System.out.println(c.remove(new Name("Andy","Light")));
System.out.println(c);
}
There is a comment and an answer that says your hashCode() method is not consistent with equals() because you didn't include lastName in the hash code calculation. They are both wrong.
The hashCode() implementation is allowed to use a subset of the values used by equals(). It will cause more hashcode collisions that way, offsetting improved speed of hashCode() vs degraded performance of hash-buckets. A subset hashcode may be ok, it depends on likelihood of Name objects having same firstName.
Your problem is that the signature of equals() is wrong. It has to be boolean equals(Object).
boolean equals(Name) is not an override of boolean equals(Object), so you didn't actually override/implement the equals() method, and as such ended up with hashCode() being inconsistent with equals() (but not for the reason the others said).
If you add the #Override annotation, the compiler would have caught this problem. Always use the annotation.
Change to:
#Override
public boolean equals(Object obj) {
if (! (obj instanceOf Name))
return false;
Name that = (Name)obj;
return this.firstName.equals(that.firstName) && this.lastName.equals(that.lastName);
}
#Override
public int hashCode() {
return this.firstName.hashCode();
}
This of course assumes that neither can be null.
As #MickMnemonic says in a comment:
It's considered bad practice to leave out fields that are included in equals()
To include lastName in the calculation, use Objects.hash():
#Override
public int hashCode() {
return Objects.hash(this.firstName, this.lastName);
}
Also, as #StephenB said in a comment:
You also need to add a compareTo method because you are implementing Comparable<Name>.
Here you use Name as a parameter, not Object, because of the generic type argument to Comparable.
Example (if sorting by first name before last name):
#Override
public int compareTo(Name that) {
int cmp = this.firstName.compareTo(that.firstName);
if (cmp == 0)
cmp = this.lastName.compareTo(that.lastName);
return cmp;
}
That implements a firstName/lastName lexicographical ordering. You may want to use compareToIgnoreCase() or maybe a Collator for correct localized ordering.
Related
hopefully this doesn't make me seem to be an idiot but I seem to be failing on a simple exercise where I have to compare two objects to check if they are equal, my Java class is below along with the error message I'm getting from the exercise. Would anyone know how to solve it? Thanks in advance.
import java.util.Objects;
public class Person {
private String name;
private SimpleDate birthday;
private int height;
private int weight;
public Person(String name, SimpleDate birthday, int height, int weight) {
this.name = name;
this.birthday = birthday;
this.height = height;
this.weight = weight;
hashCode();
}
public String getName(){
return this.name;
}
public SimpleDate getBirthday(){
return this.birthday;
}
public Integer getHeight(){
return this.height;
}
public Integer getWeight(){
return this.weight;
}
// implement an equals method here for checking the equality of objects
#Override
public boolean equals(Object compared){
return this==compared;
}
}
Error message
Joshua Bloch in Effective Java gives guidelines on how to write a nice .equals(). Here's the excerpt directly from the book:
Use the == operator to check if the argument is a reference to this object.
Use the instanceof operator to check if the argument has the correct type.
Cast the argument to the correct type.
For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.
When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?
public boolean equals(Object o) {
if(o == this) {
return true;
}
if(!(o instance of Person)) {
return false;
}
//you comparing logic here
}
You have to make sure that equals follows its contract (it's an equivalence relation). See it's documentation for more details. Also, override the hashcode() method.
You equals method is written wrongly as it just compares the location of objects in memory. That's why your tests are failing.
You changed behaviour of equals to == here:
#Override
public boolean equals(Object compared){
return this==compared;
}
and now here is already an answer - https://stackoverflow.com/a/13387787/7505731
I have a base BusinessObject abstract class which implements Comparable by comparing their long id fields. Now imagine I extend it with, say, Person and then I extend Person with Worker. So we have:
BusinessObject < Person < Worker
So now I override the compareTo(BusinessObject) in business object in Person (compare the names) and Worker (compares the job name, then person name).
Now I do something like:
List<BusinessObject> collection = new ArrayList<>();
collection.add(new Worker(1L, "Steve", "janitor"));
collection.add(new Worker(2L, "Mark", "plumber"));
collection.add(new Person(3L, "Dave"));
Collections.sort(collection);
System.out.println(collection);
By logging I can see the calls that are made:
Worker.compareTo()
Person.compareTo()
So, it means sorting methods are mixed which is obviously not good. So what is the correct way to implement Comparable with inheritance so that the method called depends on the generic type of the collection:
if collection is a List then always use BusinessObject.compareTo()
if collection is a List then always use Person.compareTo()
if collection is a List then always use Worker.compareTo()
Here's my code:
public abstract class BusinessObject implements HasId, HasText, Comparable<BusinessObject> {
protected #Nullable Long id;
public BusinessObject(#Nullable Long id) {
this.id = id;
}
#Override
public #Nullable Long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Override
public int compareTo(#NonNull BusinessObject o) {
System.out.println("compareTo BusinessObject");
return Long.compare(id, o.id);
}
#Override
public String toString() {
return String.format("BusinessObject#%d", id);
}
}
public class Person extends BusinessObject {
protected final String name;
public Person(#Nullable Long id, String name) {
super(id);
this.name = name;
}
#NonNull
#Override
public String getText(Context context) {
return null;
}
#Override
public String toString() {
return String.format("Person#%d (%s)", id, name);
}
#Override
public int compareTo(#NonNull BusinessObject o) {
if (o instanceof Person) {
System.out.println("compareTo Person");
Person po = (Person) o;
return name.compareTo(po.name);
}
else {
return super.compareTo(o);
}
}
}
public class Worker extends Person {
protected final String job;
public Worker(#Nullable Long id, String name, String job) {
super(id, name);
this.job = job;
}
#Override
public String toString() {
return String.format("Worker#%d (%s-%s)", id, name, job);
}
#Override
public int compareTo(#NonNull BusinessObject o) {
if (o instanceof Worker) {
System.out.println("compareTo Worker");
Worker wo = (Worker) o;
return String.format("%s%s", name, job).compareTo(String.format("%s%s", wo.name, wo.job));
}
else {
return super.compareTo(o);
}
}
}
Your design is inherently flawed.
First, you have BusinessObject which implements Comparable<BusinessObject>. What this says is that business objects have a natural order, dictated by their ID.
Then you override the method in Person to compare the name. What you are saying here is that "well, if both business objects being compared are persons, then the natural order is different for them".
That doesn't make sense. Either sorting by ID is natural for business objects, or it isn't. But it can't be natural for some of them, but not for others.
More formally, your override breaks the transitivity requirement of Comparable. Imagine you have a list consisting of three business objects: Worker Alice (ID 3), Worker Bob (ID 1), and Company ACME (ID 2). Transitivity says that if x < y && y < z then x < z must also be true. However, your compare method will give Bob < ACME (compare by IDs) and ACME < Alice (compare by IDs), but the transitive Bob < Alice is false (compare by names). You have violated the requirement documented in the compareTo method:
The implementor must also ensure that the relation is transitive: (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.
Bottom line: Comparable is not meant to work with open inheritance. Don't do that. Instead, use explicit Comparators for your collections where they make sense.
As a side note, and this may just be due to shortened code in your examples, but you should always override equals to be consistent with compareTo (i.e. x.equals(y) implies x.compareTo(y) == 0 and vice versa). Anything else means your objects behave in an unintuitive manner. (And then you need to override hashCode to fulfill its own contract w.r.t. equals. Overriding equals but not hashCode is a wonderful way to introduce really subtle bugs into Java programs.)
I think it is simply not possible to do, what you want to do.
The generic type does and can not have any effect on the methods used from the items.
But you might be able to solve your problem. Just do not use the compareTo methods but instead implement Comparators as needed.
There is a version of Collections.sort() which takes a Comparator as parameter.
Obviously you want to sort the items with different sort criterias depending on the list you are using. So I think the Comparator would be the best solution.
I haven't overridden much of hashCode() and equals() methods so I may be wrong
My question is for the last line where
dep1.equals(emp2) is being compiled successfully(why) (I am expecting compilation error as they have different types) and after compiling I get following
15 15 false
where I am expecting 15 15 true since I am checking the hashcode in the equals method.
class Employee {
private String name;
private int id;
public Employee(String name, int id) {
this.name = name;
this.id = id;
}
public int hashCode() {
return this.id;
}
public boolean equals(Employee employee) {
return this.hashCode() == employee.hashCode();
}
public int getEmployeeId() {
return this.id;
}
}
class Department {
private String name;
private int id;
public Department(String name, int id) {
this.name = name;
this.id = id;
}
public int hashCode() {
return this.id;
}
public boolean equals(Department department) {
return this.hashCode() == department.hashCode();
}
public int getDepartmentId() {
return this.id;
}
}
public class JavaCollections {
public static void main(String args[]) {
Employee emp2 = new Employee("Second Employee", 15);
Department dep1 = new Department("Department One", 15);
System.out.println(dep1.hashCode()+" "+emp2.hashCode()+" " + dep1.equals(emp2));
}
}
First, for the reason why this compiles: all classes in Java inherit from java.lang.Object, which defines equals(Object) method, and provides a default implementation. This is the method that you call when you compare an Employee and a Department, not one of the overloads that you have provided.
Your equals code compiles fine, because the compiler does not know that you thought you were overriding equals when you actually didn't. The compiler thinks that you want to make a new method
public boolean equals(Department department)
to compare Department objects to other Department objects.
If you are writing a code that overrides a method of a superclass, add #Override annotation to it, like this:
#Override
public boolean equals(Department department)
Now the compiler will correctly complain to you that your method does not in fact override a method in its base class, alerting you to the problem at compile time.
To fix your code change the signatures of equals to take Object, add #Override, check for null and for the correct type, do the cast, and then do the actual comparison:
#Override
public boolean equals(Department obj) {
if (obj == null || !(obj instanceof Department)) {
return false;
}
Department dept = (Department)obj
return dept.id == id;
}
Note: Implementing equals like this
return this.hashCode() == department.hashCode();
is very fragile. Although it works in your case, when hash code is a unique ID of the object, this wouldn't survive a code refactoring when hashCode is replaced with some other implementation, for example, an implementation that considers both id and name. If you want to rely on comparing IDs, compare IDs directly, without calling hashCode to get them.
That's because both of classes Employee and Department still have not overriden methods public boolean equals(Object obj) inherited from Object class.
Exactly this method is invoked in dep1.equals(emp2), not public boolean equals(Department department).
More specifically, read JLS:
An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:
...
The signature of mC is a subsignature (§8.4.2) of the signature of mA.
In this case boolean equals(Department department) is not subsignature of boolean equals(Object obj).
First, this code dep1.equals(emp2) calls default implementation of Object class.
Second, U didnt overrides the default implementation in both of your class becoz u cant override equal method for specific customizied types.
If u need ur answer to be 15 15 true
replace
public boolean equals(Department department) {
return this.hashCode() == department.hashCode();
}
by
#override
public boolean equals(Object department) {
return this.hashCode() == department.hashCode();
}
If I need to use InfoName as the key of a HashMap, do I need to define my own hashCode() and equals() method? I think it's not necessary, since the String name variable will be enough to make sure each object of InfoName is different.
public class InfoName {
enum Type {
company, product;
}
public String name;
public Type type;
public InfoName(String name, Type type) {
this.name = name;
this.type = type;
}
}
the String "name" variable will be enough to make sure each object of
InfoName is different
If you only want to use the name in the InfoName, then just make String type name as the key as it already override equals() and hashCode() .
OR
You need to override equals() and hashCode() in InfoName class, else how would JVM knows on which attribute/criteria you are using for hashing and equality check.
If you are sure to have InfoName as key you need to override both.
You an have something like
public class Test {
enum Type {
company, product;
}
public String name;
public Type type;
public Test(String name, Type type) {
this.name = name;
this.type = type;
}
#Override
public boolean equals(Object o) {//or do what you like
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Test test = (Test) o;
if (!name.equals(test.name)) return false;
if (type != test.type) return false;
return true;
}
#Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + type.hashCode();
return result;
}
}
Basically your editor provides features of overriding hashcode and equals. Have a look here Why do I need to override the equals and hashCode methods in Java?enter link description here
I think it's not necessary, since the String "name" variable will be
enough to make sure each object of InfoName is different.
I would recommend not to use name as the hash key because it seems a bad candidate. I mean could you have multiple objects with the same product name? In that case you would have many collisions.
I keep getting the error: Student is not abstract and does not override abstract method compareTo(java.lang.Object) in java.lang.Comparable
Why is this? What this is trying to accomplish is taking a list of students and comparing them by GPA.
public class Student implements Comparable
{
private String name;
private double gpa;
public Student(String name, double gpa)
{
this.name = name;
this.gpa = gpa;
}
public String getName()
{
return name;
}
public double getGpa()
{
return gpa;
}
public String toString()
{
return "Name: " + name + " GPA: " + gpa;
}
public double compareTo(Object other)
{
Student filler = (Student)other;
if(this.getGpa() < filler.getGpa())
return -1;
else if(this.getGpa() == filler.getGpa())
return 0;
else
return 1;
}
}
To answer your question directly, you need to change the return type of compareTo() from double to int.
There are also several other modifications you should make to improve your code:
implement Comparable<Student> instead of just Comparable. This makes it so you can write public int compareTo(Student other) and only allows calling compareTo() with other Student references.
Add #Override annotations before both toString() and compareTo(). This annotation helps you avoid some common errors which the compiler cannot catch.
compareTo method returns an int and not a double.
Also using an Override annotation helps to be sure that you are overriding the method correctly. So change this
public double compareTo(Object other)
to
#Override
public int compareTo(Object other)
public double compareTo(Object other)
should be
#Override
public int compareTo(T other)
Take a look at the Comparable interface. It requires a method called compareTo, that takes an argument of type T (generic parameter), that returns an int. The method you have created doesn't implement the method specified in the interface, which is why the Java compiler is complaining.
Since the Comparable interface is genericized, you should take advantage of generics and make your class implement Comparable<Student>. When you do that, the signature of compareTo becomes:
#Override
public int compareTo(Student other)
Which is better than a raw Object, since you don't have to cast, and more importantly, you don't accidentally end up passing in something that is not a Student.
One more thing: use the #Override annotation when you implement methods from the interface. Assuming you're using a halfway-decent IDE, you would have seen an error if you had:
#Override
public double compareTo(Object other)
Since there is no method with that signature in the interface.