How to override hashcode and equals for transient object in hibernate? - java

I have 3 entities, student, grade and class. Code shows below. It is just a sample.
Student class
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
private String fullName;
private long studentId;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getFullName() == null) ? 0 : getFullName().hashCode());
result = prime * result + (int) (getStudentId() ^ (getStudentId() >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof Student)
return false;
test other = (test) obj;
if (getFullName() == null) {
if (other.getFullName() != null)
return false;
} else if (!getFullName().equals(other.getFullName()))
return false;
if (getStudentId() != other.getStudentId())
return false;
return true;
}
}
SchoolClass class:
public class SchoolClass implements Serializable{
private static final long serialVersionUID = 1L;
private String className;
private long classId;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (getClassId() ^ (getClassId() >>> 32));
result = prime * result + ((getClassName() == null) ? 0 : getClassName().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof SchoolClass)
return false;
SchoolClass other = (SchoolClass) obj;
if (getClassId() != other.getClassId())
return false;
if (getClassName() == null) {
if (other.getClassName() != null)
return false;
} else if (!getClassName().equals(other.getClassName()))
return false;
return true;
}
}
Grade Class:
public class Grade implements Serializable{
private static final long serialVersionUID = 1L;
private SchoolClass schoolClass;
private Student student;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getSchoolClass() == null) ? 0 : getSchoolClass().hashCode());
result = prime * result + ((getStudent() == null) ? 0 : getStudent().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof Grade)
return false;
Grade other = (Grade) obj;
if (getSchoolClass() == null) {
if (other.getSchoolClass() != null)
return false;
} else if (!getSchoolClass().equals(other.getSchoolClass()))
return false;
if (getStudent() == null) {
if (other.getStudent() != null)
return false;
} else if (!getStudent().equals(other.getStudent()))
return false;
return true;
}
}
So I checked hibernate doc for hashcode and equals, it works perfectly fine for entity that exists in DB. The problem I have is for new transient entity objects before save to db. I did separate tests specifically on Student and SchoolClass using HashSet, the size of set won't increase if it tries to add same object.
Student s1 = studentRepo.findById(studentId).get();
SchoolClass sc = scRepo.findById(classId).get();
Grade grade = new Grade();
grade.setStudent(s1);
grade.setSchoolClass(sc);
grades.add(grade);
logger.info(grades.size());
Here I have a new Set of grades and preparing this set and save to db. Here comes the problem, this set will contains duplicate grade object. Meaning there will be 2 entries with same student and same class. In grade class, I override its hashcode and equals to Student and SchoolClass, it should NOT have duplicate entries. I figure it probably because the new Grade object is in transient state? Not really sure what is the cause.
Of course I can do unique check for grade in an manual way, but hashcode and equals should be the right way to go, isn't it?
So how to solve this? Need some help.

Thanks to #samabcde. He is right, I missed ! on the condition check.

Related

Comparing two instances of class having big number of attributes

