Java Collections.Sort Comparison method violates its general contract - java

I'm getting an error in my code since yesterday and I'm not sure why!
I googled a lot and found this Stack-Post.
I use this funtion to sort users by name and create date.
This function has worked since 2 years and now I'm getting an error for one user and I don't know what has changed.
I tried to check my data if there is anything corrupt but couldn't find anything wrong.
After reading zhe Stack-Post I still didn't understand fully what's the problem or what has changed in my project.
public void sortUsers(List<Users> sortList) {
Collections.sort(sortList, new Comparator<Users>() {
public int compare(Users user1, Users user2) {
Integer comp = 0;
comp = user1.getUsername().compareTo(user2.getUsername());
if (comp == 0) {
comp = user1.getCreateDate().before(user2.getCreateDate()) ? -1 : 1;
}
return comp;
}
});
}

comp = user1.getCreateDate().before(user2.getCreateDate()) ? -1 : 1;
This line can never return zero, in the case that the dates are equal. This means that it's not anti-symmetric (i.e. sgn(a.compareTo(b)) = -sgn(b.compareTo(a))).
The exact fix depends on what class getCreateDate() returns, but it could be something like:
comp = user1.getCreateDate().compareTo(user2.getCreateDate());
However, an easier way to do construct a compliant comparator would be:
Comparator.comparing(Users::getUsername).thenComparing(Users::getCreateDate)
or similar.

Related

Overridden equals is not called

