Difference between 2 lists of objects in Java - java

I have a obj type MyObj as follow:
class MyObj{
String id;
String username;
String fullName;
String age;
//getters & setters
}
Suppose we have 2 lists containing different number of elements like so:
List<MyObj> listA
List<MyObj> listB
I have a generic method that detects elements from listA that are missing in listB:
public static <T> List<T> getListDifference(List<T> list1, List<T> list2) {
Collection<T> first = new HashSet<T>(list1);
Collection<T> second = new HashSet<T>(list2);
first.removeAll(second);
return new ArrayList<T>(first);
}
If objects from listA and listB has the same fields for all items, everything works just fine.
The problem is that some items has only id and username but others can have fullName or age too, and as result this method doesn't work anymore. I'd like to keep the same logic, considering only id field because it's present in all objects.
One obvious method is to copy only object's id field in other List<String> and work with obtained lists to detect elements, then just search for obj from both lists by id. This method has a big complexity, because of multiple iterations. Is there a short way to achieve this?

As # Kevin Esche mentioned, you have to implement equals() and hashCode() in your MyObj POJO, and according to your situation, they should be:
#Override
public int hashCode() {
return Objects.hash(id);
}
#Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof MyObj)) return false;
MyObj myObj= (MyObj) obj;
return Objects.equals(id, myObj.id);
}
That will work even if ages are different and the ids are the same for example.

override MyObjs equals method:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyObj myObj = (MyObj) o;
return (id != null ? !id.equals(myObj.id) : myObj.id != null);
}
IntelliJ/Android Studio have helpers for automatically generating equals and hashCode where you can choose the necessary properties.

You have to implement equals and hashCode to work with sets. Sets needs a way of recognising if one object is equal to another, and default behaviour is not gonna work here.
By the way, I suggest using retainAll method on a list, check this out.

Related

Java - get propertie names referenced from method

So, I have this class:
public class Book {
private int id;
private String name;
private Something somebody;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
if (id != book.id && somebody.getId() != book.somebody.getId()) return false;
return true;
}
#Override
public int hashCode() {
return id;
}
}
I would like to get all properties used in this class in equals method - in this case, I would get "id" from Book (since name is not used in equals method), and I would also get "somebody.id" since this is also used in equals method as sub object.
I need this info, so I can serialize only this properties and then during de-serialization on another machine use only that to compare equals. Otherwise it would be too cumbersome to compare full objects for equals (if I have too many sub-properties).
If you are using the Eclipse IDE, I know it has auto-complete options for equals and hashcode that will generate code including comparison of all declared fields. I am not sure if Netbeans or other IDEs have similar functionality, but would be surprised if commonly-used IDEs did not.
Also, your equals method should not compare fields of other objects but invoke equals on them:
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false; // instanceof is fast these days
Book book = (Book) o;
if (id == book.id &&
// either ensure these are not null or use java.util.Objects.equals()
somebody.equals(book.somebody))
return true;
return false;
}
Furthermore, if your hashcode is merely using id then perhaps your equals can as well. If your object is immutable, then id is all the comparison you would need, and would be quite a bit more efficient. If that is not the case, then it is typical that the checks used in equals are reflected in your hashcode to help prevent hash collisions.

Merging two List of objects in java 8

