Comparision method violates its general contract exception - java

I have check why this exception occurs. Could you please guide me? What I am doing wrong in below code block?
Collections.sort(discountGroupDetailList, new Comparator<DiscountGroupDetail>() {
long bdsIdOne;
long bdsIdTwo;
public int compare(DiscountGroupDetail discountGroupDetailOne, DiscountGroupDetail discountGroupDetailTwo) {
boolean discOne;
boolean discTwo;
SimpleDateFormat DATE_FORMATER = new SimpleDateFormat("yyyy/MM/dd");
int compVal = 0;
discOne = discountGroupDetailOne != null && discountGroupDetailOne.getServiceId() != null
&& discountGroupDetailOne.getServiceId().getCodeAndVersion() != null
&& discountGroupDetailOne.getServiceId().getCodeAndVersion().getCodeDetail() != null
&& discountGroupDetailOne.getServiceId().getCodeAndVersion().getCodeDetail().getCode() != null;
discTwo = discountGroupDetailTwo != null && discountGroupDetailTwo.getServiceId() != null
&& discountGroupDetailTwo.getServiceId().getCodeAndVersion() != null
&& discountGroupDetailTwo.getServiceId().getCodeAndVersion().getCodeDetail() != null
&& discountGroupDetailTwo.getServiceId().getCodeAndVersion().getCodeDetail().getCode() != null;
if (discOne == true && discTwo == true) {
bdsIdOne = MessageBuilderHelper.getBaseDiscountServiceById(discountGroupDetailOne.getServiceId())
.getBdsIdNbr();
bdsIdTwo = MessageBuilderHelper.getBaseDiscountServiceById(discountGroupDetailTwo.getServiceId())
.getBdsIdNbr();
compVal = (int) (bdsIdOne - bdsIdTwo);
}
DateRange dateRangeOne = discountGroupDetailOne != null ? discountGroupDetailOne.getDateSegment() : null;
DateRange dateRangeTwo = discountGroupDetailTwo != null ? discountGroupDetailTwo.getDateSegment() : null;
if (compVal == 0 && dateRangeOne != null && dateRangeTwo != null) {
Date date = new Date();
compVal = DATE_FORMATER.format(date.parse(dateRangeOne.getStartDate())).compareTo(
DATE_FORMATER.format(date.parse(dateRangeTwo.getStartDate())));
}
DiscountCode discountTypeOne = (discountGroupDetailOne != null && discountGroupDetailOne
.getVolumeDiscountGroupDetail() != null) ? DiscountCode.getDiscount(discountGroupDetailOne
.getVolumeDiscountGroupDetail().getType()) : null;
DiscountCode discountTypeTwo = (discountGroupDetailTwo != null && discountGroupDetailTwo
.getVolumeDiscountGroupDetail() != null) ? DiscountCode.getDiscount(discountGroupDetailTwo
.getVolumeDiscountGroupDetail().getType()) : null;
boolean isFXG = ("FXG".equals(discountGroupDetailOne.getServiceGeography()) || (discountGroupDetailOne
.getServiceId() != null && "FXG".equals(discountGroupDetailOne.getServiceId().getOperatingCompany())))
&& ("FXG".equals(discountGroupDetailTwo.getServiceGeography()) || (discountGroupDetailTwo
.getServiceId() != null && "FXG".equals(discountGroupDetailTwo.getServiceId()
.getOperatingCompany())));
if (compVal == 0 && discountTypeOne != null && discountTypeTwo != null && isFXG) {
compVal = ((int) (Integer.parseInt(discountTypeOne.getDiscountID()) - Integer.parseInt(discountTypeTwo
.getDiscountID())));
}
return compVal;
}
});
}

#immibis spotted the answer before I could. To generalize the principle: Say you're writing a comparator that compares multiple pairs of keys. You want to compare the first pair, and then if that doesn't give you a < or > answer, compare the second pair, and if you still don't have < or >, compare the third pair, and so on.
The rule here is that after comparing the first pair, you may not go on to the second pair unless the keys in the first pair are equal. In your case, they're equal if both have a "code" and the codes are equal, or if they're both null. Your mistake is that you go on to the next pair if only one code is null. They're not equal in that case. I guess your thinking is that you can't compare the codes if one item doesn't have a code. But you must. You need to decree that an item with no code is less than an item with a code (or greater than; it depends on how you want to see them in your sorted array); and you have to check for that, and return (for instance) a negative number if the left item has a null code, and a positive number if the right item has a null code.
Then you need to do the same with the date range; if one argument has a date range and the other doesn't, they are not equal and you must return a negative or positive number, instead of proceeding to the third key.

