I know this has been an issue for a while now, and checked all previously answers I could get, but still this one doesn't work.
The object 'crew' represents crewmembers with ranks and other items. The comparison should be made by comparing 'assigned_rank', an int value, and if this value is equal in both instances, then 'is_trainer', a boolean, should make the difference.
This method worked great as long as it was running with java < 7. But since Java 7 I keep getting this one:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:714)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:451)
at java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:376)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:182)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
at dormas_flightlog.Query.getCrew(Query.java:714)
Here is the source, where some potentially dangerous parts have allready been out-commented, but it still does not work:
public class crew implements Serializable, Comparable<crew> {
private static final long serialVersionUID = 36L;
private int flightID = 0;
private int assigned_rank = 25;
private boolean is_trainer = false;
...
#Override
public int compareTo(crew him) {
int myRank = this.getAssigned_rank();
int hisRank = him.assigned_rank;
if (this == him) {
return 0;
}
if (myRank > hisRank) {
return 1;
}
if (myRank < hisRank) {
return -1;
}
if (myRank == hisRank) {
// if (is_trainer && !o.is_trainer) {
// i = 1;
// }
// if (!is_trainer && o.is_trainer) {
// i = -1;
// }
// if (is_trainer && o.is_trainer) {
// i = 0;
// }
// if (!is_trainer && !o.is_trainer) {
// i = 0;
// }
return 0;
}
return 0;
}
#Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + this.assigned_rank;
hash = 31 * hash + (this.is_trainer ? 1 : 0);
return hash;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
int myRank = this.getAssigned_rank();
int hisRank = 0;
if (o instanceof crew) {
crew him = (crew) o;
hisRank = him.assigned_rank;
} else {
return false;
}
if (myRank > hisRank) {
return false;
}
if (myRank < hisRank) {
return false;
}
if (myRank == hisRank) {
// if (is_trainer && !o.is_trainer) {
// i = 1;
// }
// if (!is_trainer && o.is_trainer) {
// i = -1;
// }
// if (is_trainer && o.is_trainer) {
// i = 0;
// }
// if (!is_trainer && !o.is_trainer) {
// i = 0;
// }
return true;
}
return false;
}
}
Implementing equals() was just a try to solve this problem. The given exception comes with or without equals(). I cannot see how the compareTo-method violates its contract. Any help is greatly appreciated....one day this code has to work with java 7 and I don't know how...
Thanks
see this:
From http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#source
Area: API: Utilities Synopsis: Updated sort behavior for Arrays and
Collections may throw an IllegalArgumentException
Description: The sorting algorithm used by java.util.Arrays.sort and
(indirectly) by java.util.Collections.sort has been replaced. The new
sort implementation may throw an IllegalArgumentException if it detects
a Comparable that violates the Comparable contract. The previous
implementation silently ignored such a situation. If the previous
behavior is desired, you can use the new system
property java.util.Arrays.useLegacyMergeSort, to restore previous
mergesort behavior.
Nature of Incompatibility: behavioral
RFE: 6804124
For more detailed info, see the bug database reference here.
maybe you just have NaN values which you compare through Collections.sort(...), this has been a problem to me and I got that exception even having right implementation of compare(obj1, obj2) method! Check that!
I was able to solve this error cause it was a bug in jdk7.
here I found the solution:
"Comparison method violates its general contract!" - TimSort and GridLayout
Basically i just had to add the
JAVA_OPTS="$JAVA_OPTS -Djava.util.Arrays.useLegacyMergeSort=true"
to my jboss
Unfortunately, none of the solutions work for Android. TimSort is used deep in Android's ViewGroup relating to addChildrenForAccessibility that shows up under Java 7 & 8. No user code is involved in any comparison.
From other reports, it is related to having RelativeLayout with overlapping items as is commonly done. For example, a TextView that appears over an Image, or two items at the same location, where you only set one visible at a time.
https://code.google.com/p/android/issues/detail?id=55933
I've not found any way around the bug. You can't set a -Djava option in Android Studio or Eclipse (at least that I could find). Forcing use of Java 1.6 should work, but doesn't. Seems like Amazon's newer Fire tablets and phones are far more sensitive to this bug than other devices.
There are rumors Java 9 will have a fix such as a run-time option that works, but with a bug that's been around for years, I have doubts it will ever be fixed - especially considering the animosity between Oracle and Google. Any yes, perhaps the bug is really deep in the Android code and should be fixed there. With more than a billion devices out there, that's not a viable solution for all the existing devices.
Related
below the code is using a private method to add to the variable count. Below that variable are conditionals which by my understanding, will not run until the recursion stack traces upword. Am I correct? My test is failing, and I am trying to see if it is because my code is wrong or I'm using recursion wrong.
public boolean containsRightRedEdge() {
int count = 0;
count += containsRightRedEdge(root);
if(count > 0) return true;
return false;
}
private int containsRightRedEdge(Node n) {
if (n == null) return 0;
if (isRed(n.right)) {
return 1;
}
return containsRightRedEdge(n.left) + 0 + containsRightRedEdge(n.right);
}
I would say you are using recursion pretty much correctly, but your choice of method names could be less confusing, and your logic could be simplified.
I am not too familiar with the algorithm you're trying to implement, but you might try something like this:
public boolean containsRightRedEdge(Node root) {
return getNumRightRedEdges(root) > 0;
}
private int getNumRightRedEdges(Node n) {
if (n == null) return 0;
if (isRedEdge(n)) return 1;
return getNumRightRedEdges(n.left) + getNumRightRedEdges(n.right);
}
Generally a recursive method shouldn't have the same name as a non-recursive method. These method names communicate more clearly what each one does. Also your base cases might be wrong as you've got them written currently based on how I'm interpreting the algo should work. Of course, I don't know the code inside isRed() so I'm probably making wrong assumptions here.
The code above in my question, is the correct way to use recursion in this instance. I just had a typo which is now resolved. Leaving the question for other peoples reference.
I have a class 'CoAutoria' that's suposed to hold 2 instances of an 'Author' class (which only has a name, for now) and the number of articles those authors have in common.
In order to figure out the top 10 of co-authors (regarding number of articles) I created a TreeSet of 'CoAutoria', to hold the total of articles, for each pair.
I need to cycle through a Map of years, gather the different authors and their respective Set of co-Authors. Then, for each pair, create an instance of 'CoAutoria' and: add it to the treeset (if it doesn't already exists); or simply sum its number of articles to the one existing on the set.
I already created the compareTo method, to insert it on the treeset, and created the equals method so that the order of the authors doesn't matter.
Here's the main code:`
public class CoAutoria implements Comparable<CoAutoria>
{
private Autor autor1;
private Autor autor2;
private int artigosComum;
(...)
}
#Override
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if((autor1.equals(a2.getAutor1()) && autor2.equals(a2.getAutor2())) || (autor1.equals(a2.getAutor2()) && autor2.equals(a2.getAutor1())))
{
return 0;
}
else
{
return 1;
}
}
#Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if( o == null || o.getClass() != this.getClass())
return false;
CoAutoria ca = (CoAutoria) o;
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = ca.getAutor1().getNome();
String caAutor2 = ca.getAutor2().getNome();
if((thisAutor1.equals(caAutor1) && thisAutor2.equals(caAutor2)) || (thisAutor1.equals(caAutor2) && thisAutor2.equals(caAutor1)))
{
return true;
}
else
{
return false;
}
}
The main problem is: When I check if the set already has a certain instance of 'CoAutoria', (I'm using the contains() method of TreeSet), it gives me faulty results...sometimes it checks correctly that the Pair A-B already exists in that set (on the form of B-A), but sometimes it doesn't... For what I've read, the contains uses the equals method, so that's not suposed to happen..right?
[EDIT:]
Since the first post I started to think that maybe the problem resided on the compareTo..So I changed it to
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if(this.equals(a2))
{
System.out.println("return 0");
return 0;
}
else
{
int aux = thisAutor1.compareTo(caAutor1);
if(aux != 0)
{
return aux;
}
else
{
return thisAutor2.compareTo(caAutor2);
}
}
}
But it still gives my bad results..I thought I'd figured it now: if it's the same 'CoAutoria', I return 0, if not I go through the names, and order it by their compareTo values..but something's missing
Your contains method is breaking, because your compareTo method is always returning 0 or positive, no negatives. This means your compareTo is inconsistent. A correct implementation should return 0 if the authors are the same, or positive and negative values when the authors are different.
Example (assuming author1 is different than author2):
int i = author1.compareTo(author2); // i should be positive or negative
int j = author2.compareTo(author1); // j should be the opposite of i
Yours will return 1 for both of the above cases, which will make ordered Collections not work as no element is ever smaller. As another example imagine if you had a Binary Tree(an ordered collection) that had the elements [1-10]. If you were searching for the element 5, your binary tree when comparing 5 against any element would always say that it was equal or greater.
How exactly you should change it is up to you. But an idea would be to sort the authors by name, then iterate over both collections and compare the authors together lexicographically.
EDIT: Even after your edit to your methods they are still not consistent. Try the following, they aren't the most efficient but should work unless you really want to optimize for speed. Notice they first sort to make sure author1 and author2 are in order before they are compared with the other CoAutor which is also sorted. I don't do any null checking and assume both are valid authors.
#Override
public boolean equals(Object o){
if (o == null || !(o instanceof CoAutoria)) return false;
if (o == this) return true;
return this.compareTo((CoAutoria)o) == 0;
}
#Override
public int compareTo(CoAutoria o) {
List<String> authors1 = Arrays.asList(autor1.getNome(), autor2.getNome());
List<String> authors2 = Arrays.asList(o.autor1.getNome(), o.autor2.getNome());
Collections.sort(authors1);
Collections.sort(authors2);
for (int i=0;i<authors1.size();i++){
int compare = authors1.get(i).compareTo(authors2.get(i));
if (compare != 0)
return compare;
}
return 0;
}
I am implementing my own Java class for sorting a List<T> in eclipse. I made break points in comparsion statements and it does not work as I unexpected!
Here is the code :
if(doy2 < don && doy1>don)
{
return 1;
}
else if (doy2 > don && doy1<don)
{
return -1;
}
else
{
return 0;
}
Even though the doy2 > don && doy1<don statement evaluates to true, and the code reaches return -1, but it also goes to else part and return 0. Why exactly?
Edited: The Complete code
public class DateCompartor implements Comparator<BirthdayContact> {
#Override
public int compare(BirthdayContact arg0, BirthdayContact arg1) {
Date now=new Date();
Date bd1=arg0.GetBirthDay();
Date bd2=arg1.GetBirthDay();
DateTime dt1=new DateTime(bd1);
DateTime dtnow=new DateTime(now);
DateTime dt2=new DateTime(bd2);
int doy1=dt1.getDayOfYear();
int doy2=dt2.getDayOfYear();
int don=dtnow.getDayOfYear();
if(doy2 < don && doy1>don)
{
return 1;
}
else if (doy2 > don && doy1<don)
{
return -1;
}
else
{
return 0;
}
}
}
I am comparing two dates with current date, and which ever is closer to current date, should get upper in the list.
I'd rebuild your code and step through it again. The 'weirdness' that you note might be due to the source and bytecode being out of synch.
I'd ask that you post the entire Comparable implementation. Comparator usually compares two objects, but your comparison statements appear to involve three. Perhaps you can clarify exactly what you're doing.
Be assured that your code is incorrect. You have a mental model of how this should work in mind, but your assumptions don't match reality. Your job is to bring them back into harmony.
UPDATE:
Given the new information, I'd certainly not code it the way you did. I'd calculate the difference from each date to today's time and compare those. I'm not interested enough to test it out myself. See if that works out better for you.
public class DateCompartor implements Comparator<BirthdayContact> {
#Override
public int compare(BirthdayContact arg0, BirthdayContact arg1) {
Date now=new Date();
// Read Sun's Java coding standards; these don't follow the standard.
Date bd1=arg0.GetBirthDay();
Date bd2=arg1.GetBirthDay();
long dist1 = Math.abs(bd1.getTime() - now.getTime());
long dist2 = Math.abs(bd2.getTime() - now.getTime());
return dist1.compareTo(dist2); // Might need wrapper Long here.
}
}
I'd like to call a method that either returns false, or an integer. At the moment my code is:
int winningID = -1;
if((ID = isThereAWinner()) != -1) {
// use the winner's ID
} else {
// there's no winner, do something else
}
private int isThereAWinner() {
// if a winner is found
return winnersID;
// else
return -1;
}
I don't like the if((ID = isThereAWinner()) != -1) bit as it doesn't read very well, but unlike C you can't represent booleans as integers in Java. Is there a better way to do this?
I would use something similar to Mat's answer:
class Result {
public static Result withWinner(int winner) {
return new Result(winner);
}
public static Result withoutWinner() {
return new Result(NO_WINNER);
}
private static final int NO_WINNER = -1;
private int winnerId;
private Result(int id) {
winnerId = id;
}
private int getWinnerId() {
return winnerId;
}
private boolean hasWinner() {
return winnerId != NO_WINNER;
}
}
This class hides the implementation details of how you actually represent if there were no winner at all.
Then in your winner finding method:
private Result isThereAWinner() {
// if a winner is found
return Result.withWinner(winnersID);
// else
return Result.withoutWinner();
}
And in your calling method:
Result result = isThereAWinner();
if(result.hasWinner()) {
int id = result.getWinnerId();
} else {
// do something else
}
It may seem a little bit too complex, but this approach is more flexible if there would be other result options in the future.
What about something like:
private int getWinnerId() {
// return winner id or -1
}
private boolean isValidId(int id) {
return id != -1; // or whatever
}
int winnerId = getWinnerId();
if (isValidId(winnerId)) {
...
} else {
...
}
This is all quite subjective of course, but you usually expect an isFoo method to provide only a yes/no "answer".
The problem is you are trying to return two values at once. The approach you have taken is the simplest for this. If you want a more OO or design pattern approach I would use a listener pattern.
interface WinnerListener {
void onWinner(Int winnerId);
void noWinner();
}
checkWinner(new WinnerListener() {
// handle either action
});
private void checkWinner(WinnerListener wl) {
// if a winner is found
wl.onWinner(winnersID);
// else
wl.noWinner();
}
This approach works well with complex events like multiple arguments and multiple varied events. e.g. You could have multiple winners, or other types of events.
I'm afraid not. To avoid errors caused by mistaking if(a == b) for if(a = b), Java removes the conversion between boolean type and number types. Maybe you can try exceptions instead, but I think exception is somewhat more troublesome. (My English is not quite good. I wonder if I've made it clear...)
Perhaps you may wish to consider exceptions to help you with your understanding of asthetics of coding.
Use Integer instead of int and return null instead of -1. Look from this point: "I am returning not integer, but some object that represents winner identity. No winner - no instance"
Joe another suggestion, this is constructed based on #Mat and #buc mentioned little while ago, again this is all subjective of course I'm not sure what the rest of your class/logic is. You could introduce an enum with different ResultStatuses if it makes sense within the context of your code/exmaple.
As Matt mentioned you would expect isValid method to return a boolean yes/no (some may also complain of readability)
public enum ResultStatus {
WINNER, OTHER, UNLUCKY
}
This could be an overkill as well and depends on the rest of your logic (and if logic is expanding) but I thought I'll suggest nonetheless my two cents! So therefore in your public class (similar to #bloc suggested) you could have a method such as below that will return the status of the result checked.
public ResultStatus getResultStatus() {
if (isWinner()) {
return ResultStatus.WINNER;
} else {
return isOtherCheck() ? ResultStatus.OTHER : ResultStatus.UNLUCKY;
}
}
So I've been struggling with a problem for a while now, figured I might as well ask for help here.
I'm adding Ticket objects to a TreeSet, Ticket implements Comparable and has overridden equals(), hashCode() and CompareTo() methods. I need to check if an object is already in the TreeSet using contains(). Now after adding 2 elements to the set it all checks out fine, yet after adding a third it gets messed up.
running this little piece of code after adding a third element to the TreeSet, Ticket temp2 is the object I'm checking for(verkoopLijst).
Ticket temp2 = new Ticket(boeking, TicketType.STANDAARD, 1,1);
System.out.println(verkoop.getVerkoopLijst().first().hashCode());
System.out.println(temp2.hashCode());
System.out.println(verkoop.getVerkoopLijst().first().equals(temp2));
System.out.println(verkoop.getVerkoopLijst().first().compareTo(temp2));
System.out.println(verkoop.getVerkoopLijst().contains(temp2));
returns this:
22106622
22106622
true
0
false
Now my question would be how this is even possible?
Edit:
public class Ticket implements Comparable{
private int rijNr, stoelNr;
private TicketType ticketType;
private Boeking boeking;
public Ticket(Boeking boeking, TicketType ticketType, int rijNr, int stoelNr){
//setters
}
#Override
public int hashCode(){
return boeking.getBoekingDatum().hashCode();
}
#Override
#SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o){
Ticket t = (Ticket) o;
if(this.boeking.equals(t.getBoeking())
&&
this.rijNr == t.getRijNr() && this.stoelNr == t.getStoelNr()
&&
this.ticketType.equals(t.getTicketType()))
{
return true;
}
else return false;
}
/*I adjusted compareTo this way because I need to make sure there are no duplicate Tickets in my treeset. Treeset seems to call CompareTo() to check for equality before adding an object to the set, instead of equals().
*/
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1;
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
//Getters & Setters
On compareTo contract
The problem is in your compareTo. Here's an excerpt from the documentation:
Implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
Your original code is reproduced here for reference:
// original compareTo implementation with bug marked
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1; // BUG!!!! See explanation below!
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
Why is the return 1; a bug? Consider the following scenario:
Given Ticket t1, t2
Given t1.boeking.compareTo(t2.boeking) == 0
Given t1.equals(t2) return false
Now we have both of the following:
t1.compareTo(t2) returns 1
t2.compareTo(t1) returns 1
That last consequence is a violation of the compareTo contract.
Fixing the problem
First and foremost, you should have taken advantage of the fact that Comparable<T> is a parameterizable generic type. That is, instead of:
// original declaration; uses raw type!
public class Ticket implements Comparable
it'd be much more appropriate to instead declare something like this:
// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>
Now we can write our compareTo(Ticket) (no longer compareTo(Object)). There are many ways to rewrite this, but here's a rather simplistic one that works:
#Override public int compareTo(Ticket t) {
int v;
v = this.boeking.compareTo(t.boeking);
if (v != 0) return v;
v = compareInt(this.rijNr, t.rijNr);
if (v != 0) return v;
v = compareInt(this.stoelNr, t.stoelNr);
if (v != 0) return v;
v = compareInt(this.ticketType, t.ticketType);
if (v != 0) return v;
return 0;
}
private static int compareInt(int i1, int i2) {
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return +1;
} else {
return 0;
}
}
Now we can also define equals(Object) in terms of compareTo(Ticket) instead of the other way around:
#Override public boolean equals(Object o) {
return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
}
Note the structure of the compareTo: it has multiple return statements, but in fact, the flow of logic is quite readable. Note also how the priority of the sorting criteria is explicit, and easily reorderable should you have different priorities in mind.
Related questions
What is a raw type and why shouldn't we use it?
How to sort an array or ArrayList ASC first by x and then by y?
Should a function have only one return statement?
This could happen if your compareTo method isn't consistent. I.e. if a.compareTo(b) > 0, then b.compareTo(a) must be < 0. And if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) must be > 0. If those aren't true, TreeSet can get all confused.
Firstly, if you are using a TreeSet, the actual behavior of your hashCode methods won't affect the results. TreeSet does not rely on hashing.
Really we need to see more code; e.g. the actual implementations of the equals and compareTo methods, and the code that instantiates the TreeSet.
However, if I was to guess, it would be that you have overloaded the equals method by declaring it with the signature boolean equals(Ticket other). That would lead to the behavior that you are seeing. To get the required behavior, you must override the method; e.g.
#Override
public boolean equals(Object other) { ...
(It is a good idea to put in the #Override annotation to make it clear that the method overrides a method in the superclass, or implements a method in an interface. If your method isn't actually an override, then you'll get a compilation error ... which would be a good thing.)
EDIT
Based on the code that you have added to the question, the problem is not overload vs override. (As I said, I was only guessing ...)
It is most likely that the compareTo and equals are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on the compareTo and equals methods of the Boeking class.
The first if statement of the Ticket.compareTo looks highly suspicious. It looks like the return 1; could cause t1.compareTo(t2) and t2.compareTo(t1) to both return 1 for some tickets t1 and t2 ... and that would definitely be wrong.