I have a Java class Parent with 20 attributes (attrib1, attrib2 .. attrib20) and its corresponding getters and setters. Also I have two lists of Parent objects: list1 and list2.
Now I want to merge both lists and avoid duplicate objects based on attrib1 and attrib2.
Using Java 8:
List<Parent> result = Stream.concat(list1.stream(), list2.stream())
.distinct()
.collect(Collectors.toList());
But in which place I have to specify the attributes? Should I override hashCode and equals method?
If you want to implement equals and hashCode, the place to do it is inside the class Parent. Within that class add the methods like
#Override
public int hashCode() {
return Objects.hash(getAttrib1(), getAttrib2(), getAttrib3(),
// …
getAttrib19(), getAttrib20());
}
#Override
public boolean equals(Object obj) {
if(this==obj) return true;
if(!(obj instanceof Parent)) return false;
Parent p=(Parent) obj;
return Objects.equals(getAttrib1(), p.getAttrib1())
&& Objects.equals(getAttrib2(), p.getAttrib2())
&& Objects.equals(getAttrib3(), p.getAttrib3())
// …
&& Objects.equals(getAttrib19(), p.getAttrib19())
&& Objects.equals(getAttrib20(), p.getAttrib20());
}
If you did this, distinct() invoked on a Stream<Parent> will automatically do the right thing.
If you don’t want (or can’t) change the class Parent, there is no delegation mechanism for equality, but you may resort to ordering as that has a delegation mechanism:
Comparator<Parent> c=Comparator.comparing(Parent::getAttrib1)
.thenComparing(Parent::getAttrib2)
.thenComparing(Parent::getAttrib3)
// …
.thenComparing(Parent::getAttrib19)
.thenComparing(Parent::getAttrib20);
This defines an order based on the properties. It requires that the types of the attributes itself are comparable. If you have such a definition, you can use it to implement the equivalent of a distinct(), based on that Comparator:
List<Parent> result = Stream.concat(list1.stream(), list2.stream())
.filter(new TreeSet<>(c)::add)
.collect(Collectors.toList());
There is also a thread-safe variant, in case you want to use it with parallel streams:
List<Parent> result = Stream.concat(list1.stream(), list2.stream())
.filter(new ConcurrentSkipListSet<>(c)::add)
.collect(Collectors.toList());
For example:
public class Parent {
public int no;
public String name;
#Override
public int hashCode() {
return (no << 4) ^ name.hashCode();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Parent))
return false;
Parent o = (Parent)obj;
return this.no == o.no && this.name.equals(o.name);
}
}
Override the equals and hashCode methods in Parent class to avoid duplicates from the lists. This will give you the exact result what you want.
If you want to override .equals(…) and .hashCode(), you need to do so on the Parent class. Note that this may cause other uses of Parent to fail. Alexis C.'s linked solution is more conservative.

Search an ArrayList using an object [duplicate]