This is almost always caused by your comparator either not being transitive or not being asymmetric. For transitivity, given three DiscountGroupDetail objects a, b, and c, if a precedes b and b precedes c, then the general contract requires that a precede c in all cases. Likewise, if a equals b and b equals c, then it is required that a equals c. For asymmetry, swapping the comparison order for two objects a and b that do not return 0 must return a number of the opposite sign.
Given your complex logic, it's difficult for me to determine exactly where things are going wrong, but the above is what you need to check.

Related

Check which combinations of parameters are null in Java

I am new to Java. I am facing an issue now in which I couldn't find the easiest and cleanest way of solving it.
Suppose I have 3 parameters(string) passed to a function(could be a Hashmap too).I want to check if individual variable or combination of variables is not Null and act accordingly.
For example one way to do this is using if-else this way
if(a!=null && b == null && c == null) {
//doSomething
}
else if(a==null && b!= null && c == null ) {
//doSomething
}
else if(a==null && b0= null && c != null) {
//doSomething
}
......
//Similarly combination of two variables
if(a!=null && b != null && c == null) {
//doSomething
}
else if(a!=null && b== null && c != null) {
//doSomething
}
else if(a==null && b!= null && c != null) {
//doSomething
}
......
//and so on
//Similarly combination of three variables
if(a!=null && b != null && c != null) {
//doSomething
}
....
How to achieve this kind of situation. I found similar question, but didn't make the code clean. Any help will be appreciated
Write these utility functions and you can compare n terms easily.
public static boolean areAllNull(Object... objects) {
return Stream.of(objects).allMatch(Objects::isNull);
}
public static boolean areAllNotNull(Object... objects) {
return Stream.of(objects).allMatch(Objects::nonNull);
}
you can use these functions for n comparisons.
if(areAllNotNull(a) && areAllNull(b,c)) {
//doSomething
}
else if(areAllNotNull(b) && areAllNull(a,c)) {
//doSomething
}
else if(areAllNotNull(c) && areAllNull(b,a)) {
//doSomething
}
This is my solution. Note, that you have multiple if...else in one single method. And then you add doSomething. This is going to be terrible to ready and later to realize.
What about to move one single condition into separate method and name it with relative name. Then, lets encapsulate it into Consumer and all of it into a predefined list. Later, if your doSomething will be huge, then you can move from single method to single class, not modifying client code.
This is class, to collect required variable for conditions:
final class Data {
private final String a;
private final String b;
private final String c;
}
Then define one Consumer per on if statement:
Consumer<Data> wonderfulConsumer = data -> {
if (a != null && b == null && c == null) {
// do something for wonderful consumer
}
};
Consumer<Data> badLuckConsumer = data -> {
if (a == null && b != null && c == null) {
// do something for bad luck consumer
}
};
Note, all these consumers could be modified separately (even be in the different classes).
Then in the client code, define list of all known consumers: List<Consumer<Data>> consumers = Arrays.asList(wonderfulConsumer, badLuckConsumer).
And finally your method will be like this and you do not need to change it when you decide to modify or add consumers.
Data data = new Data(a, b, c);
consumers.forEach(consumer -> consumer.accept(data));
If I had to do this , i will do it in the same way that you have done.
but if you dont like that and if you think it is not readable you can do it in this way, i expect lot of negative comments to this answer because this is a bad solution.
public static void yourMethod(Object a,Object b,Object c)
{
int evaluation = howManyNotNull(a,b,c);
if(evaluation == 0) // all are nulls
{
// your code
}
else if(evaluation == 1) // only one is not null
{
if(a!=null)
{
}
else if(b!=null)
{
}
else
{
// c not null
}
}
else if(evaluation == 2) // two variables are not null but other one is null
{
if(a==null)
{
}
else if(b == null)
{
}
else
{
// c is null, a and b not null
}
}
else
{
// all abc not null
}
}
private static int howManyNotNull(Object a, Object b, Object c)
{
return (a==null?0:1) + (b==null?0:1) + (c==null?0:1);
}
There is extended version of this , assign 3 prime values for a, b , c (example :a=2,b=3,c=5), then use a supportive method like this
private static int convertAvailabilityToInt(Object a, Object b, Object c)
{
return (a==null?1:2) * (b==null?1:3) * (c==null?1:5);
}
if the answer is 1 ,then all are not null .
You can use for example a 3 digit string simulating 3 flags.
You first set it to "000".
Then you check each variable for null, if it is not you will replace the 0 with 1.
Then you could use switch cases to treat each case.
You are doing everything right but you have to remember that primitive data types cannot be null. For example string is not null, but empty string "", or int cannot be null, its by default sets to 0. In conclusion Objects like Map , ArrayList or Integer.. you can check for null, but primitive data types cannot be null, so you cannot check them for it. For deeper understanding just learn about primitive and advanced data types.
I hope I got your problem right :)

