Compare two Java Collections using Comparator instead of equals() - java

Problem Statement
I have two Collections of the same type of object that I want to compare. In this case, I want to compare them based on an attribute that does not factor into equals() for the Objects. In my example, I'm using ranked collections of Names for instance:
public class Name {
private String name;
private int weightedRank;
//getters & setters
#Override
public boolean equals(Object obj) {
return this.name.equals(obj.name); //Naive implementation just to show
//equals is based on the name field.
}
}
I want to compare the two Collections to assert that, for position i in each Collection, the weightedRank of each Name at that position is the same value. I did some Googling but didn't find a suitable method in Commons Collections or any other API so I came up with the following:
public <T> boolean comparatorEquals(Collection<T> col1, Collection<T> col2,
Comparator<T> c)
{
if (col1 == null)
return col2 == null;
if (col2 == null)
return false;
if (col1.size() != col2.size())
return false;
Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();
while(i1.hasNext() && i2.hasNext()) {
if (c.compare(i1.next(), i2.next()) != 0) {
return false;
}
}
return true;
}
Question
Is there another way to do this? Did I miss an obvious method from Commons Collections?
Related
I also spotted this question on SO which is similar though in that case I'm thinking overriding equals() makes a little more sense.
Edit
Something very similar to this will be going into a release of Apache Commons Collections in the near future (at the time of this writing). See https://issues.apache.org/jira/browse/COLLECTIONS-446.

You could use the Guava Equivalence class in order to decouple the notions of "comparing" and "equivalence". You would still have to write your comparing method (AFAIK Guava does not have it) that accepts an Equivalence subclass instead of the Comparator, but at least your code would be less confusing, and you could compare your collections based on any equivalence criteria.
Using a collection of equivance-wrapped objects (see the wrap method in Equivalence) would be similar to the Adapter-based solution proposed by sharakan, but the equivalence implementation would be decoupled from the adapter implementation, allowing you to easily use multiple Equivalence criteria.

You can use new isEqualCollection method added to CollectionUtils since version 4. This method uses external comparsion mechanism provided by Equator interface implementation. Please, check this javadocs: CollectionUtils.isEqualCollection(...) and Equator.

I'm not sure this way is actually better, but it is "another way"...
Take your original two collections, and create new ones containing an Adapter for each base object. The Adapter should have .equals() and .hashCode() implemented as being based on Name.calculateWeightedRank(). Then you can use normal Collection equality to compare the collections of Adapters.
* Edit *
Using Eclipse's standard hashCode/equals generation for the Adapter. Your code would just call adaptCollection on each of your base collections, then List.equals() the two results.
public class Adapter {
public List<Adapter> adaptCollection(List<Name> names) {
List<Adapter> adapters = new ArrayList<Adapter>(names.size());
for (Name name : names) {
adapters.add(new Adapter(name));
}
return adapters;
}
private final int name;
public Adapter(Name name) {
this.name = name.getWeightedResult();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Adapter other = (Adapter) obj;
if (name != other.name)
return false;
return true;
}
}

EDIT: Removed old answer.
Another option that you have is creating an interface called Weighted that could look like this:
public interface Weighted {
int getWeightedRank();
}
Then have your Name class implement this interface. Then you could change your method to look like this:
public <T extends Weighted> boolean weightedEquals(Collection<T> col1, Collection<T> col2)
{
if (col1 == null)
return col2 == null;
if (col2 == null)
return false;
if (col1.size() != col2.size())
return false;
Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();
while(i1.hasNext() && i2.hasNext()) {
if (i1.next().getWeightedRank() != i2.next().getWeightedRank()) {
return false;
}
}
return true;
}
Then as you find additional classes that need to be weighted and compared you can put them in your collection and they could be compared with each other as well. Just an idea.

Related

Find object in ArrayList

I want to find a LegalEntity object in an ArrayList. The object can possibly be a different instance. I'm only interested in whether they represent the same value, i.e. they have the same primary key. All LegalEntity instances are created from database values by EJB:
List<LegalEntity> allLegalEntities = myEJB.getLegalEntityfindAll());
LegalEntity currentLegalEntity = myEJB.getLegalEntityfindById(123L);
My first naive idea never finds matches:
if (allLegalEntities.contains(currentLegalEntity)) {
}
I then thought that perhaps I need to create my own equals() method:
public boolean equals(LegalEntity other) {
return legalEntityId.equals(other.legalEntityId);
}
But this method is not even being invoked. Is there a way to find an object in a list that doesn't involve looping?
I'm learning Java so it might easily be some foolish misunderstanding on my side.
Your approach is correct, but you need to override the method equals that accepts an Object:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
LegalEntity other = (LegalEntity) obj;
// check if equals based one some properties
}
However you also need to override hashCode:
#Override
public int hashCode() {
// return a unique int
}
So this might not be the easiest solution.
Another approach is to use filter:
LegalEntity myLegalEntity = myEJB.getLegalEntityfindAll().stream()
.filter(legalEntity -> legalEntity.getProperty().equals("someting"))
.findAny()
.orElse(null);
More info here
If you're using Java 8 you can use streams:
List<LegalEntity> allLegalEntities = myEJB.getLegalEntityfindAll());
LegalEntity currentLegalEntity = allLegalEntities.stream().filter(entity -> entity.getId() == 123L).findFirst();

