TreeSet.contains() does not call overwritten equals - java

i have a problem with the contains() method of TreeSet. As I understand it, contains() should call equals() of the contained Objects as the javadoc says:
boolean java.util.TreeSet.contains(Object o): Returns true if this set
contains the specified element. More formally, returns true if and
only if this set contains an element e such that (o==null ? e==null :
o.equals(e)).
What I try to do:
I have a list of TreeSets with Result Objects that have a member String baseword. Now I want to compare each TreeSet with all Others, and make for each pair a list of basewords they share. For this, I iterate over the list once for a treeSet1 and a second time for a treeSet2, then I iterate over all ResultObjects in treeSet2 and run treeSet1.contains(ResultObject) for each, to see if treeSet1 contains a Result Object with this wordbase. I adjusted the compareTo and equals methods of the ResultObject. But it seems that my equals is never called.
Can anyone explain me why this doesn't work?
Greetings,
Daniel
public static void getIntersection(ArrayList<TreeSet<Result>> list, int value){
for (TreeSet<Result> treeSet : list){
//for each treeSet, we iterate again through the list of TreeSet, starting at the TreeSet that is next
//to the one we got in the outer loop
for (TreeSet<Result> treeSet2 : list.subList((list.indexOf(treeSet))+1, list.size())){
//so at this point, we got 2 different TreeSets
HashSet<String> intersection = new HashSet<String>();
for (Result result : treeSet){
//we iterate over each result in the first treeSet and see if the wordbase exists also in the second one
//!!!
if (treeSet2.contains(result)){
intersection.add(result.wordbase);
}
}
if (!intersection.isEmpty()){
intersections.add(intersection);
}
}
}
public class Result implements Comparable<Result>{
public Result(String wordbase, double result[]){
this.result = result;
this.wordbase = wordbase;
}
public String wordbase;
public double[] result;
public int compareTo(DifferenceAnalysisResult o) {
if (o == null) return 0;
return this.wordbase.compareTo(o.wordbase);
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((wordbase == null) ? 0 : wordbase.hashCode());
return result;
}
//never called
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DifferenceAnalysisResult other = (DifferenceAnalysisResult) obj;
if (wordbase == null) {
if (other.wordbase != null)
return false;
} else if (!wordbase.equals(other.wordbase))
return false;
return true;
}
}

As I understand it, contains() should call equals() of the contained Objects
Not for TreeSet, no. It calls compare:
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.
...
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface.
Your compareTo method isn't currently consistent with equals - x.compareTo(null) returns 0, whereas x.equals(null) returns false. Maybe you're okay with that, but you shouldn't expect equals to be called.

Related

How to implement a compareTo() method when consistent with Equal and hashcode

I have a class Product, which three variables:
class Product implements Comparable<Product>{
private Type type; // Type is an enum
Set<Attribute> attributes; // Attribute is a regular class
ProductName name; // ProductName is another enum
}
I used Eclipse to automatically generate the equal() and hashcode() methods:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((attributes == null) ? 0 : attributes.hashCode());
result = prime * result + ((type == null) ? 0 : type.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;
Product other = (Product) obj;
if (attributes == null) {
if (other.attributes != null)
return false;
} else if (!attributes.equals(other.attributes))
return false;
if (type != other.type)
return false;
return true;
}
Now in my application I need to sort a Set of Product, so I need to implement the Comparable interface and compareTo method:
#Override
public int compareTo(Product other){
int diff = type.hashCode() - other.getType().hashCode();
if (diff > 0) {
return 1;
} else if (diff < 0) {
return -1;
}
diff = attributes.hashCode() - other.getAttributes().hashCode();
if (diff > 0) {
return 1;
} else if (diff < 0) {
return -1;
}
return 0;
}
Does this implementation make sense? What about if I just want to sort the product based on the String values of "type" and "attributes" values. So how to implement this?
Edit:
The reason I want to sort a Set of is because I have Junit test which asserts on the string values of a HashSet. My goal is to maintain the same order of output as I sort the set. otherwise, even if the Set's values are the same, the assertion will fail due to random output of a set.
Edit2:
Through the discussion, it's clear that to assert the equality of String values of a HashSet isn't good in unit tests. For my situation I currently write a sort() function to sort the HashSet String values in natural ordering, so it can consistently output the same String value for my unit tests and that suffice for now. Thanks all.
Looks like from all the comments in here you dont need to use Comparator at all. Because:
1) You are using HashSet that does not work with Comparator. It is not ordered.
2) You just need to make sure that two HashSets containing Products are equal. It means they are same size and contain the same set of Products.
Since you already added hashCode and equals methods to Product all you need to do is call equals method on those HashSets.
HashSet<Product> set1 = ...
HashSet<Product> set2 = ...
assertTrue( set1.equals(set2) );
This implementation does not seem to be consistent. You have no control over how the hash codes look like. If you have obj1 < obj2 according to compareTo in the first try, the next time you start your JVM it could be the other way around obj1 > obj2.
The only thing that you really know is that if diff == 0 then the objects are considered to be equal. However you can also just use the equals method for that check.
It is now up to you how you define when obj1 < obj2 or obj1 > obj2. Just make sure that it is consistent.
By the way, you know that the current implementation does not include ProductName name in the equals check? Dont know if that is intended thus the remark.
The question is, what do you know about that attributes? Maybe they implement Comparable (for example if they are Numbers), then you can order according to their compareTo method. If you totally know nothing about the objects, it will be hard to build up a consistent ordering.
If you just want them to be ordered consistently but the ordering itself does not play any role, you could just give them ids at creation time and sort by them. At this point you could indeed use the hashcodes if it does not matter that it can change between JVM calls, but only then.

