It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
java.util.Set API states:
sets contain no pair of elements e1 and e2 such that e1.equals(e2)
But as far as I understand TreeSet uses Comparable/Comparator to determine if e1 and e2 are duplicates. Am I missing something?
If compareTo is consistent with equals (which it should), it doesn't matter whether a TreeSet uses compareTo or equals to determine equality. From the JavaDoc:
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. (See Comparable or Comparator
for a precise definition of consistent with equals.) 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. The
behavior of a set is well-defined even if its ordering is inconsistent
with equals; it just fails to obey the general contract of the Set
interface.
This program will print 'true', even though equals always returns false. But the bug would be in the fact the compareTo and equals are inconsistent for A, and is not a bug in TreeSet.
class A implements Comparable<A> {
public int compareTo(A a) {
return 0;
}
public boolean equals(Object other) {
return false;
}
public static void main(String[] args) {
TreeSet<A> set = new TreeSet<A>();
set.add(new A());
System.out.println(set.contains(new A()));
}
}
TreeSet use Comparator. Do you know it actually maintains elements by using map.
public TreeSet() {
this(new TreeMap<E,Object>());
}
As you said Sets contain no pair of elements e1 and e2 such that e1.equals(e2). That is why Comparator has equals method.
No matter how we are going to use Set it depends with Comparator. If we use our own comparator, we can use Set as List but normally we don't do that. If we add our own Object in to TreeSet must pass Comparator when initializing TreeSet or object must be implemented by Comparable
Comparable
A comparable object having capability to compare itself with another object
Comparator Used for comparing two different objects.
public int compare(Employee o1, Employee o2) {
// if sort by name
return o1.getName().compareTo(o2.getName());
}
public boolean equals(Object obj) {
// equals with ID
Employee e = (Employee)obj;
return this.getId().equals(e.getId());
}
Here is Java Sorting: Comparator vs Comparable a tutorial.
Your assumption is wrong; TreeSet doesn't use Comparable/Comparator to determine if e1 is equal to e2.
Equals method is part of Object class and used to decide if two elements of collection are same or not. i.e. e1.equals(e2)
But Comparable/Comparator interface is used to decide if an element is greater than, equal or less than other element. And this is used during sorting. So only thing you need to ensure is that equals and compareto methods are consistent.
So to compare two objects equals method is used and comparator/comparable is used during sorting
EDIT
Below is the method definition from JDK 6; equals method of AbstractSet. TreeSet extends AbstractSet
public boolean equals(Object obj)
{
if(obj == this)
return true;
if(!(obj instanceof Set))
return false;
Collection collection = (Collection)obj;
if(collection.size() != size())
return false;
try
{
return containsAll(collection);
}
catch(ClassCastException classcastexception)
{
return false;
}
catch(NullPointerException nullpointerexception)
{
return false;
}
}
http://docs.oracle.com/javase/6/docs/api/java/util/AbstractSet.html
Related
I am trying to add an Object (Exception) to a Set, however it adds every Exception, eventhough some are duplicates.
debug
In my case duplicates are Exceptions which have the same Detail message.
How do I properly add the Exceptions to the HashSet only if the Exception.getDetails() doesn't already exist?
Is there another approach than a HashSet?
Performance is a criteria here, quadratic solutions (O(n^2))are not an option.
You have a few options:
override hashcode and equals in your exception class
use a TreeSet with a custom Comparator
use a Map<String, Exception> where the key is the getDetails() result (for example, a HashMap)
You need to override how the Execptions are compared so it recognises duplicates the way you want. You can't do this for a HashSet but you can for TreeSet e.g.
Set<Exception> exceptions = new TreeSet<>(Comparator.comparing(Object::toString));
This example compares the toString which is the exception type and message in most cases.
If you really want to use a HashSet you need to wrap the Exception in a class which implements hashCode and equals the way you want.
If all you care about is the type and message you can store just the toString of each exception
final Set<String> exceptions = new HashSet<>();
public void addException(Exception e) {
exceptions.add(e.toString());
}
You need to redefine equals and hashCode methods.
If the detail is a String you can redefine them as follow
public boolean equals(Object obj) {
if (!(obj instanceof YourException)) {
return false;
}
return getDetail().equals(((YourException) obj).getDetail());
}
public int hashCode() {
return getDetail().hashCode();
}
Consider this code as a base to program. You have to check for null values for example.
Once redefined equals and hashCode inserting YourException in a TreeSet is an operation done in O(log(n)) where n is the size of the set, from javadoc:
This implementation provides guaranteed log(n) time cost for the basic operations (add, remove and contains).
I was working with the TreeSet collection with the following code:
import java.util.*;
public class Employee implements Comparable<Employee>{
private int ID;
public Employee(int iD) {
ID = iD;
}
#Override
public int compareTo(Employee obj) {
return this.ID-obj.ID;
}
private static void intoTreeSet() {
Employee e1=new Employee(4);
Employee e2=new Employee(2);
Employee e3=new Employee(1);
Employee e4=new Employee(5);
Employee e5=new Employee(3);
Employee eTemp=new Employee(3);
Set<Employee> set=new TreeSet();
set.add(e1);set.add(e2);set.add(e3);set.add(e4);set.add(e5);
System.out.println("output says: ");
for(Employee e:set){
System.out.print(e.ID+" ~ ");
}
System.out.println();
if(set.contains(eTemp)){
System.out.println("C O N T A I N S !!!");
}
if(e5.equals(eTemp)){
System.out.println("E Q U A L S !!!");
}
}
public static void main(String[] args) {
intoTreeSet();
}
}
Output
output says:
1 ~ 2 ~ 3 ~ 4 ~ 5 ~
C O N T A I N S !!!
I am confused to see the output. I want to know, if it does NOT pass equals case, then how come it pass contains case.
I know that two objects can only be equal if their class overrides equals method and they are equal according to some property. I intentionally did not override equals method to see how contains work. If it were a non-tree based collection lets say an ArrayList it would NOT have passed contains test. Why is it so ? can any one explain this behaviour and clear my confusion.
The javadoc for java.util.TreeSet says:
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. (See Comparable or Comparator for a precise definition of consistent with equals.) 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. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
In other words, the implementations of compareTo and equals must be consistent with each other. If they're not, the behaviour of TreeSet will be erratic. It may work, it may not. To understand when it does and when it doesn't you need to look closely at the TreeSet implementation, but since the javadoc is very explicit on the conditions under which TreeSet works, it's really not a good idea to try and subvert it.
The most important point to remember here is, a TreeSet is a SortedSet which performs element comparisons using compareTo (or compare) method.
The Employee class is comparable. From the definition from the docs of the comparable interface,
This interface imposes a total ordering on the objects of each class
that implements it. This ordering is referred to as the class's
natural ordering, and the class's compareTo method is referred to as
its natural comparison method.
So if your compareTo method returns 0, for two instances of the same class, they are considered to be naturally equal by the TreeSet.
The document also says,
It is strongly recommended (though not required) that natural orderings be consistent with
equals. This is so because sorted sets (and sorted maps) without explicit
comparators behave "strangely" when they are used with elements (or
keys) whose natural ordering is inconsistent with equals.
Though it is not defined how "strangely" it can behave.
In our case e5.equals(eTemp) is false, since the equals method is not overridden.
And e5.compareTo(eTemp) is true, so from the set's perspective e5 and eTemp are equal.
To demonstrate this you can perform the below operations:
Employee e1 = new Employee(3);
Employee e2 = new Employee(3);
set.add(e1); // gets added to the set
The below operation will return false because the set considers an equivalent of e2 already to be present in the set, though e1.equals(e2) is false, and the size of the set remains same.
System.out.println(set.add(e2)); // false
So to to be consistent, you could override the equals method, though it is not necessary.
I have tried to compare to sets of type String using the Sets.intersection method() in Guava. It works fine. I want to know what method should I implement to compare two objects? I have overridden the compareTo() method but the Sets.intersection treat similar objects as different. Can you advice please?
Thanks.
Given the implementation of Sets.intersection():
public static <E> SetView<E> intersection(final Set<E> set1, final Set<?> set2) {
//...
return new SetView<E>() {
//...
#Override public boolean contains(Object object) {
return set1.contains(object) && set2.contains(object);
}
#Override public boolean containsAll(Collection<?> collection) {
return set1.containsAll(collection)
&& set2.containsAll(collection);
}
};
}
I'd say you have to implement whatever methods are needed to make contains() and containsAll() work for the Sets you pass in, because all the work is delegated to the Sets you pass in.
So for HashSets that would be equals() and hashCode(), and for TreeSets that would be compareTo() if you implement Comparable or compare() if you use a Comparator (probably want to still override equals() and hashCode() to keep things consistent outside the Map too).
You just have to read the definition of Set:
More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element.
Implement hashCode() and equals().
I have a list of DataPoint objects. The class definition is:
public static class DataPoint
{
public Comparable X;
public Comparable Y;
public Comparable Z;
public String text;
...
}
"list" is an ArrayList of DataPoint objects. How do I sort list only on the X value? Would Collections.sort(list, comparator) be used here?
Yes, you should create specific comparator for each field. Example:
Comparator<DataPoint> compByX = new Comparator<DataPoint>() {
#Override
public int compare(DataPoint left, DataPoint right) {
return left.X.compareTo(right.X);
}
};
Collections.sort(list, compByX);
You have two choices:
implement Comparable<DataPoint> for your DataPoint class
write a custom comparator that implements Comparator<DataPoint> and then use Collections.sort
First solution is meaningful if you want to give a natural ordering on your objects (which will be the most used one). Usually it's the one you use first while you use comparators just when you need additional orderings.
They both behave in the same way but Comparable<T> is inherently attached to the object as it is its default comparison algorithm. Whenever sorting is involved the default one will be used unless you specify another one.
class DataPoint implements Comparable<DataPoint> {
#Override
public int compareTo(DataPoint o) {
return X.compareTo(o.X);
}
}
Mind that when you need to compare objects you usually need also other operations on them so take care of overriding hashCode() and equals(Object o). The latter is used in sorting as documentations states:
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. ... It is strongly recommended (though not required) that natural orderings be consistent with equals.
This means that if you just compare X variable then two different DataPoint objects with same X will be considered equal with respect to compareTo. This can lead to strange situations.
I have two classes - Task (which implements Comparable) and DeadlinedTask (where DeadlinedTask extends Task). And for each of them I have written an overloaded compareTo function (each has compareTo(Task) and compareTo(DeadlinedTask)).
The idea is that I can sort normal Tasks by category, and DeadlinedTasks by deadline, but I also want all of the DeadlinedTasks to be sorted above the Tasks.
When I call Collections.sort(myListOfTasks) on a list of only Tasks (no DeadlinedTasks), everything works like a charm.
However when I have a list of both Tasks and DeadlinedTasks, the objects change order, but they are not fully sorted.
I have tried returning numbers other than 1 on the interclass compares (1, 1000, 1000000 all did the same thing). Is there any way to do this through compareTo and Collections.sort, is there a different java functionality I can use, or do I have to write my own search function (as a Comparator?)?
Task compareTo Methods:
public int compareTo(Task other){
if(this.GetCategory().compareTo(other.GetCategory())==0)
return this.GetName().compareTo(other.GetName());
else
return this.GetCategory().compareTo(other.GetCategory());
}
public int compareTo(DeadlinedTask other){
return 1;
}
DeadlinedTask compareTo Methods:
public int compareTo(Task other){
return -1;
}
public int compareTo(DeadlinedTask other){
if(this.GetDeadline().compareTo(other.GetDeadline())==0)
return this.GetName().compareTo(other.GetName());
else
return this.GetDeadline().compareTo(other.GetDeadline());
}
Thanks for any help
... or do I have to write my own search function (as a Comparator?)?
Yes. I think that's the best way.
The normal way to handle equals and compareTo is to return false (for equals) or throw ClassCastException (for compareTo) if the arguments actual type doesn't match the actual type of this.
If you try to implement equals or compareTo for subtypes, you can easily create semantic anomalies such as:
a.equals(b) and b.equals(a) returning different values, or
a.compareTo(b) and b.compareTo(a) returning inconsistent values.
Avoiding those anomalies would entail making the supertype aware of the subtype. That is a bad from a design perspective because it restricts your ability to create more subtypes in the future.
For use-cases where you need to implement a rule that orders instances of two or more different classes, a Comparator is the best solution.
Per class, only one compareTo method can be used to implement the Comparable interface. If you use Comparable without generics, then this is
public int compareTo(Object o)
If you're using generics, e.g. Comparable<Task>, then it's
public int compareTo(Task o)
Your compareTo(DeadlinedTask o) method will be ignored concerning the Comparable<Task> interface. It just "accidentally" has the same name, but it's an independent overloading.
(By the way, it's not possible to implement both Comparable<Task> and Comparable<DeadlineTask>).
So what you'll have to do instead, is change your Task.compareTo(Task o) method to use instanceof (it has to use runtime information after all). I agree with Stephen, that it would even be better to write a Comparator.
Comparable defines a natural order for all instances of a class. So if DeadlinedTask should always come before Tasks, then the compareTo method should implement it.
You should not redefine compareTo in DeadlinedTask, because this would break the contract of anti-commutativity : if (t1.compareTo(t2) > 0), then t2.compareTo(t1) < 0.
I would thus completely avoid to implement Comparable in the Task class, and use a dedicated comparator when sorting a collection of tasks. If you really want your task to implement Comparable, than you need to make its implementation depend on the existence of DeadlinedTask (which is not very OO) :
public class Task implements Comparable<Task> {
// ...
public final int compareTo(Task t) {
if (this instanceof DeadlinedTask) {
if (t instanceof DeadlinedTask) {
return ((DeadlinedTask) this).getDeadline().compareTo(((DeadlinedTask) t).getDeadline());
}
else {
return -1;
}
}
else if (t instanceof DeadlinedTask) {
return 1;
}
else {
return this.category.compareTo(t.category);
}
}
}
Note that Java uses a lower-case letter at the beginning of methods (getDeadline(), and not GetDeadline()), and that you don't need to use getters to access private properties of your own class.
In addition to what StevenC have said, if you know in advance that you will have a hierarchy of value objects, you can check whether the class of the argument of the compareTo() method is a subtype of the class of the object and if yes, reverse the comparison, so you will always have the child comparing against the parent:
public boolean compareTo(Object o) {
// check for null
boolean isSubtype = getClass().isAssignableFrom(o.getClass()) && getClass()!=o.getClass()
if (isSubtype) return -((/*cast to this type*/) o).compareTo(this);
}
This way, the comparison remains consistent and the base type does not to be aware pf each individual subtype, but just that subtypes exist.
Yes it seems a comparator is the simplest (& cleanest way)
But you can simply delegate te bulk ot the work to the compareTo(...) methods you have already written, all you really need to add is code to handle comparison between the sub and super classes:
public int Compare(Task t1, Task t2) {
if (t1 instance of DeadlinedTask && !(t2 instanceof DeadlinedTask))
return 1;
else if (t2 instance of DeadlinedTask && !(t1 instanceof DeadlinedTask))
return -1;
else
return t1.compareTo(t2);
}
but it just occured, how are you declaring the classes? do you include Comparable in the implements clause of the Task class and visa versa? if not, then perhaps when the lhs object is a Task, then only compare(Task) gets called ?? otherwise you need to have both in the implements clause ie:
class Task implements Comparable<Task>, Comparable<DeadlinedTask>
The magnitude of the value returned will not change anything, ie returning 1 and 1000000 is exactly the same, as tests are only < 0, > 0 and == 0 (this contract IS specified in the docs for the Comparator interface. I used to tell students trying to remember what return values mean, to imagine comparing ints, then we could just write:
int compare (int a, int b) { return a - b; }