I have added some simple objects to a TreeSet, but when I call the TreeSet's remove() and contains() methods, they don't work. However, when I iterate over the set the object is printed. Employee objects shall be added to the set while the objects uniqueness is based on the objects name property. The Id property is the value that should be sorted, but which is not unique.
public class Employee {
private String name;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// Two objects are considered equal if their names are equal
#Override
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (o.getClass() == this.getClass()) {
Employee p = ( Employee) o;
if (p.getName() != null && this.getName() != null)
return this.getName().equals(p.getName());
else
return false;
} else {
return false;
}
}
}
//*******************************************************
public class EmployeeComp implements Comparator<Employee> {
// Sort Ids, but allow duplicates, hence this function is never returning 0
#Override
public int compare(Employee p1, Employee p2) {
int re = 0;
boolean scoreLt = (p1.getId() > p2.getId());
boolean scoreGt = (p1.getId() < p2.getId());
if(scoreLt)
re = -1;
if(scoreGt)
re = 1;
else
re = -1;
return re;
}
}
//*******************************************************
// Collection shall store unique names with ordered values like:
// Alex, 923
// Toni, 728
// Eddi, 232
// Peter, 232
// Eddi, 156 *** not allowed
import java.util.TreeSet;
public class Main {
private static EmployeeComp comp = new EmployeeComp();
private static TreeSet<Employee> employees = new TreeSet<Employee>(comp);
public static void main(String[] args) {
Employee p1 = new Employee();
p1.setName("Eddi");
p1.setId(232);
Employee p2 = new Employee();
p2.setName("Toni");
p2.setId(728);
Employee p3 = new Employee();
p3.setName("Peter");
p3.setId(232);
Employee p4 = new Employee();
p4.setName("Alex");
p4.setId(923);
employees.add(p1);
employees.add(p2);
employees.add(p3);
employees.add(p4);
// Here, contains() and remove() should check the object address
// and not perform their actions based on compareTo
}
}
A TreeSet inserts/removes according to the results of Comparable, not .equals()/.hashCode()!
This means, BTW, that the objects of your Set do implement Comparable (if they didn't, each time you'd have tried and inserted a member, you'd have been greeted with a ClassCastException).
To be more accurate, TreeSet is an implementation of SortedSet.
If you want a .equals()/.hashCode()-compatible set, use, for instance, a HashSet.
For the illustration, here is what happens with BigDecimal (posted a few hours ago here):
final BigDecimal one = new BigDecimal("1");
final BigDecimal oneDotZero = new BigDecimal("1.0");
final Set<BigDecimal> hashSet = new HashSet<>();
// BigDecimal implements Comparable of itself, so we can use that
final Set<BigDecimal> treeSet = new TreeSet<>();
hashSet.add(one);
hashSet.add(oneDotZero);
// hashSet's size is 2: one.equals(oneDotZero) == false
treeSet.add(one);
treeSet.add(oneDotZero);
// treeSet's size is... 1! one.compareTo(oneDotZero) == 0
To quote the javadoc for Comparable, it means that BigDecimal's .compareTo() is "not consistent with .equals()".
** EDIT ** As to what the OP wants:
a Collection which will not accept duplicate names;
a sorted view of that Collection which will sort against the user's id.
As mentioned above, you cannot have one collection which does both. The solution:
for the first, a HashSet;
for the second, a copy of that set into an ArrayList, then using Collections.sort().
This means .equals() and .hashCode() must act only on the name, while a custom Comparator will act on the id. The Comparator has no other choice but to be custom since it is a comparator which is not consisten with .equals() in any event.
As to the proposed code, there are problems.
First: Employee overrides .equals() but not .hashCode(). As such, Employee breaks the .equals() contract (one part of which is that if two objects are equal, they must have the same hashcode). What is more, .hashCode() is critical for HashSet to work at all. Fix:
#Override
public int hashCode()
{
return name == null ? 0 : name.hashCode();
}
#Override
public boolean equals(final Object obj)
{
if (obj == null)
return false;
if (this == obj)
return false;
if (!(obj instanceof Employee))
return false;
final Employee other = (Employee) obj;
return name == null ? other.name == null
: name.equals(other.name);
}
Second: the comparator is equally as broken as Employee since it breaks the Comparator contract (for any o1 and o2, o1.compareTo(o2) == - o2.compareTo(o1)). Fix:
public final class EmployeeComp
implements Comparator<Employee>
{
#Override
public int compare(final Employee o1, final Employee o2)
{
final int id1 = o1.getId(), id2 = o2.getId();
if (id1 == id2)
return 0;
return id1 > id2 ? 1 : -1;
}
}
Then, how to obtain a sorted copy of the set:
// "set" contains the unique employees
final List<Employee> sorted = new ArrayList<Employee>(set);
Collections.sort(list, new EmployeeComp());
DONE.
Your problem is conceptual.
If you want a sorted collection of unique objects: TreeSet
If you want a sorted collection were different objects can have the same comparison value for sorting purposes: PriorityQueue
Incidentally, the methods in a PriorityList are more suited to the usual needs of the second case than the ones on TreeSet. I used to think of it as TreeSet shortcomings. For example, to take the first item out of the collection.
Hope that helps :-)
Related
I have two List of employeeData object
List<EmployeeDatas> allEmployees
List<EmployeeDatas> currentEmployees
Class EmployeeDatas {
String name;
String lastName;
String joiningDate;
String promotionDate;
}
I want to compare the second list data with the first one without using the double nested loop
if the data matched return true otherwise false.
for (allEmployee : allEmployees) {
for ( currentEmployee : currentEmployees ) {
if(allEmployee.name.equal(currentEmployee.name) &&
allEmployee.lastName.equal(currentEmployee.lastName) &&
allEmployee.joiningDate.equal(currentEmployee.joiningDate) &&
allEmployee.promotionDate.equal(currentEmployee.promotionDate)) {
return true;
}
}
}
is it possible to use Map and solve it in O(N) time rather O(N^2)
You can use HashSet for that purpose. But for that, you need the equals/hashCode contract to be properly implemented.
Here's an example:
public class EmployeeData {
private String name;
private String lastName;
private String joiningDate;
private String promotionDate;
#Override
public boolean equals(Object o) {
return o instanceof EmployeeData other
&& Objects.equals(name, other.name)
&& Objects.equals(lastName, other.lastName)
&& Objects.equals(joiningDate, other.joiningDate)
&& Objects.equals(promotionDate, other.promotionDate);
}
#Override
public int hashCode() {
return Objects.hash(name, lastName, joiningDate, promotionDate);
}
}
Now to check if at least one employee from the currentEmployee list is present in the allEmployees you can dump the contents of allEmployees into a HashSet and iterate over currentEmployee checking the elements against the set. Such check has an amortized cost of O(1), therefore the overall time complexity would be O(n) (*n - represents the number of elements in the currentEmployee list).
public static boolean containsAny(List<EmployeeData> allEmployees,
List<EmployeeData> currentEmployees) {
Set<EmployeeData> allEmp = new HashSet<>(allEmployees);
for (EmployeeData currentEmployee : currentEmployees) {
if (allEmp.contains(currentEmployee)) {
return true;
}
}
return false;
}
We can make this code very concise by using Stream API.
public static boolean containsAny(List<EmployeeData> allEmployees,
List<EmployeeData> currentEmployees) {
Set<EmployeeData> allEmp = new HashSet<>(allEmployees);
return currentEmployees.stream().anyMatch(allEmp::contains);
}
Note: this code behaves in exactly the same way as the snippet you're provided, i.e. it would return after the first match.
It you want to find out whether all elements from the currentEmployee list are present in the currentEmployee, then you can make use of the method Collection.containsAll():
public static boolean containsAll(List<EmployeeData> allEmployees,
List<EmployeeData> currentEmployees) {
Set<EmployeeData> allEmp = new HashSet<>(allEmployees);
return allEmp.containsAll(currentEmployees);
}
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public int hashCode(){
return 100;
}
#Override
public boolean equals(Object te){
return false;
}
public static void main(String[] args) {
MapTest obj = new MapTest();
MapTest obj1 = new MapTest();
obj.setId("test");
obj1.setId("test2");
Map<MapTest,Integer> test = new HashMap<MapTest,Integer>();
test.put(obj, 1000);
test.put(obj1, 2000);
test.put(new MapTest(), 4000);
System.out.println(test.get(obj)); //1000
System.out.println(test.get(obj1));//2000
System.out.println(test.get(new MapTest()));//Null
System.out.println(test.size());//3
}
}
Here the object returns same hashcodes so inorder to avoid overwriting while adding data to HashMap I have a equals method that returns false. Now when I use get I get the proper data example obj1 and obj are giving exact values but my question is when both their hashcodes are same how java distinguishes different objects and return their exact values from hashmap.
The answer is in how HashMap implements the get method. When it checks the equality of the given key against the keys stored in the map (when they have equal hashcode value), it first does a reference equality check. The code below is taken from Java 8's implementation of get and as you see, there is the == check for the keys.
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
If you use another object, with the same id value, it will not work, as there is no structural equality defined in your objects. Try this:
TestMap obj2 = new TestMap();
obj2.setId("test");
System.out.println(test.get(obj2)); // Returns Null
The hashcode equals is the first requirement for hashmap to decide whether two keys the same, but not the only one/
(key1 == key2 || key1.equals(key2)) is also needed.
If you change your equals method to always return true, you can see there will be only one element in the map at last.
I'd like to have list of object sorted with property 'sort_1'. But when I want to remove I'd like it to use property 'id'. The following code represents the problem.
package javaapplication1;
import java.util.TreeSet;
public class MyObj implements Comparable<MyObj> {
public long sort_1;
public long id;
public MyObj(long sort, long id) {
this.sort_1=sort;
this.id=id;
}
#Override
public int compareTo(MyObj other) {
int ret = Long.compare(sort_1, other.sort_1);
return ret;
}
public String toString() {
return id+":"+sort_1;
}
public static void main(String[] args) {
TreeSet<MyObj> lst=new TreeSet<MyObj>();
MyObj o1 = new MyObj(99,1);
MyObj o2 = new MyObj(11,9);
lst.add(o1);
lst.add(o2);
System.out.println(lst);
MyObj o3 = new MyObj(1234, 1);
//remove myObje with id 1
boolean remove=lst.remove(o3);
System.out.println(lst);
}
}
Output of this code is:
[9:11, 1:99]
[9:11, 1:99]
I need to have list sorted as I do a lot of additions to the list. I don't want to explicitly use any 'sort' method. What are my options ?
EDIT:
My requirement is to have: objects with 'id' as unique but there can be object's with duplicate 'sort' value.
Just by chance I found this out yesterday as well. This seems to be an artifact of the implementation of TreeMap (which is what TreeSet uses to store its entries).
TreeMap uses a binary search tree for storing the key/value pairs, but it only ever uses the given Comparator (or the compare function if the key class implements Comparable) to check for equality, as you can see in this code exxcerpt:
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
#SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
I'd almost call this a (not really fixable) bug since the JavaDoc of the Comparable interface explicitly says that returning 0 with the compareTo function does not have to imply "equalness":
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)).
You won't be able to store stuff in the TreeSet the way you want it to. I'd recommend using a normal HashMap or a LinkedHashMap and then just sorting the output when you need to sort it with Collections.sort.
Besides all of this, I always find it strange to implement the Comparable interface. Most things don't really have a "natural" ordering that is immediately obvious. Sometimes this can lead to strange bugs (like this one!), so I typically always sort only when I need it using custom Comparators. Java 8 makes writing those really easy as well!
I think the problem you're having is that you are implementing Comparable, but your implementation seems to be inconsistent with equals - and you have not implemented any equality methods. That is:
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
In your case, when you build these three objects:
MyObj o1 = new MyObj(99,1);
MyObj o2 = new MyObj(11,9);
MyObj o3 = new MyObj(1234, 1);
You will see that o1.compareTo(o3) == -1, while o1.equals(o3) == false.
But you seem to want o1.equals(o3) == true.
Also, recognize that TreeSet.add() returns false if the object already exists in the set. This check is based on the equals() method.
To remedy this, override Object.equals() and Object.hashCode() such that they take into consideration the MyObj.id field, and continue to use the sort_1 field in the compareTo() method when they are not equal.
package javaapplication1;
import java.util.TreeSet;
public class MyObj implements Comparable<MyObj> {
public long sort_1;
public long id;
public MyObj(long sort, long id) {
this.sort_1 = sort;
this.id = id;
}
#Override
public int compareTo(MyObj other) {
return (this.equals(other))? 0 : Long.compare(sort_1, other.sort_1);
}
#Override
public boolean equals(Object obj) {
MyObj other = (MyObj) obj;
return this.id == other.id && this.sort_1 == other.sort_1;
}
#Override
public int hashCode() {
return (int) id;
}
public String toString() {
return id + ":" + sort_1;
}
public static void main(String[] args) {
TreeSet<MyObj> lst = new TreeSet<MyObj>();
MyObj o1 = new MyObj(99L, 1L);
MyObj o2 = new MyObj(11L, 9L);
MyObj o3 = new MyObj(1234L, 1L);
MyObj o4 = new MyObj(1234L, 1L);
System.out.println( "Adding o1: " + lst.add(o1));
System.out.println( "Adding o2: " + lst.add(o2));
System.out.println( "Adding o3: " + lst.add(o3));
System.out.println( "Adding o4: " + lst.add(o4));
System.out.println(lst);
System.out.println("o1.compareTo(o3) : " + o1.compareTo(o3));
System.out.println("o1.equals(o3) : " + o1.equals(o3));
//remove myObje with id 1
boolean remove = lst.remove(o3);
System.out.println(lst);
}
}
Output:
Adding o1: true
Adding o2: true
Adding o3: true
Adding o4: false
[9:11, 1:99, 1:1234]
o1.compareTo(o3) : -1
o1.equals(o3) : false
[9:11, 1:99]
Use Map<Long,MyObject> objectsByIDs; to store your data objects by id: objectsByIDs.put(id,myObjectInstance);. Then you may retrieve them from the map this wayMyObject o = objectsByIDs.get(id); And remove it from both: objectsByIDs.remove(o); lst.remove(o).
this is odd indeed. The documentation of TreeSet.remove() explicitly states that the method invokes equals() in order to find the argument in the Set.
However, the stack trace for remove looks like this
Thread [main] (Suspended (breakpoint at line 18 in MyObj))
MyObj.compareTo(MyObj) line: 18
MyObj.compareTo(Object) line: 1
TreeMap<K,V>.getEntry(Object) line: not available
TreeMap<K,V>.remove(Object) line: not available
TreeSet<E>.remove(Object) line: not available
MyObj.main(String[]) line: 45
Even using Comparator does not work. Seems to me like some smart developer at Sun/Oracle/openJDK thought that doing compareTo() == 0 is the same as equals(). it is not.
You're only option is either use an external data structure to check equality as was suggested, or do the loop yourself, find the item you want, and remove it.
EDIT:
Now I get it. they search for the item using binary search, that is why they do compareTo().
Having a different logic for remove and sorting in TreeSet is almost certainly impossible, and even if it was possible it would break if you looked at it funny. Don't do that.
Trying to mess with the comparator so it does something magic is a terrible idea. Accept that there is one and only one notion of comparison the TreeSet will care about. Anything you want to do with another notion of comparison shouldn't use the TreeSet methods to do that.
What you can do instead is have a special removeId(int) method that does something like
void removeId(int id) {
Iterator<MyObj> itr = set.iterator();
while (itr.hasNext()) {
if (itr.next().id == id) {
itr.remove();
break;
}
}
}
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.
I am trying to sort a TreeMap on key. Key is some custom DataStructure having int, List, String, etc.
The member on which I am expecting a sort has some duplicates. Let's say that member is Rank. More than 1 object can have same rank.
Simplified version example:
NOTE: in the CompareTo method below 0 is not returned intentionally to NOT ignore duplicates.(Please correct me if this is not the right way to avoid duplicates)
import java.util.TreeMap;
public class TreeTest {
public static void main(String[] args) {
TreeMap<Custom,String> t = new TreeMap<Custom,String>();
Custom c1 = new Custom();
c1.setName("a");
c1.setRank(0);
Custom c2 = new Custom();
c2.setName("b");
c2.setRank(1);
Custom c3 = new Custom();
c3.setName("c");
c3.setRank(0);
t.put(c1, "first");
t.put(c2, "Second");
t.put(c3, "Third");
System.out.println(t.keySet());
for(Custom c:t.keySet()){
System.out.println(t.get(c));
}
}
}
And Custom Object
package com.example.ui;
public class Custom implements Comparable<Custom>{
int rank;
String name;
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + rank;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Custom other = (Custom) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (rank != other.rank)
return false;
return true;
}
// 0 is not returned intentionally to NOT ignore duplicates.
public int compareTo(Custom o) {
if(o.rank>this.rank)
return 1;
if(o.rank==this.rank)
return -1;
return -1;
}
}
Output::
[com.example.ui.Custom#fa0, com.example.ui.Custom#fbe, com.example.ui.Custom#f80]
null
null
null
Expected:
First, Second, Third based on Rank 0,1,0 respectively.
I looked at couple of examples on Google. Most of them were basic usage on TreeMap sort using keys or values with primitive datatypes, but none with duplicates when sorting member
is a part of custom key DataStructure.
Please help?
The problem is that your implementation of compareTo is not consistent with equals, which is required by TreeMap. From the API docs:
Note that the ordering maintained by a sorted map (whether or not an
explicit comparator is provided) must be consistent with equals if
this sorted map is to correctly implement the Map interface.
One possible consistent implementation would be to first compare by rank and then by name if the rank values are equal. For two instances of Custom with equal ranks and identical names you should not expect to be able to store them both as keys within the same Map - This violates the contract of Map.
public int compareTo(Custom o) {
int ret = this.rank - o.rank;
// Equal rank so fall back to comparing by name.
if (ret == 0) {
ret = this.name.compareTo(o.name);
}
return ret;
}
As mentioned, your implementation of equals and compareTo are not consistent with each other. If I read your question correctly, what you require is to preserve duplicates that have the same key. I'd recommend you to look into the TreeMultimap of the Google Guava collections. It creates set containers for each value object sothat different values having the same key are preserved.
e.g.
treeMultimap.put ("rank1", "Joe");
treeMultimap.put ("rank1", Jane");
treeMultimap.get ("rank1"); // Set("Joe","Jane");
The constrain in this data structure is that K,V pairs must be unique. That is, you can't insert ("rank1", "Joe") twice in the Multimap.
One important note: The reason why you see so many examples of Map, using simple types and, in particular, strings, is that keys in a map must be immutable. The equals and hashcode values of an object must not change in the time it's used as a key in a map. Translated to your example, you cannot do customObject.setRank(...) and updates a rank value when it's used as a key. To do so, you first need to remove the key and its values, update it and then insert it again.
You can also do it by implementing Comparator as anonymous inner type and override compare() to return desired comparison.
public class TreeMaps
{
public static void main(String[] args)
{
Custom c1 = new Custom(1,"A");
Custom c2 = new Custom(3,"C");
Custom c3 = new Custom(2,"B");
TreeMap<Custom , Integer > tree = new TreeMap<Custom, Integer> (new Comparator<Custom>() {
#Override
public int compare(Custom o1, Custom o2) {
return o1.rank - o2.rank;
}
});
tree.put(c1, 1);
tree.put(c2, 2);
tree.put(c3, 3);
System.out.println(tree);
}
}
class Custom
{
int rank ;
String name ;
public Custom(int rank , String name) {
this.rank = rank ;
this.name = name ;
}
#Override
public String toString()
{
return "Custom[" + this.rank + "-" + this.name + "]" ;
}
}