I have a class Reminder that has both hashcode and equals overridden like this:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cronExpression == null) ? 0 : cronExpression.hashCode());
result = prime * result + ((subject == null) ? 0 : subject.hashCode());
result = prime * result + timeout;
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 (!(obj instanceof Reminder))
return false;
Reminder other = (Reminder) obj;
if (cronExpression == null) {
if (other.cronExpression != null)
return false;
} else if (!cronExpression.equals(other.cronExpression))
return false;
if (subject == null) {
if (other.subject != null)
return false;
} else if (!subject.equals(other.subject))
return false;
if (timeout != other.timeout)
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
Both overrides were automatically generated using Eclipse. I'm using the Reminder in a HashSet instantiated like this: private Set<Reminder> localReminders = new HashSet<Reminder>();
When updating this set, I'm using localreminders.contains(anotherReminder) and for some reason that I've been trying to figure out for a while now, it does not call the overridden equals method. Even though cronExpression, subject, timeout and type of the reminders compared are the same, contains returns false.
So far I've only come across answers where equalsand/or hashcode were implemented incorrectly or not at all. Any help would be very much appreciated!
Let me know if you need more information like additional code for this!
EDIT: the properties used in hashcodeand equals are all String, except for timeout which is int.
EDIT2: while debugging, I currently have these two reminders in my HashSet:
Reminder [cronExpression=0 10 10 ? * *, subject=, type=OTHER_TYPE, audioPath=/other_type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for Task#af94b0, timeout=35940]
Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for ReminderTask#f1f373, timeout=35940]
The one that I am checking whether it is contained in my set looks like this:
Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=null, timeout=35940]
The only difference I can spot here is that in one, the future is null while it is actually set in the other. But since the future property is not included in either hashcode or ´equals`, this should not matter.
As you can see in the implementation of the equals method you call cronExpression.equals(other.cronExpression) and subject.equals(other.subject) and type.equals(other.type). If only one of this is not implemented right then you get wrong result. Please check if all of the properties that you use in this method has correct implementation of equals.
By the way also check the implementation of the methods cronExpression.hashCode(), subject.hashCode() and type.hashCode(). They are used in your hashCode method.
Edit: If as you said cronExpression, subject and type are Strings then it should be easy for you to make main method populate two objects from class Reminder with the same info and test the methods. To be sure where is the problem you can call if(firstReminder.equals(secondReminder)).
From my experiance you can have problems with the strings. For example if one of the string has space at the end is different then the other or similar kind of issue.
Edit 2: Ok, from your input It seems this objects to have the same strings.
Is it possible Reminder class to be extended and you to compare child class object with Reminder object? If this happen in the child class equals and hashcode can be implemented and then the result can be wrong.
Also just be sure can you log the size of each string? This is very strange.
Maybe it is possible you to have hidden character. See this for more information: Is there an invisible character that is not regarded as whitespace?
Good luck!
The Problem may be with your hashcode() method. It should generate a unique code. There are some guidelines to overridde hashcode().Hashcode Best Practice
If hashcode of objects are different then equals() will not called even if they are equal.
Because HashSet first check hashcodes of both objects and if hashcodes are equal then only it will call equals() to check whether both objects are really equal or not.
Read Oracle Javadoc to override hashcode override contract
You need to provide us the import of the Reminder class if you want us to be able to help you.
For your culture and curiosity : java.util.HashSet.contains(Object o), reading the code it points to :
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
which itself points to :
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
As you can see, the important part of your implementation is Reminder.hashCode().
Regarding your specific issue : As you are probably using quartz for org.quartz.CronExpression, you can see that org.quartz.CronExpression.hashCode() method is not implemented, so it calls it's parent hashCode(), which is Object.hashCode().
From the documentation (JRE 7), you can read :
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)
So both of similar item with different instance of org.quartz.CronExpression will have different hashCode() result.

Can you equalsIgnoreCase a Set?

So, say I have a program, and the users target in the Set is called 'Computer'. The user enters in 'computer', 'COMPUTER','ComPutEr', but it never finds it because it's not capitalized correctly.
How would you go about taking a Set words = ... ... ... and taking the information inside of Set and checking if it is equal to 'Computer', but ignoring capitalization. Oooor! Making it so everything else is lowercase, but the first character.
Example Code:
Set<String> words= this.getConfig().getConfigurationSection("Test").getKeys(false);
if( allGroups.contains('Computer') ) {
Please ignore the this.getConfig().getConfigurationSection("Test").getKeys(false);. I am looking for an answer to fix a Minecraft plugin I'm making, but this seems like a more basic Java knowledge question.
Thank you for the help guys
You could possibly use a TreeSet because it sorts the input it can take a comparator. Using that you could implement the behaviour you want. Something like
Comparator<String> comparator = new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
return o1.toLowerCase().compareTo(o2.toLowerCase());
}
};
Set<String> set = new TreeSet<>(comparator);
Or (from the comments) String.CASE_INSENSITIVE_ORDER like
Set<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
I ended up solving the problem, thanks to Elliot bringing it to my attention.
Set<String> words= new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
words.addAll(this.getConfig().getConfigurationSection("Test").getKeys(false));
args[0] = args[0].toLowerCase();
args[0] = args[0].substring(0, 1).toUpperCase() + args[0].substring(1);
if( words.contains(args[0]) ) {
Although this is not a great way of going about it in my book, I have used this same method of solving this problem with an ATM program I wrote. I'm currently thinking of a way to make the String 'args[0]' just 1 line to fix it all, but this is what currently works for me.
Thanks!

Objects: how to use the string declared before

I have the following code
public void makeBaby() {
String duplicate;
boolean full = false;
boolean same = false;
for (int i = 0; i < park.length; i++) {
if (park[i] == null) {
full = false;
} else if (i == park.length - 1 && park[i] != null) {
full = true;
}
if (i != park.length - 1) {
for (int j = 1; j < park.length; j++) {
if (park[i].name.equals(park[j].name)) {
same = true;
duplicate = park[i].name;
}
}
}
if (!full) {
System.out.println("The zoo is full. Cannot make any more babies");
} else if (!same) {
Animal duplicate = new Animal((float) 0.1 * park[i].mass,park[i].name, park[i].legs);
addAnimal(duplicate);
}
}
}
As may be able to see in the code, I have to see if the array called park is full and if not, I need to make a baby animal. but before that, I also need to check if there are 2 animals that have the same type(name).
But I am experiencing problems with the line
Animal duplicate = new Animal((float) 0.1 * park[i].mass,park[i].name, park[i].legs);
addAnimal(duplicate);
because the somehow java does not recognize the duplicate as the duplicate I set in the if statement above.
It is simply creating an animal called duplicate which is not what I want to do..
There are some other things that don't add up. For example:
full is declared as false. In the first if-statement:
if (park[i] == null) {
full = false;
which is already known.
If i am not mistaken:
(!full)
is read as false and is meant to be executed when full = true.
(full)
same goes for (!same)
I am no coding genius, so correct me if i'm wrong. :)
You have two variables each named duplicate, I suspect that this is confusing you.
You have variable
String duplicate;
and you have
Animal duplicate
What is your intention? they are different types.
I suspect you mean:
Animal theNewBaby ...
addAnimal(theNewBaby);
And somewhere you intend the name of the new baby to be the String you stored in duplicate. As we can't see your Animal() constructor we don't know.
You have multiple problems here... For one you have two duplicate variables... one String and one Animal. So depending on which parameter your addAnimal() method takes you might be grabbing the wrong duplicate.
Secondly, I don't believe your "Same" code check is going to work as you are looping through the same list twice. Unless you exclude the first found animal from your second j loop you are always going to find a match even when there is only one animal.
That might work for amoebas but not for elephants! :-)
You only have to rename your variable duplicate(for example sDuplicate with the type as prefix), there is no other way if you want to give addAnimal() the string duplicate you set in the if statement. And add to the if condition:
&& i!=j
then your duplicate check will work.

Vector<Customer> C++, List<Customer> & Vector<Customer> Java

I learned this piece of code in University, while learning C++.. and I later used the same thing in C#... but now I want to use it in Java... I have looked all over Internet for similar thing, but i dun even no how to phrase it so i get the correct results.
So umm please let me know how to do this is in JAVA and also let me know what is this way called??? I know What it does.. but i have no idea how it does it.. so mind explaining it as well...
C++ is the same thing as below except one DIFFERENCE look Below
C# if (Customers[i].delExecute(delIn) == true)
C++ if (Customers[i]->delExecute(delIn) == true)
Java ?? if (????????????????????????????????)
So here I go ill try my best to explain.
C#
Class called Customer with custId etc etc
List<Customer> Customers = new List<Customers>
public void delCust(int delIn)
{
for (int i = 0; i < Customers.Count(); i++)
{
if (Customers[i].delExecute(delIn) == true)
{
Customers.Remove(Customers[i]);
break;
}
}
}
bool delExecute(int delInput) {
if (custId == delInput) {
return true;
}
return false;
}
OK right so the above code works
now I want the same thing with JAVA... anyone got any ideas how?
if (Customers.get(i).delExecute(delIn))
BTW, there is no need to say == true explicitly
In Java, Vector is an out-dated class which you should probably avoid using. Instead, you should use an ArrayList. Either way, though, both are Lists, and thus implement get for random-access.
You can implement what you're attempting as follows...
if (customers.get(i).delExecute(delIn))
Note that == true is redundant, as it is effectively an identity (true == true is true, false == true is false). You should also avoid capitalized field names in Java.
Now, there is also a preferred way to do what you are attempting using an Iterator.
private List<Customer> customers = new ArrayList<Customers>();
public void delCust(int delIn) {
final Iterator<Customer> cursor = customers.iterator();
while (cursor.hasNext()) {
if (cursor.next().delExecute(delIn)) {
cursor.remove();
break;
}
}
}
... coupled with:
boolean delExecute(int delInput) {
return custId == delInput;
}

Casting a Object to HashMap

I'm having trouble working out how to count instances of Values in a HashMap.
I have seen that there is methods attached to the Object class that look as if they are able to help me, so I've tried to cast those in to work but I must be doing something wrong somewhere.
If there's an easier way, I haven't found it yet. NB: Library is my HashMap.
public void borrowBooks(String id, String name, String sid, String sname) {
if((getKeyFromValue(Books, name).equals(id))&&(getKeyFromValue(Students, sname).equals(sid))){
if((Object)Library.countValues(sid)!=5){
Library.put(id, sid);
}
else{
System.out.println("You have exceeded your quota. Return a book before you take one out." );
}
}
}
Which doc are you looking at ? The Javadoc for Hashmap doesn't specify a countValues() method.
I think you want a HashMap<String, List<String>> so you store a list of books per student (if I'm reading your code correctly).
You'll have to create a list per student and put that into the HashMap, but then you can simply count the entries in the List using List.size().
e.g.
if (Library.get(id) == null) {
Library.put(id, new ArrayList<String>());
}
List<String> books = Library.get(id);
int number = books.size() // gives you the size
Ignoring threading etc.
First: There is (almost) no point in ever casting anything to Object. Since everything extends Object, you can always access the methods without casting.
Second: The way you're casting actually casts the return value, not the Library. If you were doing a cast that was really necessary, you would need an extra set of parentheses:
if(((Object)Library).countValues(sid) != 5)
Third: There is no countValues method in either HashMap or Object. You'll have to make your own.
This is the general algorithm to use (I'm hesitant to post code because this looks like homework):
initialize count to 0
for each entry in Library:
if the value is what you want:
increment the count
int count = 0;
for(String str : Library.values())
{
if(str == sid)
count++;
if(count == 5)
break;
}
if(count < 5)
Library.put(id, sid);
else
System.out.println("You have exceeded your quota. Return a book before you take one out." );

Categories