Android custom sorting using comparable

I have a little problem with this snippet of code:
#Override
public int compareTo(EventResponse o) {
int compare1 = startTime.compareTo(o.startTime);
if (compare1 == 0 && o.myProviderId != null && o.providerId != null) {
return o.providerId.compareTo(o.myProviderId) != 0 ? -1 : 0;
} else {
return compare1;
}
}
I have EventResponse class which i have created that implements Comparable interface, o.myProviderId is ID of currently signed in user (into application) and o.providerId is ID of user that is assigned to particular object (EventResponse).
What I need to achieve is if there are two objects with the same startTime to show objects assigned to me first and then objects assigned to some other provider/s.
This code snippet should (and I think it did in the past) do the trick but I get error message stating: "Comparison method violates its general contract".
Please help!
The violation of the contract often means that the comparator is not providing the correct or consistent value when comparing objects.
For example :
if (compare1 == 0 && o.myProviderId != null && o.providerId != null) {
return o.providerId.compareTo(o.myProviderId) != 0 ? -1 : 0;
}
So, what happens when o.providerId.compareTo(o.myProviderId) gives you 1 or -1. In both scenario you are going to return -1 isn't it ?
Instead, why not simply return what gets return from compareTo ?
if (compare1 == 0 && o.myProviderId != null && o.providerId != null) {
return o.providerId.compareTo(o.myProviderId);
}

CompareTo is transitive

I have a POJO looking like this:
public class Pojo implements Comparable<Pojo> {
private String type;
private String journalId;
private Date bookingDate;
private Long account;
private String description;
private BigDecimal debit;
private BigDecimal credit;
....
}
and I want to sort a list of these POJOs. Currently my compareTo method looks like this:
#Override
public int compareTo(EfdisJournal other) {
int i = this.type.compareTo(other.type);
if (i != 0)
return i;
if (this.bookingDate != null && other.bookingDate != null)
i = this.bookingDate.compareTo(other.bookingDate);
if (i != 0)
return i;
if (this.journalId != null && other.journalId != null)
i = this.journalId.compareTo(other.journalId);
if (i != 0)
return i;
return this.account.compareTo(other.account);
}
If I run a sort with this compareTo method, I get this java.lang.IllegalArgumentException: Comparison method violates its general contract error. I did google a bit and I think it happens because some of the fields are null on comparison. Yet I have no idea how to solve this or if I am right why that error appears.
The comparison should work like this: 1st compare by type, then compare by bookingDate, as 3rd compare by journalId and at last compare by account. All comparisons should be ascending.
type is never null
bookingDate may be null
journalId may be null
account is never null
EDIT:
Sadly I was not able to implement the method, so that the order is as needed. Yet, i solved the problem I had, because the stored procedure yielded 2 resultsets, of which the second was order as needed, so the only thing I had to do was to use the 2nd resultset instead of the first.
You need to deal with the case where one instance has a null bookingDate, and the other has a non-null bookingDate.
You should decide whether things with null bookingDate should be sorted before or after things with a non-null bookingDate, and write your compareTo appropriately. (And then journalId too.) Then you can get an order that sorts consistently.
For instance:
#Override
public int compareTo(EfdisJournal other) {
int i = this.type.compareTo(other.type);
if (i != 0) {
return i;
}
if ((this.bookingDate==null) ^ (other.bookingDate==null)) {
return (this.bookingDate==null ? -1 : 1);
}
if (this.bookingDate != null && other.bookingDate != null) {
i = this.bookingDate.compareTo(other.bookingDate);
}
if (i != 0) {
return i;
}
if ((this.journalId==null) ^ (other.journalId==null)) {
return (this.journalId==null ? -1 : 1);
}
if (this.journalId != null && other.journalId != null) {
i = this.journalId.compareTo(other.journalId);
}
if (i != 0) {
return i;
}
return this.account.compareTo(other.account);
}
You're ignoring situations where bookingDate and/or journalId is null with one and non-null with the other.