Java Set with multiple equality criteria

I have a particular requirement where I need to dedupe a list of objects based on a combination of equality criteria.
e.g. Two Student objects are equal if:
1. firstName and id are same OR 2. lastName, class, and emailId are same
I was planning to use a Set to remove duplicates. However, there's a problem:
I can override the equals method but the hashCode method may not return same hash code for two equal objects.
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if ((firstName.equals(other.firstName) && id==other.id) ||
(lastName.equals(other.lastName) && class==other.class && emailId.equals(other.emailId ))
return true;
return false;
}
Now I cannot override hashCode method in a way that it returns same hash codes for two objects that are equal according to this equals method.
Is there a way to dedupe based on multiple equality criteria? I considered using a List and then using the contains method to check if the element is already there, but this increases the complexity as contains runs in O(n) time. I don't want to return the exact same hash codes for all the objects as that's just increases the time and beats the purpose of using hash codes. I've also considered sorting items using a custom comparator, but that again takes at least O(n log n), plus one more walk through to remove the duplicates.
As of now, the best solution I have is to maintain two different sets, one for each condition and use that to build a List, but that takes almost three times the memory. I'm looking for a faster and memory efficient way as I'll be dealing with a large number of records.
You can make Student Comparable and use TreeSet. Simple implementation of compareTo may be:
#Override
public int compareTo(Student other) {
if (this.equals(other)) {
return 0;
} else {
return (this.firstName + this.lastName + emailId + clazz + id)
.compareTo(other.firstName + other.lastName + other.emailId + clazz + id);
}
}
Or make your own Set implementation, for instance containing a List of distinct Student objects, checking for equality every time you add a student. This will have O(n) add complexity, so can't be considered a good implementation, but it is simple to write.
class ListSet<T> extends AbstractSet<T> {
private List<T> list = new ArrayList<T>();
#Override
public boolean add(T t) {
if (list.contains(t)) {
return false;
} else {
return list.add(t);
}
}
#Override
public Iterator<T> iterator() {
return list.iterator();
}
#Override
public int size() {
return list.size();
}
}

Checking if a value is already in a List won't work

I am doing a small program that holds shelves in a library list. If the number of shelf was already entered before, you can't enter it again. However, it's not working.
Here is my code in the main class:
Shelf s = new Shelf(1);
Shelf s2 = new Shelf(1);
Library l = new Library();
l.Addshelf(s);
l.Addshelf(s2);
As you can see I entered 1 in both objects as the shelf number so this code below should then run from the library class
public void Addshelf(Shelf s)
{
List li = new ArrayList();
if(li.contains(s))
{
System.out.println("already exists");
} else {
li.add(s);
}
}
The problem must be in the above method. I want to know how I check if that shelf number already exists in the list, in which case it should prompt me with the above statement - "already exists.
You'll have to override equals method in Shelf in order to get the behavior you desire.
Without overriding equals, ArrayList::contains, which calls ArrayList::indexOf, would use the default implementation of Object::equals, which compares object references.
#Override
public boolean equals (Object anObject)
{
if (this == anObject)
return true;
if (anObject instanceof Shelf) {
Shelf anotherShelf = (Shelf) anObject;
return this.getShelfNumber() == anotherShelf.getShelfNumber(); // assuming this
// is a primitive
// (if not, use equals)
}
return false;
}
If you look at the Javadoc for List at the contains method you will see that it uses the equals()method to evaluate if two objects are the same. So you have to override the method equals on your Shelf class.
Example:
public class Shelf
{
public int a;
public Shelf (int x)
{
this.a= x;
}
#Override
public boolean equals(Object object)
{
boolean isEqual= false;
if (object != null && object instanceof Shelf)
{
isEqual = (this.a == ((Shelf) object).a);
}
return isEqual;
}
}
Make sure that you have override equals() method in Shelf.
From Java doc. How contains() works?
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that (o==null ? e==null : o.equals(e)).
^^
Try overriding methods hashCode() and equals(Object obj) in your Shelf class and then call contains.
Equals and HashCode tutorial

