TreeSet example - java

Why the 3rd object is not being added to the treeset here though it is a different one?
import java.util.*;
class Student implements Comparable<Student>{
public String fn,ln;
public Student(String fn,String ln){
this.fn=fn;
this.ln=ln;
}
//overiding equals
public boolean equals(Object o) {
if (!(o instanceof Student))
return false;
Student s=(Student) o;
if(this==s)
return true;
if(this.fn.equals(s.fn) && this.ln.equals(s.ln))
return true;
return false;
}
//overiding hashcode
public int hashCode() {
return fn.hashCode()+ln.hashCode();
}
//overiding compareTo
public int compareTo(Student o) {
return this.fn.compareTo(o.fn);
}
}
public class Practice {
public static void main(String[] args) {
Student st1=new Student("Girish","J");
Student st2=new Student("Master","M");
Student st3=new Student("Girish","Jay");
Set S=new TreeSet();
//adding 3 different student objects
System.out.println(S.add(st1));
System.out.println(S.add(st2));
System.out.println(S.add(st3));
Iterator sitr=S.iterator();
while(sitr.hasNext())
{
Student stu=(Student) sitr.next();
System.out.println(stu.fn+" "+stu.ln);
}
}
}
Output:
true
true
false
Girish J
Master M

Your comparator function only uses fn:
public int compareTo(Student o) {
return this.fn.compareTo(o.fn);
}
TreeSet only uses ordering comparisons - it doesn't use hashCode() and equals().
By this comparison, st1 and st3 are equal (s1.compareTo(s3) will return 0) therefore st3 isn't added to the set.
If you want to maintain the distinction, you should probably compare fn and then use ln if the fn values are the same:
public int compareTo(Student o) {
int fnResult = this.fn.compareTo(o.fn);
return fnResult == 0 ? ln.compareTo(o.ln) : fnResult;
}