I have a class having more than 30 attributes.
I want to override the equals method in order to compare two instance of my class.
However I want to avoid re-write all the 30 attributes in my method as this
#Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Address address = (Address) o;
return Objects.equals(this.attr1, address.attr1) &&
Objects.equals(this.attr2, address.attr2) &&
Objects.equals(this.attr3, address.attr3) &&
......
Objects.equals(this.attr30, address.attr30);
}
Have you a more simple and proper way ?
Well, this is basically the type of boilerplate code that is necessary. Luckily, there are lots of developers just as annoyed of writing such code as you are. For reasons like that, Project Lombok was founded.
Please see this link.
As an example, see the following two code snippets extracted from the page that I linked above:
Lombok
import lombok.EqualsAndHashCode;
#EqualsAndHashCode(exclude={"id", "shape"})
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
#EqualsAndHashCode(callSuper=true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
}
Vanilla Java
import java.util.Arrays;
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.name;
}
#Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof EqualsAndHashCodeExample)) return false;
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (Double.compare(this.score, other.score) != 0) return false;
if (!Arrays.deepEquals(this.tags, other.tags)) return false;
return true;
}
#Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.score);
result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.tags);
return result;
}
protected boolean canEqual(Object other) {
return other instanceof EqualsAndHashCodeExample;
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
#Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Square)) return false;
Square other = (Square) o;
if (!other.canEqual((Object)this)) return false;
if (!super.equals(o)) return false;
if (this.width != other.width) return false;
if (this.height != other.height) return false;
return true;
}
#Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + super.hashCode();
result = (result*PRIME) + this.width;
result = (result*PRIME) + this.height;
return result;
}
protected boolean canEqual(Object other) {
return other instanceof Square;
}
}
}
If you like this approach, I'd recommend checking out Project Lombok in its entirety. It really helps cleaning up your code!
!!! Beware !!!
In order to be able to actually use methods generated by Lombok, you need to install the Lombok plugin into your IDE! Otherwise, your IDE won't know of the automatically generated methods.
You can use the Field class in the java.lang.reflect package like so:
#Override
public boolean equals(Object o) {
//instanceof check, null check, etc
Field[] fields = Address.class.getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
if (!field.get(this).equals(field.get((Address) o))) {
return false;
} //end if
} catch (IllegalAccessException e) {
//handle exception
} //end try catch
} //end for
return true;
} //equals
You have this method in apache commons library that uses reflection for compare them
org.apache.commons.lang.builder.EqualsBuilder.reflectionEquals(Object, Object)
Example
import org.apache.commons.lang.builder.EqualsBuilder;
public class MyObject {
...
#Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
}
You can use lombok project to auto-generate hashCode and equals methods at build time .
You can use Unitils http://www.unitils.org/cookbook.html
import static org.unitils.reflectionassert.ReflectionAssert.*;
// Exact field-by-field comparison
assertReflectionEquals(new Person("John", "Doe", new Address("New street", 5, "Brussels")),
new Person("John", "Doe", new Address("New street", 5, "Brussels"));

How to write equals and hashCode methods in java for attribute that is a list of other attributes

I have a HashMap where the key is a class and value is an integer. I need to check if an object of the class already exists in the map. I use containsKey(), but for some reason it does not work when I include attribute sideDish in the equals() and hashCode(). Here is my code for the classes:
OrderItem class:
#ToString
#Entity
#Table(name="OrderItem")
public class OrderItem implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Getter #Setter
private Long id;
#ManyToOne
#Getter #Setter
private Food food;
#ManyToMany
#Getter #Setter
private List<SideDish> sideDishes;
public OrderItem() {}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((food == null) ? 0 : food.hashCode());
result = prime * result + ((sideDishes == null) ? 0 : sideDishes.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderItem other = (OrderItem) obj;
if (food == null) {
if (other.food != null)
return false;
} else if (!food.equals(other.food))
return false;
if (sideDishes == null) {
if (other.sideDishes != null)
return false;
} else if (!sideDishes.equals(other.sideDishes))
return false;
return true;
}
}
Food class:
#ToString
#Entity
#Table(name="Food")
public class Food implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Getter #Setter
private Long id;
#Column(nullable = false, unique = true)
#NotNull(message = "Name cannot be null.")
#Getter #Setter
private String name;
#ManyToMany
#Getter #Setter
private List<SideDish> sidedishes;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((foodtype == null) ? 0 : foodtype.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Food other = (Food) obj;
if (foodtype == null) {
if (other.foodtype != null)
return false;
} else if (!foodtype.equals(other.foodtype))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
SideDish class:
#Entity
#ToString(exclude= {"id","dishtype"})
#Table(name="SideDish")
public class SideDish implements Serializable, Comparable<SideDish>{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
#Column(nullable = false, unique = true)
private String name;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SideDish other = (SideDish) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
For some reason, if I removethe sideDish attribute from equals() and hashCode() in the OrderItem class, it works perfectly.
But I also need sideDish to be checked as part of the object identity.
Here is how I use it:
HashMap<OrderItem, Integer> orderItemsToSend = new HashMap<OrderItem, Integer>();
for (Order order : orders) {
for (OrderItem orderItem : order.getOrderItems()) {
int numSimilarOrders = getNumOfSimilarOrders(orderItem, orders);
if(!orderItemsToSend.containsKey(orderItem)) {
orderItemsToSend.put(orderItem, numSimilarOrders);
}else {
System.out.println("Vec je dodat item koji isti kao: " + orderItem.getFood().getName());
}
}
}
In your OrderItem class, both your hashCode() and equals() depend on the property List<SideDish> sideDishes.
Thus, if sideDishes changes, so does the hashCode() (and so does equality).
A HashMap uses both hashCode() and equals() to store and find the object which is the key. It uses a concept called "hash buckets". If you put a key into a HashMap, and then the hashCode() changes, that object will be in the wrong hash bucket, and you won't be able to find it again.
A key is something which is used for lookup purposes - that's what the word "key" means. An important quality of a key, whether in a database, or a hashmap, is immutability. So in Java, that means an object which changes its hashCode() makes for a bad key.
It's a bit like if a file system did lookups by the hash of the filename, but then you changed the filename, but it didn't update the hash. You'd only find the file by doing a lookup with the old name.
This simple test program will illustrate the point.
We store 2 objects in a HashMap, and then change the hashCode(). The map still contains both objects, but now one of them cannot be found or used for lookup.
The solution is use some simple immutable object as the key, such as a Long of its database ID.
Sample output is below the code.
public class HashTest {
static class Hashable {
String name;
#Override
public int hashCode() {
return ((name == null) ? 0 : name.hashCode());
}
#Override
public boolean equals(Object object) {
return (object instanceof Hashable) && equals((Hashable) object);
}
private boolean equals(Hashable that) {
return Objects.equals(this.name, that.name);
}
#Override
public String toString() {
// Use identityHashCode() so we can really see which object is which
return "[" + name + ":" + System.identityHashCode(this) + "]";
}
}
public static void main(String[] args) {
Hashable one = new Hashable();
one.name = "one";
Hashable two = new Hashable();
two.name = "one";
print(one, two);
two.name = "two";
print(one, two);
HashMap<Hashable, Integer> map = new HashMap<>();
map.put(one, 1);
map.put(two, 2);
find(map, one, two);
one.name = "two"; // Let's confuse things
print(one, two);
find(map, one, two);
}
private static void print(Hashable one, Hashable two) {
System.out.print("Names:" + one.name + ":" + two.name);
System.out.print("\tHashcodes:" + one.hashCode() + ":" + two.hashCode());
System.out.println("\tEquals:" + one.equals(two));
}
private static void find(HashMap<Hashable, Integer> map, Hashable one, Hashable two) {
System.out.print(map);
System.out.print("\tFound: " + map.get(one));
System.out.println("\tFound: " + map.get(two));
}
}
Sample output:
Names:one:one Hashcodes:110182:110182 Equals:true
Names:one:two Hashcodes:110182:115276 Equals:false
{[one:366712642]=1, [two:1829164700]=2} Found: 1 Found: 2
Names:two:two Hashcodes:115276:115276 Equals:true
{[two:366712642]=1, [two:1829164700]=2} Found: 2 Found: 2

Overriding equals() for use with Linked List

I have two methods for looking up the index of a particular element in a linked list. I'm trying to use contais() and indexOf() to find the index. I am unsure how to override equals() to suit my need. One method finds the index based on surname and initials the other just the telephone number. Here are my methods:
#Override
public int lookupNumber(String surname, String initials) {
Entry entry1 = new Entry(surname, initials);
if (listDirectory.contains(entry1)) {
int index = listDirectory.indexOf(entry1);
return index;
}
else {
return -1;
}
}
#Override
public int lookupName(int extension) {
Entry entry1 = new Entry(Integer.toString(extension));
if (listDirectory.contains(entry1)) {
int index = listDirectory.indexOf(entry1);
return index;
}
else {
return -1;
}
}
I've tried to use the equals() and hashCode() that eclipse provides:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entry other = (Entry) obj;
if (extension == null) {
if (other.extension != null)
return false;
} else if (!extension.equals(other.extension))
return false;
if (initals == null) {
if (other.initals != null)
return false;
} else if (!initals.equals(other.initals))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
return true;
}
But it won't work for both my methods, as it compares all the variables in the objects not just the ones I need it to compare. What is the correct logic for this?
Help much appreciated, thanks.
Update - here is my full Entry class -
public class Entry {
private String surname;
private String initals;
private String extension;
public Entry(String surname, String initals,String extension) {
this.surname = surname;
this.initals = initals;
this.extension = extension;
}
public Entry(String surname,String initals){
this.surname = surname;
this.initals = initals;
}
public Entry(String extension) {
this.extension = extension;
}
public String getInitals(){
return initals;
}
public String getSurname(){
return surname;
}
public String getExtension(){
return extension;
}
public void setExtension(String extension) {
this.extension = extension;
}
public String toString(){
return surname + "\t " + initals + "\t" + extension;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((extension == null) ? 0 : extension.hashCode());
result = prime * result + ((initals == null) ? 0 : initals.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entry other = (Entry) obj;
if (extension == null) {
if (other.extension != null)
return false;
} else if (!extension.equals(other.extension))
return false;
if (initals == null) {
if (other.initals != null)
return false;
} else if (!initals.equals(other.initals))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
return true;
}
}
Update - My problem is in my lookupNumber() method I want to find elements by (initials,surname) whereas in my lookupNumber() I want to find elements by (extension). How would I do this?
The Java Object class contains an equals() method of its own, and the Entry class should be the blueprint for objects of type Entry. Java inheritance is such that all classes inherit from the Object class, which is of little use to many of us. Really all you would need is
#Override
public boolean equals(Entry otherEntry)
{
if(!(this.surname.equals(otherEntry.getSurname()))
return false;
else if(!(this.initials.equals(otherEntry.getInitials()))
return false;
else if(!(this.extension.equals(otherEntry.getExtension()))
return false;
else
return true;
}
The above code just checks for inequality amongst the instance variables, returning false upon any one not equal.

hashCode(), equals(Object) and compareTo(Class)

I've following the following Vertex class and it implements equals, hashCode and compareTo method. Even then my HashMap returns null. I don't know why?
public class Vertex implements Comparable<Vertex> {
int id;
public Vertex(int number) {
id = number;
}
public boolean equals(Object other) {
if (other == null)
return false;
else if (other.getClass() != this.getClass())
return false;
else {
Vertex copy = (Vertex) other;
if (copy.id == this.id)
return true;
else
return false;
}
}
public int hasCode() {
int prime = 31;
int smallPrime = 3;
int hashCode = this.id ^ smallPrime - prime * this.hasCode();
return hashCode;
}
public int compareTo(Vertex other) {
if (this.id < other.id)
return -1;
else if (this.id > other.id)
return 1;
else
return 0;
}
}
Your method is called hasCode(). Make it hashCode() instead.
I'd suggest using your IDE to automatically generate hashCode() and equals(..). That will generate the proper methods (right now you have a recursive call in hashCode())
Also, in your equals() method
else if(other.getClass()!=this.getClass())
return false;
can be changed to
else if(!(other instanceof Vertex))
return false;
Try this based on what Integer does. Note: the use of #Override would have shown you were overriding the wrong method.
public class Vertex {
final int id;
public Vertex(int number){
id = number;
}
#Override
public boolean equals(Object other){
if(!(other instanceof Vertex)) return false;
return ((Vertex)other).id == id;
}
#Override
public int hashCode() { return id; }
}

object match , increase and add to list

I have a type of object which contains id and points information.
I have a list of that object. I need to add each new id, if any old id enters i need to add 1 to the points to the old entry .then add to the list.
Here is my pojo
public class Salary {
int id;
int points;
public Salary(int id, int points) {
this.id = id;
this.points = points;
}
// getters and setters
}
Here is the situation
class info{
public static void main(String[] args) {
List<Salary> salaries = new ArrayList<>();
salaries.add(new Salary(1,100));
salaries.add(new Salary(2,200));
Salary newSalary = new Salary(1,200);
}
}
Since the id is same i need to make 100 to 101 and update the list , how can i do that with java 8 ?
In your Salary class you should implement equals and hashcode as follows:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Salary other = (Salary) obj;
if (id != other.id)
return false;
return true;
}
and in your main method add this:
Salary newSalary = new Salary(1,200);
for(Salary s: salaries){
if(s.equals(newSalary)){
s.points++;
}
}

Categories