What is a shorter way of comparing Boolean objects in Java 6?

I’m using Java 6. I have two Boolean objects, a and b. How do I compare the two in terms of their value? I have come up with this, but it seems really long and messy …
(a == null && b == null) || (a != null && b != null && a.booleanValue().equals(b.booleanValue()))
I like Apache’s StringUtils.equals(a, b) method for comparing strings, but there doesn’t seem to be an equivalent method for BooleanUtils.
You can use the following for any two Objects including two Boolean
a == null ? b == null : a.equals(b);
This works for Java 1.0+
you can use the compareTo in the class boolean.. it is avaliable since 1.5
take a look to this snippet
private static int compare(Boolean a, Boolean b) {
// if (a == null || b == null) {
// return -1;
// }else {
// return a.compareTo(b);
// }
return (a == null || b == null) ? -1 : a.compareTo(b);
}
the return line is doing the same as the commented one...

Sort ArrayList by Date in Java

I'm trying to sort a list of Product objects by their useby Dates, but some of the objects may have their Date useby = null
What I want to do is sort the list by "useby" Dates, and put all the Objects that has Date useby = null on the bottom or on the top of the list.
public static void sort() {
Collections.sort(list, new Comparator<Product>() {
public int compare(Product o1, Product o2) {
if (o1.getUseDate() == null || o2.getUseDate() == null) {
return 0;
} else {
return o1.getUseDate().compareTo(o2.getUseDate());
}
}
});
}
This is my code and I don't know how to fix this.
if (o1.getUseDate() == null || o2.getUseDate() == null). Here you are checking if either of the two Product's has a null date. If even one of them does the return result of 0 means that the Comparator will see them as equal. Even if just one of them has a null date. Here you probably want to do some more in-depth checking. The following code will result in Product's with null Dates being put at the start.
if(o1.getUseDate() == null && o2.getUseDate() == null)
return 0; //They are both null, both equal
if(o1.getUseDate() == null && o2.getUseDate() != null)
return -1; // The first is null and the second is not, return the first as lower than the second
if(o1.getUseDate() != null && o2.getUseDate() == null)
return 1; //The first is not null and the second is, return the first as higher than the second
else
return o1.getUseDate().compare(o2.getUseDate()); //Return the actual comparison
This is obviously a little verbose, but it should give you an idea as to what you should actually be looking for.
return 0; means that compared elements are equal (in other words you shouldn't swap them).
In case of nulls consider
returning 0 if both values are null (first value is same as second value - don't swap)
returning -1 if first value is null but second is not null (first element is lesser than second - don't swap)
returning 1 if second value is null and first is not null (first element is greater than second - swap them)
From compare javadoc:
Compares its two arguments for order. Returns a negative integer,
zero, or a positive integer as the first argument is less than, equal
to, or greater than the second.
But from this line of your code:
if (o1.getUseDate() == null || o2.getUseDate() == null) {
return 0;
we can see that you're practically telling the comparator to consider useDates as being equal when at least one of them is null, and this is not what you want.
Since null means "nothing", it's quite weird to compare the concept of nothing with a date, but if you want to allow null values, then you have to handle this situation properly, resulting into a more vorbose code:
if(o1.getUseDate() == null){
return o2.getUseDate() == null ? 0 : -1; //or +1 if you want null values at the bottom of your list
}
if(o2.getUseDate() == null){
return o1.getUseDate() == null ? 0 : -1; // or +1 ...
}
return o1.getUseDate().compare(o2.getUseDate()); // 2 non null dates, do a real comparison

Categories