Your observations are correct that TreeSet does not use .equals and .hashcode for comparison.
From the javadocs:
This is so because the Set interface is defined in terms of the equals operation, but a
TreeSet instance performs all element comparisons using its compareTo (or compare) method,
so two elements that are deemed equal by this method are, from the standpoint of the set,
equal.
Basically, they are saying that for TreeSet, equality is determined not through .equals, but through .compareTo on the Comparable interface. Note that .compareTo should always be in line with .equals, meaning that if a.equals(b), then a.compareTo(b) == 0.
This has to do with the fact that TreeSet is an implementation of SortedSet. As such, it needs .compareTo in order to determine order, since .equals is not enough in that case.
PS: If you do not want to implement Comparable (which sometimes you can't since you might not always control the code of the objets), you could always pass a Comparator to the TreeSet constructor.

Your comparison is only using the fn value...
public int compareTo(Student o) {
return this.fn.compareTo(o.fn);
}
Which fails for the 3rd Student because the first name is identical to the 1st Student
You need to adjust your code to compare both the fn and the ln values...
public int compareTo(Student o) {
int firstNameComparison = this.fn.compareTo(o.fn);
if (firstnameComparison != 0){
// the first names are different
return firstNameComparison;
}
else {
// the first names are the same, so compare the last name
return this.ln.compareTo(o.ln);
}
}
This code compares the fn values first. If they are identical, it then compares the ln values.

Related

Switching a compare to function to a boolean in Java

I am trying to change the compareTo after #override to a boolean function. This function will take a generic Object o and return whether or not it equals that Ingredient object. The only attribute in ingredient is name. So cast Object o to a Ingredient and then return whether the string names equal.
public class Ingredient implements Comparable<Ingredient> {
private String name;
public Ingredient(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
/**
* Compares two ingredients.
*
* note: if we add other ingredient attributes,
* we will need to change this method.
*/
#Override
public int compareTo(Ingredient other) {
return this.name.compareTo(other.name);
}
}
So, I change the last function to:
Does this work? Or, am I on the right track...
public boolean equals(Object o){
if(this == o) return true;
Ingredient ingredient = (Ingredient)o;
if(ingredient.name.equals(this.name)) {
return true;
} else return false;
}
You don't even have to use an interface.
According to the Java Docs, a Comparable is being used specify and order, e.g. sorting an Array.
To check for equality, you have to override the method equals (and not even implement some interface), which returns a boolean:
true for equality
false for inequality
Also, changing the return type of inherited methods is not allowed in java.
You are overwriting the int compareTo() Method of the Object class. There is no bool compareTo() to overwrite. It just doesn't exists.
When overwriting int compareTo(), you should return 0 if the objects are equal, a negative number if other is greater than this and a positive number if other is smaller than this

Functionality of overridden compareTo() method

public class Drink implements Comparable {
public String name;
#Override
public int compareTo(Object o) {
return 0;
}
#Override
public String toString() {
return name;
}
public static void main(String[] args) {
Drink one = new Drink();
Drink two = new Drink();
one.name = "Coffee";
two.name = "Tea";
TreeSet set = new TreeSet();
set.add(one);
set.add(two);
Iterator itr = set.iterator();
while(itr.hasNext()) {
System.out.println(itr.next()); //prints Tea
}
}
}
Usually, compareTo() method prints in lexicographical order, but when compareTo() method is overridden as in the above code then how it is comparing the two strings?
According to your compareTo method, all objects are equal to each other, since you always return 0, so when you try to add two Drink objects to your TreeSet, only the first one will be added, since a Set doesn't allow duplicates.
It would make more sense to have an implementation like this, that actually compares the names :
public class Drink implements Comparable<Drink> {
public String name;
#Override
public int compareTo(Drink o) {
return name.compareTo(o.name);
}
...
}
It is not comparing the string in this as thecomapareTo() method is returning 0 (meaning objects are equal) so set.add(two) will be considered as duplicated and only the first value added will be printed.
Try reversing the order of addition of values to the set and you will get your answer
The overridden compareTo method is used for customize comparison. In this function, you compare two objects based on your business logic and on the basis of your logic, you return -1,0 or 1, where -1 represents invoking object is smaller than the invoked object while _1 represents the other way. 0 represents that both the objects are equal.
In your code, right now it is not putting any logic. Its just returning a prototype value. You might put something like that in your code
return name.compareTo((String)o);
which will be the default functionality if you don't put your custom override method.

Enterring meaningfully equal data to a HashSet

Friends, I found a question like this in HashSets.
public class _235 {
private String s;
public _235(String s){
this.s=s;
}
public static void main(String[] args){
HashSet<Object> hs=new HashSet<Object>();
_235 ws1=new _235("ABC");
_235 ws2=new _235("ABC");
String s1=new String("ABC");
String s2=new String("ABC");
hs.add(ws1);
hs.add(ws2);
hs.add(s1);
hs.add(s2);
System.out.println(hs.size());
}
}
When I checked both ws1 and ws1 have added to the HashSet but not from s1 and s2 only one String has added. Since w1 and w2 have not gone through equal() I believe HashSet doesn't recognize them as equal 2 objects. But why doesn't this become same for Strings s1 and s2 as well. How it hs been identified they are as meaningfully equal objects. Please kindly explain.
HashSet requires your custom class to override equals() and hashcode() methods which are used internally to detect duplicate elements.
String class has this implementation but your custom class _235 does not have one.
Note: It is important to override both equals() and hashcode() and not just one of them or else results could be unpredictable when used with hash-based collections.
You must override hashCode() in every class that overrides equals().
Failure to do so will result in a violation of the general contract
for Object.hashCode(), which will prevent your class from functioning
properly in conjunction with all hash-based collections, including
HashMap, HashSet, and Hashtable.
from Effective Java, by Joshua Bloch
Here is a link with good explanation.
First of all String has overridden its equals method that checks for the content of it not through hashCode so when you add in the parameter of String "ABC" and other one "ABC" it will return true if the contents are the same.
String equals method:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
While on objects they only used the default equals method which use the hashCode of the object
Object equals method:
public boolean equals(Object obj) {
return (this == obj);
}
In your result:
the wsi and ws2 will always return false when executing equals method because they are located on different memory location while the s1 and s2 will return true when they have the same content thus only adding one instance of it to the HashSet, that HashSet does not allowed duplicates.

Two objects with same data are not the same?

I have a test class like so:
public class CompareObjects {
public static class Obj {
public int i;
public Obj(int i) {
this.i = i;
}
}
public static void main(String[] args) {
Obj o1 = new Obj(0);
Obj o2 = new Obj(0);
if(o1 == o2) {
System.out.println("Equal");
}else{
System.out.println("Not equal");
}
}
}
I though the test would return "Equal", but it didn't. Why doesn't Java consider two objects with equal components not the same? Am I doing something wrong here? I have a feeling I completely overlooked something when I started learning Java.
Also, I tested the two against each other with the equals() method, and I get the same result. My reason for this question is that I would like to be able to test an ArrayList with the contains() method to see if one object has the same components as another and therefore equal. Any ideas?
== compares the references to the object. For example:
Obj a = new Obj(0);
Obj b = a;
//a == b
Try implementing equals():
public static class Obj {
public int i;
public Obj(int i) {
this.i = i;
}
#Override
public boolean equals(Object other) {
if (other == this) return true;
if (!(other instanceof Obj) || other == null) return false;
return i == ((Obj)other).i;
}
#Override
public int hashCode() {
return i;
}
}
Then, you can use if(o1.equals(o2)) {. However, this is not really a good example, read this (link) for more information.
== returns true only if you are comparing the same object [i.e. the same memory location].
If you want to compare objects by their fields, you have to overload the equals() method, in order to induce an equivalence relation over them.
public boolean equals(Object other){
return this.i == other.i;
}
Be sure that the equals() method respects reflexivity, symmmetry, transitivity.
== compares the reference equality, i.e if they refer to the same object in the memory.
You need to override equals() method, and use it whenever you want to compare their values. Also, override hashCode() which is used by HashMap for example.
The == operator does not check for equality in class data; rather, it checks to see if they are the same location in memory. If you did o2 = o1 instead of initializing them the same way, they would be the same location in memory, so o2==o1 would return true. However, since they were initialized some separately, it returns false. Instead, you should define an equals method and implement that.

ArrayList not using the overridden equals

I'm having a problem with getting an ArrayList to correctly use an overriden equals. the problem is that I'm trying to use the equals to only test for a single key field, and using ArrayList.contains() to test for the existence of an object with the correct field. Here is an example
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof String) {
String inString = (String) in;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
//add some entries
objectList.add(new InnerClass("UNIQUE ID1", 42));
System.out.println( objectList.contains("UNIQUE ID1"));
}
}
What worries me is that not only am I getting false on the output, but I'm also not getting the "reached here" output.
Does anyone have any ideas why this override is being completely ignored? Is there some subtlety with overrides and inner classes I don't know of?
Edit:
Having problems with the site so I cant seem to mark the answered.
Thanks for the quick response: yes an oversight on my part that it is the String .equals thta is called, not my custom one. I guess it's old fashioned checks for now
If you check sources of ArrayList, you will see that it calls equals of other object. In your case it will call equals of String "UNIQUE ID1" which will check that other object is not of type String and just returns false:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
...
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
...
return -1;
}
For your case call contains with InnerClass that only contains id:
objectList.contains(new InnerClass("UNIQUE ID1"))
Don't forget to implement equals for InnerClass which compares id only.
According to the JavaDoc of List.contains(o), it is defined to return true
if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
Note that this definition calls equals on o, which is the parameter and not the element that is in the List.
Therefore String.equals() will be called and not InnerClass.equals().
Also note that the contract for Object.equals() states that
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
But you violate this constraint, since new TestClass("foo", 1).equals("foo") returns true but "foo".equals(new TestClass("foo", 1)) will always return false.
Unfortunately this means that your use case (a custom class that can be equal to another standard class) can not be implemented in a completely conforming way.
If you still want to do something like this, you'll have to read the specification (and sometimes the implementation) of all your collection classes very carefully and check for pitfalls such as this.
You're invoking contains with an argument that's a String and not an InnerClass:
System.out.println( objectList.contains("UNIQUE ID1"))
In my JDK:
public class ArrayList {
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
// omitted for brevity - aix
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
return i;
}
return -1;
}
}
Note how indexOf calls o.equals(). In your case, o is a String, so your objectList.contains will be using String.equals and not InnerClass.equals.
Generally, you need to also override hashCode() but this is not the main problem here. You are having an asymmetric equals(..) method. The docs make it clear that it should be symmetric:
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
And what you observe is an unexpected behaviour due to broken contract.
Create an utility method that iterates all items and verifies with equals(..) on the string:
public static boolean containsString(List<InnerClass> items, String str) {
for (InnerClass item : items) {
if (item.getTestKey().equals(str)) {
return true;
}
}
return false;
}
You can do a similar thing with guava's Iterables.any(..) method:
final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
#Override
public boolean apply(InnerClass input){
return input.getTestKey().equals(str);
}
}
Your equals implementation is wrong. Your in parameter should not be a String. It should be an InnerClass.
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InnerClass) return false;
InnerClass that = (InnerClass)o;
// check for null keys if you need to
return this.testKey.equals(that.testKey);
}
(Note that instanceof null returns false, so you don't need to check for null first).
You would then test for existence of an equivalent object in your list using:
objectList.contains(new InnerClass("UNIQUE ID1"));
But if you really want to check for InnerClass by String key, why not use Map<String,InnerClass> instead?
Although not answering your question, many Collections use hashcode(). You should override that too to "agree" with equals().
Actually, you should always implement both equals and hashcode together, and they should always be consistent with each other. As the javadoc for Object.equals() states:
Note that it is generally necessary to
override the hashCode method whenever
this method is overridden, so as to
maintain the general contract for the
hashCode method, which states that
equal objects must have equal hash
codes.
Specifically, many Collections rely on this contract being upheld - behaviour is undefined otherwise.
There are a few issues with your code. My suggestion would be to avoid overriding the equals entirely if you are not familiar with it and extend it into a new implementation like so...
class MyCustomArrayList extends ArrayList<InnerClass>{
public boolean containsString(String value){
for(InnerClass item : this){
if (item.getString().equals(value){
return true;
}
}
return false;
}
}
Then you can do something like
List myList = new MyCustomArrayList()
myList.containsString("some string");
I suggest this because if you override the equals should also override the hashCode and it seems you are lacking a little knowledge in this area - so i would just avoid it.
Also, the contains method calls the equals method which is why you are seeing the "reached here". Again if you don't understand the call flow i would just avoid it.
in the other way, your equal method gets called if you change your code as follows. hope this clears the concept.
package com.test;
import java.util.ArrayList;
import java.util.List;
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in1) {
System.out.println("reached here");
if(in1 == null) {
return false;
}else if( in1 instanceof InnerClass) {
return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
InnerClass in2 = new InnerClass("UNIQUE ID1", 42);
//add some entries
objectList.add(in1);
System.out.println( objectList.contains(in2));
}
}
As many posts have said, the problem is that list.indexOf(obj) function calls "equals" of the obj, not the items on the list.
I had the same problem and "contains()" didn't satisfy me, as I need to know where is the element!. My aproach is to create an empty element with just the parameter to compare, and then call indexOf.
Implement a function like this,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
And then, call indexOf like this:
ind position = list.indexOf(InnerClass.empty(key));
There are two errors in your code.
First:
The "contains" method called on "objectList" object should pass a new InnerClass object as the parameter.
Second:
The equals method (should accept the parameter as Object, and is correct) should handle the code properly according to the received object.
Like this:
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof InnerClass) {
String inString = ((InnerClass)in).testKey;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
This post was first written before Java 8 was available but now that it's 2017 instead of using the List.containts(...) method you can use the new Java 8 way like this:
System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());
And give your TestClass a getter for your testKey field:
public String getTestKey() {
return testKey;
}
The benefit of this approach is that you don't have to modify the equals or hash method and you'll look like a boss to your peers!

Categories