Is this Java TreeSet Possible

Consider that I would like to create a class which manages a TreeSet of custom objects with two keys: A String instance identifier, and a long order identifier. The Long value would be used to determine the order of the elements in the list, while the string would be used to determine if two of these elements are duplicates. To clarify, here is what the methods would look like for the custom object
#Override
public boolean equals(Object o) {
if(o == null)
return false;
if(!(o instanceof CustomObject))
return false;
//Determined by string_id values ONLY. Used in TreeSet implementation
CustomObject obj = (CustomObject) o;
return obj.string_id.equals(this.string_id);
}
#Override
public int compareTo(Object another) {
if(another == null || !(another instanceof CustomObject))
return -1;
CustomObject other = (CustomObject) another;
if(other.long_id > this.long_id)
return -1;
else if(other.long_id < this.long_id)
return 1;
else
return 0;
}
Can my TreeSet function this way? I ask, because while testing this, I've found that my implementation only keeps the latest entry, and discards the rest. I'm looking to find out if this is simply an error with my implementation, or if I'm not properly using the TreeSet class and need to refactor my approach.

Removing duplicates without overriding hashCode() [duplicate]

This question already has answers here:
Comparing two lists and removing duplicates from one
(3 answers)
Closed 7 years ago.
For enterprise reasons I can't override hashCode and I must use Java 6 (but I can use guava)
Whats the bests/simplest/quickest/most efficient/[insert indeterminate adjective equivalent to best] mechanism to remove duplicate beans from a Java collection?
A duplicate is defined by a subset of getters returning same values, e.g.
pojoA.getVal() == pojoB.getVal() && pojoA.getOtherVal() == pojoB.getOtherVal()
Wrap the objects of interest into your own class, and override its hashCode/equals to pay attention to a specific subset of attributes. Make a hash set of wrappers, then harvest the objects from the set to get a duplicate-free subset.
Here is an example:
class ActualData {
public String getAttr1();
public String getAttr2();
public String getAttr3();
public String getAttr4();
}
Let's say you want to pay attention to attributes 1, 2, and 4. Then you can make a wrapper like this:
class Wrapper {
private final ActualData data;
public ActualData getData() {
return data;
}
private final int hash;
public Wrapper(ActualData data) {
this.data = data;
this.has = ... // Compute hash based on data's attr1, 2, and 4
}
#Override
public int hashCode() {
return hashCode;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Wrapper)) return false;
Wrapper other = (Wrapper)obj;
return data.getAttr1().equals(other.getAttr1())
&& data.getAttr2().equals(other.getAttr2())
&& data.getAttr4().equals(other.getAttr4());
}
}
Now you can make a HashSet<Wrapper>:
Set<Wrapper> set = new HashSet<>();
for (ActualData item : listWithDuplicates) {
if (!set.add(new Wrapper(item))) {
System.out.println("Item "+item+" was a duplicate");
}
}
You could use a new TreeSet<Pojo> (comparator) with comparator implemented to reflect your condition (assuming integers here but replace as needed - for non comparable objects you need to find a hack to return some integer).
if (pojoA.getVal() != pojoB.getVal())
return Integer.compare(pojoA.getVal(), pojoB.getVal());
if (pojoA.getOtherVal() != pojoB.getOtherVal())
return Integer.compare(pojoA.getOtherVal(), pojoB.getOtherVal());
return 0;
Not as efficient as a plain HashSet though - #dasblikenlight suggestion is probably better.

getting an object from an arrayList with objects attribute