removeAll from Interface Set

I want to compare database dump to xml and *.sql. In debagge toRemove and toAdd only differ in dimension. toRemove has size 3, toAdd has size 4. But after running the code, removeAll, toRemove has size 3 and toAdd has size 4. What's wrong?
final DBHashSet fromdb = new DBHashSet(strURL, strUser, strPassword);
final DBHashSet fromxml = new DBHashSet(namefile);
Set<DBRecord> toRemove = new HashSet<DBRecord>(fromdb);
toRemove.removeAll(fromxml);
Set<DBRecord> toAdd = new HashSet<DBRecord>(fromxml);
toAdd.removeAll(fromdb);
Update:
public class DBRecord {
public String depcode;
public String depjob;
public String description;
public DBRecord(String newdepcode, String newdepjobe, String newdesc) {
this.depcode = newdepcode;
this.depjob = newdepjobe;
this.description = newdesc;
}
public String getKey() {
return depcode + depjob;
}
public boolean IsEqualsKey(DBRecord rec) {
return (this.getKey().equals(rec.getKey()));
}
public boolean equals(Object o) {
if (o == this)
return true;
if (o == null)
return false;
if (!(getClass() == o.getClass()))
return false;
else {
DBRecord rec = (DBRecord) o;
if ((rec.depcode.equals(this.depcode)) && (rec.depjob.equals(this.depjob)))
return true;
else
return false;
}
}
}
In order to properly use HashSet (and HashMap, for that matter), you must implement a hashCode() as per the following contract:
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.
The code you've supplied for DBRecord does not overide it, hence the problem.
You'd probably want to override it in the following way, or something similar:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + depcode.hashCode();
result = prime * result + depjob.hashCode());
return result;
}

AssertEquals with Collections with non primitive template parameters

I have a class for a string-number pair. This class has the method compareTo implemented.
A method of another class returns a collection of elements of the pair type.
I wanted to perform a unit test on this method, and therefore wrote the following:
#Test
public void testWeight() {
Collection<StringNumber<BigDecimal>> expected = new Vector<StringNumber<BigDecimal>>();
expected.add(new StringNumber<BigDecimal>("a", BigDecimal.ONE));
expected.add(new StringNumber<BigDecimal>("b", BigDecimal.ONE));
Collection<StringNumber<BigDecimal>> actual = new Vector<StringNumber<BigDecimal>>();
expected.add(new StringNumber<BigDecimal>("a", BigDecimal.ONE));
expected.add(new StringNumber<BigDecimal>("b", BigDecimal.ONE));
//Collection<StringNumber<BigDecimal>> actual = A.f();
assertEquals(expected, actual);
}
But as you can see, the assertion fails, even though the elements in the collections are identical. What can be the reason?
The error I get is
java.lang.AssertionError: expected: java.util.Vector<[a:1, b:1]>
but was: java.util.Vector<[a:1, b:1]>
Which does not make scene to me.
Your StringNumber class requires equals() method. Then it will work. Assuming this class contains string and number fields (auto-generated by my IDE):
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof StringNumber)) {
return false;
}
StringNumber that = (StringNumber) o;
if (number != null ? !number.equals(that.number) : that.number != null) {
return false;
}
return !(string != null ? !string.equals(that.string) : that.string != null);
}
#Override
public int hashCode() {
int result = string != null ? string.hashCode() : 0;
result = 31 * result + (number != null ? number.hashCode() : 0);
return result;
}
Few remarks:
Two Vector's (why are you using such archaic data structure) are equal if:
both [...] have the same size, and all corresponding pairs of elements in the two lists are equal. (Two elements e1 and e2 are equal if (e1==null ? e2==null : e1.equals(e2)).)
That's why overriding equals() is required.
when implementing equals() you must implement hashCode(). Not required here, but better be safe than sorry: What issues should be considered when overriding equals and hashCode in Java?.

Categories

Resources