This question already has answers here:
Search an ArrayList for a particular Object
(6 answers)
Closed 9 years ago.
I have an ArrayList that is defined as follows:
ArrayList<Doctor> arr = new ArrayList<Doctor>();
The Doctor class contains the following:
String name;
String gender;
String speciality;
In the arr arraylist i have added 100 Doctor objects.
Now i need to search the arr ArrayList to see if a particular doctor object is present.
I tried the following approach;
boolean contains = maarrp.containsKey(doc.name);
But, i don't want to compare using keys or an element of the Doctor object. Instead i want to compare the whole doctor object. How can i do this?
You need to implement the equals() and hashcode() methods in you Doctor Object then Search against ArrayList like below.
ArrayList<Doctor> arr = new ArrayList<Doctor>();
arr.add(new Doctor());
arr.add(new Doctor());
arr.add(new Doctor());
arr.add(new Doctor());
if(arr.contains(doc){
}
Create your Doctor class like below
class Doctor{
Long id;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.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;
Doctor other = (Doctor) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
Override equals() and hashcode() methods in Doctor class.
your class should be something like :
class Doctor
{
String name;
String gender;
String speciality;
public boolean equals()
{
//some logic on which you want to say
//two doctors are same.
return true;
}
public int hashCode()
{
return 0;
}
}
for hashCode you must follow these rules :
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
Note : hashCode must not always return 0 :)
Override equals() and hashcode() methods in Doctor Class
/*add a unique id field in doctor class to make each doctor unique.*/
String docUniqueId;
String name;
String gender;
String speciality;
#Override
public boolean equals(Object ob) {
if (!ob instanceof Doctor ) return false;
Doctor that = (Doctor )ob;
return this.docUniqueId== that.docUniqueId;
}
#Override
public int hashCode() {
return docUniqueId;
}
You could implement the equals() and hashcode() methods of your Doctor class. These methods are called when the contains() method of the ArrayList object is called.
To simplify this work, you can eventually use the commons-lang library which provides:
EqualsBuilder (http://commons.apache.org/proper/commons-lang/apidocs/index.html?org/apache/commons/lang3/builder/ToStringBuilder.html)
and HashCodeBuilder (http://commons.apache.org/proper/commons-lang/apidocs/index.html?org/apache/commons/lang3/builder/HashCodeBuilder.html)

Cannot remove or find an object in Java's CopyOnWriteArraySet

I use CopyOnWriteArraySet to store one instance of a custom class, which looks like this:
public class MyClass{
String _name;
public MyClass(String name){
_name = name;
}
#Override
public int hashCode(){
return _name.hashCode();
}
#Override
public boolean equals(Object obj){
if (obj == this) return true;
if ((obj instanceof MyClass) == false) return false;
MyClass otherObject = (MyClass) obj;
return _name.equals(otherObject._name);
}
#Override
public String toString(){
return _name;
}
}
When I print the set, everything seems ok:
MyClass theObject = new MyClass("Object 1");
CopyOnWriteArraySet<MyClass> theSet = new CopyOnWriteArraySet();
theSet.add(theObject);
for (MyClass tmp : theSet){
System.out.println(tmp.toString());
}
The result is:
Object 1
So, obviously the object is in the set.
Now, I want to remove the object from the set:
theSet.remove(theObject);
Then I print the content of the set again.
The result:
Object 1
Very weird. So, I tried this:
System.out.println(String.valueOf(theSet.contains(theObject)));
The result:
false
Obviously, the set cannot find theObject although it's there.
So, I thought, there's something wrong with the equals() method.
Thus, I changed the method overrides of equals() and hashCode() by adding a console print to the first line of each function:
#Override
public int hashCode(){
System.out.println("hashCode() called");
return _name.hashCode();
}
#Override
public boolean equals(Object obj){
System.out.println("equals() called");
if (obj == this) return true;
if ((obj instanceof MyClass) == false) return false;
MyClass otherObject = (MyClass) obj;
return _name.equals(otherObject.name);
}
Then, I call again:
theSet.remove(theObject);
The result:
hashCode() called
So, the equals() method isn't called at all?
Can someone explain what's going on there?
I already tried to compare the hashCodes of theObject and the instance inside the set and they're both equal.
Strange..i have tested your codes. And it works well in my environment.
And the remove operation doesn't call hashCode() but call equals() instead.
The jdk what i used is 1.6.0_23.
HashSet's use the hashCode, however the CopyOnWriteArraySet is not a HashSet (neither is TreeSet) and neither call hashCode(). If hashCode is being called you are not using this collection.
It is very weird because I cannot reproduce your problem.
MyClass theObject = new MyClass("Object 1");
CopyOnWriteArrayList<MyClass> theSet = new CopyOnWriteArrayList();
// OR
CopyOnWriteArraySet<MyClass> theSet = new CopyOnWriteArraySet();
theSet.add(theObject);
System.out.println("After add.");
System.out.println(theSet);
theSet.remove(theObject);
System.out.println("\nAfter remove");
System.out.println(theSet);
prints
After add.
[Object 1]
After remove
[]
Even when I change hashCode to
public int hashCode() {
throw new UnsupportedOperationException();
}
it gets the same result because these classes don't use hashCode() (except in the hashCode() method)
I found the reason for the problem.
I'm using Hibernate which creates an own instance of org.hibernate.collection.PersistentSet which replaced my CopyOnWriteArraySet!
The fact that .contains() and .remove() didn't work was a bug in Hibernate: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3799
The solution in my case was to not override the .hashCode() method.
Note: This might not be the best solution for all cases. For me, it worked though.
In the link above, there are several workarounds described.

What collection to use here

I'm wondering what collection I should use for this purpose:
Requirements
Must contain tuples <value1,value2>
There is not relation between those values (no key-value pairs)
Can only contain unique tuples
<value1,value2> is equal to <value2,value1>
What would be best to use here?
Use any Set (HashSet, for instance). Create an object to represent your tuple and implement hashcode and equals properly.
Implement your own tuple class with equals and hashCode as outlined below, then use Set:
public class Tuple<T> {
T v1;
T v2;
#override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o instanceof Tuple) {
return (v1.equals(o.v1) && v2.equals(o.v2))
|| (v1.equals(o.v2) && v2.equals(o.v1));
}
return false;
}
#override
public int hashCode() {
// must produce a.hashCode() == b.hashCode() if a.equals(b)
// example below may or may not work for your concrete equals()
return v1.hashCode() ^ v2.hashCode();
}
}
A Set appears to meet your criteria. The Set would have to contain another collection or a custom object that contains the two values.

Categories