I have 2 classes.
public class klass1 {
String bir;
String myID;
klass1(String bir, String myID)
{
this.bir=bir;
this.myID=myID;
}
}
.
import java.util.*;
public class dd {
public static void main(String[] args) {
ArrayList<Object> ar=new ArrayList();
ar.add(new klass1("wer","32"));
ar.add(new klass1("das","23"));
ar.add(new klass1("vz","45"));
ar.add(new klass1("yte","12"));
ar.add(new klass1("rwwer","43"));
ar.remove(new klass1("vz","45"));//it's not worked!!!
System.out.println(ar.size());
}
}
What I want is removing or getting an object from array list with object's second attribute. How can I do that? Is there an easy way for it?
Just implement the equals method in the class Klass1.
public class Klass1 {
String bir;
String myID;
Klass1(String bir, String myID)
{
this.bir=bir;
this.myID=myID;
}
public boolean equals(Object o){
if(o instanceof Klass1)
return ((Klass1)o).myID.equals(myID);
else
return false;
}
}
Its because you are trying to delete a new object which isnt in the arraylist. When you use new klass1("vz","45") you are creating a new instance of this class which isnt in the arraylist.
What the system does internally is to compare those classes using equals. Why this doesn't work is explained in the following code:
Object o1 = new Object();
Object o2 = new Object();
System.out.println(o1 == o2); // false, obviously
System.out.println(o1.equals(o2)); // false
System.out.println(o1); // java.lang.Object#17046822
System.out.println(o2); // java.lang.Object#22509bfc
You can tell by the number following the # that these objects have a different hash values, and this is what the equals function of Object does check.
This is relevant for your klass, because unless you overwrite equals, you will use the equals of Object. And if you implement equals you should always implement hashcode as well. Because both tell you something about whether or not two objects are the "same", and if the one says something else than the other, some part of your code might get confused.
How to properly implement equals for your class:
#Override
public int hashCode() {
int hash = 7;
hash = 17 * hash + Objects.hashCode(this.bir);
hash = 17 * hash + Objects.hashCode(this.myID);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final klass1 other = (klass1) obj;
if (!Objects.equals(this.bir, other.bir)) {
return false;
}
if (!Objects.equals(this.myID, other.myID)) {
return false;
}
return true;
}
This can be done in most IDEs btw with a shortcut (i.E. alt-insert in Netbeans). Note that I did this in Java 7 using Objects. If you are in Java 6, you need to manually type(a == b) || (a != null && a.equals(b)); with the appropriate objects to compare.
Creating a proper hashcode is not always trivial, for more complex objects you might want to read a bit about hashcodes first. For simple objects: multiply primes with something.
The equals method is usually trivial, it is just important to first check for null and for class equality. This is often forgotten by programmers and a common source for NullPointerExceptions and ClassCastExceptions.

Contains for List of Pair

List<Pair<String, String> > lp = new ArrayList<Pair<String, String> >();
lp.add(new Pair("1", "2"));
How should I check if the list lp contains 1 and 2 i.e the Pair ("1", "2").
Your Pair class needs to implement equals() and hashCode() and you're all set. List.contains() is implemented in terms of the type's equals() method. See the API for List.contains(). (Edited a bit to address comments from #maaartinus, whose answer you should read b/c the observations are solid, and it's a bit ridiculous for me to fold them in here. As maaartinus points out, a best-practice here would be to avoid error-prone manual definitions for equals and hashcode, and instead build on Guava's helper functions for nullable equals and hashCode for n objects).
final class Pair<T> {
final T left;
final T right;
public Pair(T left, T right)
{
if (left == null || right == null) {
throw new IllegalArgumentException("left and right must be non-null!");
}
this.left = left;
this.right = right;
}
public boolean equals(Object o)
{
// see #maaartinus answer
if (! (o instanceof Pair)) { return false; }
Pair p = (Pair)o;
return left.equals(p.left) && right.equals(p.right);
}
public int hashCode()
{
return 7 * left.hashCode() + 13 * right.hashCode();
}
}
With suitable equals(), you can now do:
lp.add(new Pair("1", "2"));
assert lp.contains(new Pair("1","2"));
Responding to the comments below, perhaps it would be good to include a good reference for "Why do I need to implement hashCode()?"
JavaPractices.com — Implementing equals() — "if you override equals, you must override hashCode"
Object.equals() contract as defined in the API documentation
StackOverflow answer
The implementation in the answer by andersoj
return left != null && right != null && left.equals(p.left) && right.equals(p.right);
is wrong: The null tests clearly suggest that null is a legal value for left and right. So there are at least two problems there:
new Pair(null, null).hashCode() throws NPE
new Pair(null, null) does NOT equal to itself!
Have a look at Guava class Objects for a correct implementation. Use it or write a static helper methods like
public static boolean equal(Object a, Object b) {
return a==b || a!=null && a.equals(b);
}
public static int hashCode(Object a) {
return a==null ? 0 : a.hashCode();
}
and always use them.
Never ever write equals containing a null test.
It's to easy to blow it, and nobody noticed it. Using the Helper, it's trivial to get it right:
public boolean equals(Object o) {
if (!(o instanceof Pair)) return false;
Pair p = (Pair) o;
return Helper.equals(left, p.left) && Helper.equals(right, p.right);
}
public int hashCode() {
return 7 * Helper.hashCode(left) + 13 * Helper.hashCode(right);
}
Of course, forbidding nulls in the constructor is an option, too.

Categories