Why does my compare methd throw IllegalArgumentException sometimes? - java

I am having this problem for some time, have searched lots of StackOverflow questions but couldn't solve my problem.
I also asked a similar question before and got the suggestion to use,
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
It didn't solve my problem. I never got this exception on any of my test devices, but some of my users have been reporting it regularly. I am really clueless how to solve it.
The Exception
This is the exception that I am getting,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:743)
at java.util.TimSort.mergeAt(TimSort.java:479)
at java.util.TimSort.mergeCollapse(TimSort.java:404)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2023)
at java.util.Collections.sort(Collections.java:1883)
or sometimes this,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:864)
at java.util.TimSort.mergeAt(TimSort.java:481)
at java.util.TimSort.mergeCollapse(TimSort.java:406)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2010)
at java.util.Collections.sort(Collections.java:1883)
What I Have Done
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 != null && d2 != null) {
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 != null && l2 != null) {
result = l1.compareTo(l2);
}
}
}
return result;
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 != null && f2 != null) {
result = Long.valueOf(f1.length()).compareTo(Long.valueOf(f2.length()));
}
}
return result;
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
This is how I am sorting,
Collections.sort(dirs, FileItemComparator.getComparator(FileItemComparator.NAME_SORT));
The Problem
I am sure there is something wrong in the compare method with transitive dependencies. I have tried a lot and can't seem to fix it. Actually, I never got this problem in any of my test devices, but my users are reporting it constantly.
I hope anyone here will be able to catch the problem and help me solve it once and for all.
Updated Code (Thanks to #Eran)
I thought it would be best to help others by posting the complete updated code. It will help a lot of people facing the same problem.
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 == null) {
if (d2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (d2 == null) {
return -1;
}
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 == null) {
if (f2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (f2 == null) {
return -1;
}
Long l1 = Long.valueOf(f1.length());
Long l2 = Long.valueOf(f2.length());
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}

Let's look at your first compare method :
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
Suppose you are comparing two FileItems (let's call them o1 and o2), one with a file name and the other without (i.e. null file name). Your method will return 0.
Now if you compare o2 with another FileItem (o3) for which the file name is not null, you return 0 again.
But if you compare o1 to o3, since both of them have non null file name, the comparison returns -1 or 1 (assuming the file names are different).
Therefore your comparison is inconsistent since it's not transitive.
If one element lacks a property required for the comparison and the other doesn't, you shouldn't return 0. You should decide whether to return 1 or -1 (depending whether, for example, the FileItems with null names should be ordered before or after the FileItems with non null names).
For example :
public int compare(FileItem o1, FileItem o2)
{
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}

This is a common mistake with comparators - you are not handling null consistently. The usual pattern would look like this:
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null < not null
if (o1 == null || o2 == null) {
return -1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
Added
Note that this implementation also a common mistake as I am allowing compare(null,not_null) to equal compare(not_null,null) which also violates the contract - please use #Eran's solution or something like this.
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null != not null
if (o1 == null || o2 == null) {
// Swap these around if you want 'null' at the other end.
return o1 == null ? -1: 1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
// Swap these around if you want 'null' at the other end.
return n1 == null ? -1: 1;
}
return n1.compareTo(n2);
}

Related

How to reduce cyclomatic complexity of this java method

I have the next java implementation, but it has a SonarQube issue and i don't know how to reduce the method.
if (user.getUserId() != null && user.getEmail() != null
&& user.getName() != null) {
return 1;
} else if (user.getUserId() != null && user.getEmail() != null) {
return 2;
} else if (user.getUserId() != null && user.getName() != null) {
return 3;
} else if (user.getUserId() != null) {
return 4;
} else if (user.getEmail() != null && user.getName() != null) {
return 5;
} else if (user.getName() != null) {
return 6;
} else if (user.getEmail() != null) {
return 7;
} else {
return 0;
}
when the user is a Pojo of Users
class Users {
String userId;
String email;
String name;
//getters and setters...
}
This is the real code and issue in SonarQube:
what about Pattern of Chain of Responsibility.
public abstract class Handler {
protected Handler next;
public abstract Integer process(User user);
}
Handler checkIdEmailName = new Handler() {
#Override
public Integer process(User user) {
boolean check = false;
if (check) {
return 1;
}else {
return next.process(user);
}
}
};
Handler checkIdEmail = new Handler() {
#Override
public Integer process(User user) {
boolean check = false;
if (check) {
return 1;
}else {
return next.process(user);
}
}
};
checkIdEmailName.next = checkIdEmail;
Integer result = checkIdEmailName.process(new User());
Break it to another methods would reduce the complexity of the method itself.
Something like
if (user.getUserId() != null && user.getEmail() != null
&& user.getName() != null) {
return 1;
} else if (user.getUserId() != null && user.getEmail() != null) {
return 2;
} else if (user.getUserId() != null && user.getName() != null) {
return 3;
} else if (user.getUserId() != null) {
return 4;
} else {
return yourNewMethod();
}
private int yourNewMethod(User user) {
if (user.getEmail() != null && user.getName() != null) {
return 5;
} else if (user.getName() != null) {
return 6;
} else if (user.getEmail() != null) {
return 7;
} else {
return 0;
}
}
so first of all we should emphasis what cognitive complexity is all about, and how it would make sense to reduce it. Mainly by extracting things which belongs together.
When taking a look at your method with the first 4 conditions, they share a similarity, and it is the check for userId. This means you can easily combine the checks below into one branch. Which will be easier to grasp, and also offers a nice way for extraction
if (user.getUserId() != null) {
if (user.getEmail() != null
&& user.getName() != null) {
return 1;
} else if (user.getEmail() != null) {
return 2;
} else if (user.getName() != null) {
return 3;
} else {
return 4;
}
} else if (user.getEmail() != null && user.getName() != null) {
return 5;
} else if (user.getName() != null) {
return 6;
} else if (user.getEmail() != null) {
return 7;
} else {
return 0;
}
Based on this you can further extract easily like
if (user.getUserId() != null) {
return fancyNameForAdditionalUserIdChecks(user);
} else if (user.getEmail() != null && user.getName() != null) {
return 5;
} else if (user.getName() != null) {
return 6;
} else if (user.getEmail() != null) {
return 7;
} else {
return 0;
}
private int fancyNameForAdditionalUserIdChecks(Users user) {
if (user.getEmail() != null
&& user.getName() != null) {
return 1;
} else if (user.getEmail() != null) {
return 2;
} else if (user.getName() != null) {
return 3;
} else {
return 4;
}
}
Now we only have four main branches in the first if - a developer who does not care about cases where the userid is null, can simple skip it, and he will not need to process all those branches. Where as if needs to, he has only one condition to first check, and the rest is easier to grasp.
To handle such a case, you always should think about, how somebody is reading code. What benefit he gets, and were you can reduce the cognitive load so it will gets easier for the following developer to understand. Machines will never have problem reading our code, but writing code which is easy to read by humans, that is the hard part.
Sidenote:
i would rather rework your Dao methods, and in this case put that logic into the dao, by providing a map of paramaters to the dao, and let the dao handle such a logic. like dao.find(Map<String, Object> params) and you could handle this based on the fields available or not available.
Here is the reduce complexity code,
boolean userIdAvailable = user.getUserId() != null;
boolean emailAvailable = user.getEmail() != null;
boolean nameAvailable = user.getName();
short response = checkAll(userIdAvailable, emailAvailable, nameAvailable);
if (response == 0) {
response = checkEmailAndName(emailAvailable, nameAvailable);
if (response == 0) {
checkUserIdAndName(userIdAvailable, nameAvailable);
}
}
short checkAll(boolean userIdAvailable, boolean emailAvailable, boolean nameAvailable) {
return userIdAvailable && emailAvailable && nameAvailable ? 1 : 0;
}
short checkEach(boolean userIdAvailable, boolean emailAvailable, boolean nameAvailable) {
short response = 0;
if(userIdAvailable) {
response = 4;
} else if(emailAvailable) {
response = 7;
} else if(nameAvailable) {
response = 6;
}
return response;
}
short checkUserIdAndEmail(boolean emailAvailable, boolean userIdAvailable) {
return userIdAvailable && emailAvailable ? 2 : 0
}
short checkEmailAndName(boolean emailAvailable, boolean nameAvailable) {
return emailAvailable && nameAvailable ? 5 : 0
}
short checkUserIdAndName(boolean userIdAvailable, boolean nameAvailable) {
return userIdAvailable && nameAvailable ? 3 : 0
}

Java Treemap comparator ignoring get when key is different instances

The issue I'm having is that the get method throws NPE when the key is a different instance than the one in the TreeMap.
public class ConjuntDocuments {
private TreeMap<Capcalera, Document> almacen;
private ArrayList<Pair_plantilla> plantilla;
ConjuntDocuments() {
almacen = new TreeMap<Capcalera, Document>(new CustomComparator());
plantilla = new ArrayList<Pair_plantilla>();
}
private static class CustomComparator implements Comparator<Capcalera> {
#Override
public int compare(Capcalera c1, Capcalera c2) {
int ax = c1.get_tit().get_nom().compareFrase(c2.get_tit().get_nom());
if (ax < 0) return -1;
else if (ax > 0) return 1;
//titols iguals
else {
ax = c1.get_au().get_nom().compareFrase(c2.get_au().get_nom());
if (ax < 0) return -1;
else if (ax > 0) return 1;
}
//titols i autors iguals
return 0;
}
}
compareFrase compares ArrayLists(Paraula) -> Frase, Paraula is like a string, get_chars returns a String.
public int compareFrase(Frase f) {
for(int i=0; i<min(this.get_size(), f.get_size()); ++i){
int aux = this.get_paraula(i).get_chars().compareTo(f.get_paraula(i).get_chars());
if(aux < 0) return -1;
else if(aux > 0) return 1;
}
if(this.get_size() < f.get_size()) return -1;
else if(this.get_size() > f.get_size()) return 1;
return 0;
}
Titol and autor are Frases -> ArrayList(Paraula)
public class Capcalera {
private Titol tit;
private Autor au;
So after trying to figure this out, I've realised that the get method only works if the key referenced is the same instance than the one mapped, right after putting an entry (almacen.put(capcalera,document) , if I try to call almacen.get(Capcalera) it will return the value correctly, but if I create a new Capcalera, it will throw NPE. I'm assuming there is an issue with the comparator but since the entries are sorted correctly I can't figure out what is wrong.
EDIT:
I've implemented .equals and .hashcode from Capcalera, but I might be doing something wrong because .get from the Treemap still throws NPE.
#Override
public int hashCode() {
int hashTitol = tit != null ? tit.hashCode() : 0;
int hashAutor = au != null ? au.hashCode() : 0;
return (hashTitol + hashAutor) * hashAutor + hashTitol;
}
#Override
public boolean equals(Object other) {
if (other instanceof Capcalera) {
Capcalera otherCapcalera = (Capcalera) other;
return
(( this.get_tit().get_nom().equalsFrase(otherCapcalera.get_tit().get_nom()) ||
( this.get_tit() != null && otherCapcalera.get_tit() != null &&
this.get_tit().get_nom().equalsFrase(otherCapcalera.get_tit().get_nom()) )) &&
( this.get_au().get_nom().equalsFrase(otherCapcalera.get_au().get_nom()) ||
( this.get_au() != null && otherCapcalera.get_au() != null &&
this.get_au().get_nom().equalsFrase(otherCapcalera.get_au().get_nom()))) );
}
return false;
}
equalsFrase returns true if Titol/Autor are equals
public boolean equalsFrase(Frase f) {
for(int i=0; i<min(this.get_size(), f.get_size()); ++i){
int aux = this.get_paraula(i).get_chars().compareTo(f.get_paraula(i).get_chars());
if(aux < 0) return false;
else if(aux > 0) return false;
}
if(this.get_size() < f.get_size()) return false;
else if(this.get_size() > f.get_size()) return false;
return true;
}

Java Why two this same List when I check if are equals I see false [duplicate]

This question already has answers here:
Why do I need to override the equals and hashCode methods in Java?
(31 answers)
Closed 6 years ago.
Hi I have Two lists of objects :
public class TimeTableForBus {
String bsName;
int bsType;
Time ttTime;
int lon;
int lat;
int ttID;
int bus_stop_status;
}
And I generated two list just like this :
private static ArrayList getList( QueryRunner qRunner, Connection conn){
try {
beans = (List) qRunner.query(conn, "call mpklocal.LCD_GetDispInfoAllTimeTable()",
new BeanListHandler(TimeTableForBus.class));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < beans.size(); i++) {
TimeTableForBus bean = (TimeTableForBus) beans.get(i);
// bean.print();
}
ArrayList<TimeTableForBus> bus = new ArrayList<>();
for (int i = 0; i < beans.size(); i++) {
bus.add((TimeTableForBus) beans.get(i));
}
return bus;
}
When I check if are equals I see false when I do this I see false . The list have this same objects:
public static boolean equalLists(List<TimeTableForBus> one, List<TimeTableForBus> two){
if (one == null && two == null){
return true;
}
if((one == null && two != null)
|| one != null && two == null
|| one.size() != two.size()){
return false;
}
//to avoid messing the order of the lists we will use a copy
//as noted in comments by A. R. S.
// one = new ArrayList<String>(one);
// two = new ArrayList<String>(two);
//
// Collections.sort(one);
// Collections.sort(two);
return one.equals(two);
}
public static boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
if(a==null) {
if(b==null) {
//Here 2 null lists are equivelent. You may want to change this.
return true;
} else {
return false;
}
}
if(b==null) {
return false;
}
Map<Object, Integer> tempMap = new HashMap<>();
for(Object element : a) {
Integer currentCount = tempMap.get(element);
if(currentCount == null) {
tempMap.put(element, 1);
} else {
tempMap.put(element, currentCount+1);
}
}
for(Object element : b) {
Integer currentCount = tempMap.get(element);
if(currentCount == null) {
return false;
} else {
tempMap.put(element, currentCount-1);
}
}
for(Integer count : tempMap.values()) {
if(count != 0) {
return false;
}
}
return true;
}
And I don't know why I have this result
Try to override public boolean equals(Object o) and public int hashCode() like this:
public class TimeTableForBus {
String bsName;
int bsType;
Time ttTime;
int lon;
int lat;
int ttID;
int bus_stop_status;
#Override
public int hashCode() {
int result = 31;
result = 37 * result + generateHash(bsName);
result = 37 * result + generateHash(bsType);
result = 37 * result + generateHash(ttTime);
result = 37 * result + generateHash(lon);
result = 37 * result + generateHash(lat);
result = 37 * result + generateHash(ttID);
result = 37 * result + generateHash(bus_stop_status);
return result;
}
#Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof TimeTableForBus))
return false;
TimeTableForBus model = (TimeTableForBus)o;
return Objects.equals(bsName, model.bsName)
&& bsType == model.bsType
&& Objects.equals(ttTime, model.ttTime)
&& lon == model.lon
&& lat == model.lat
&& ttID == model.ttID
&& bus_stop_status == model.bus_stop_status;
}
private int generateHash(long value) {
return (int)(value ^ (value >>> 32));
}
private int generateHash(Object value) {
return value == null ? 0 : value.hashCode();
}
}

Comparator (sort) to compare array list of objects with 3 properties by priority

So lets say there is a node, and it has three properties: pdf:Title, dc:title & node name , I would like to compare and sort the array based of these properties with priority in same order. So if node1 has all three properties and node2 has only dc:title it should compare pdf:Title of node1 with dc:titleof node2.
This is the code I am using:
#Override
public int compare(Node o1, Node o2) {
try {
int compareValue;
boolean o1HasPdfTitle = o1.hasProperty("jcr:content/metadata/pdf:Title");
boolean o1HasDcTitle = o1.hasProperty("jcr:content/metadata/dc:title");
boolean o2HasPdfTitle = o2.hasProperty("jcr:content/metadata/pdf:Title");
boolean o2HasDcTitle = o2.hasProperty("jcr:content/metadata/dc:title");
if (o1HasPdfTitle && o2HasPdfTitle) {
compareValue = o1.getProperty("jcr:content/metadata/pdf:Title").getString().compareTo(o2.getProperty("jcr:content/metadata/pdf:Title").getString());
return compareValue;
} else if (o1HasPdfTitle && o2HasDcTitle) {
compareValue = o1.getProperty("jcr:content/metadata/pdf:Title").getString().compareTo(o2.getProperty("jcr:content/metadata/dc:title").getString());
return compareValue;
} else if (o1HasDcTitle && o2HasPdfTitle) {
compareValue = o1.getProperty("jcr:content/metadata/dc:title").getString().compareTo(o2.getProperty("jcr:content/metadata/pdf:Title").getString());
return compareValue;
} else if (o1HasDcTitle && o2HasDcTitle) {
compareValue = o1.getProperty("jcr:content/metadata/dc:title").getString().compareTo(o2.getProperty("jcr:content/metadata/dc:title").getString());
return compareValue;
} else if (!o1HasPdfTitle && !o1HasDcTitle && o2HasPdfTitle){
compareValue = o1.getName().compareTo(o2.getProperty("jcr:content/metadata/pdf:Title").getString());
return compareValue;
} else if (!o1HasPdfTitle && !o1HasDcTitle && o2HasDcTitle){
compareValue = o1.getName().compareTo(o2.getProperty("jcr:content/metadata/dc:title").getString());
return compareValue;
} else if (o1HasPdfTitle && !o2HasPdfTitle && !o2HasDcTitle){
compareValue = o1.getProperty("jcr:content/metadata/pdf:Title").getString().compareTo(o2.getName());
return compareValue;
} else if (o1HasDcTitle && !o2HasPdfTitle && !o2HasDcTitle){
compareValue = o1.getProperty("jcr:content/metadata/dc:title").getString().compareTo(o2.getName());
return compareValue;
} else {
compareValue = o1.getName().compareTo(o2.getName());
return compareValue;
}
} catch (Exception e) {
LOGGER.debug("CustomComparatorTitle debug message" + e);
return 0;
}
Problem: It does not sort correctly, they are not alphabetically ordered. Am I missing something?
Also is there a better way to wright the code without using so many conditions? I was trying to avoid the IllegalArgumentException: Comparison method violates its general contract!
i'm not sure I understand your code, but it's seems its better in this way:
final String pPdf="jcr:content/metadata/pdf:Title";
final String pDC="jcr:content/metadata/dc:title";
String[] values=new String[2];
for (int i = 0; i < values.length; i++)
{
Node curNode=i==0?o1:o2;
if(curNode.hasProperty(pPdf))
values[i]=curNode.getProperty(pPdf).getString();
else if(curNode.hasProperty(pDC))
values[i]=curNode.getProperty(pDC).getString();
else
values[i]=curNode.getName();
}
return values[0].compareTo(values[1]);
it's not better?

Sort a Map<String, Object> first on Object.property1 and then for each Object.property1, sort by Object.property2

I have used the below method to Sort a Map first on Object.property1 and then for each Object.property1, sort by Object.property2.
for example,
property1 = TaxIdNumber and
property2 = ProviderName
I was just wondering this can be done in a more shorter and precise manner. Any help or suggestion would be appreciated.
private List<TestObject> sortByValue(final Map m) {
List<TestObject> values = new ArrayList<TestObject>();
values.addAll(m.values());
// First sort the list by Tax ID.
Collections.sort(values, new Comparator<TestObject>() {
public int compare(TestObject r1, TestObject r2) {
Long taxId1 = (r1 == null ? null : r1.getTaxIdNumber());
Long taxId2 = (r2 == null ? null : r2.getTaxIdNumber());
if (taxId1 == null || taxId2 == null) {
return 0;
}
return taxId1.compareTo(taxId2);
}
});
// Then sort the list by Provider name.
Collections.sort(values, new Comparator<TestObject>() {
public int compare(TestObject r1, TestObject r2) {
String name1 = (r1 == null ? null : r1.getProviderName());
String name2 = (r2 == null ? null : r2.getProviderName());
if (name1 == null || name2 == null) {
return 0;
}
if (r1.getTaxIdNumber() == r2.getTaxIdNumber()) {
return name1.compareTo(name2);
} else {
return 0;
}
}
});
return values;
}
You only need one comparator. first compare the taxids. If they are unequal return -1 or 1 as appropriate. If they are equals, then compare the provider name.
something like:
Collections.sort(values, new Comparator<TestObject>() {
public int compare(TestObject r1, TestObject r2) {
Long taxId1 = (r1 == null ? null : r1.getTaxIdNumber());
Long taxId2 = (r2 == null ? null : r2.getTaxIdNumber());
if (taxId1 == null || taxId2 == null) {
return 0;
}
int cmp = taxId1.compareTo(taxId2);
if (cmp != 0)
return cmp;
String name1 = (r1 == null ? null : r1.getProviderName());
String name2 = (r2 == null ? null : r2.getProviderName());
if (name1 == null || name2 == null) {
return 0;
}
return name1.compareTo(name2);
}
});
Your null-handling violates the contract of compare, as you deem null equal to any other value, while the JavaDoc writes:
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.
and in particular:
Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.
which your code fails to accomplish for x = null, y = "a", z = "b".
Therefore, if any objects or properties in the list are null, the list may not be sorted correctly.
That being said, I wonder if the list may really contain null values or properties? If not, I'd remove all null checks and end up with
Collections.sort(list, new Comparator<TestObject>() {
#Override public int compare(TestObject o1, TestObject o2) {
int c = o1.getTaxIdNumber().compareTo(o2.getTaxIdNumber);
if (c != 0) {
return c;
}
return o1.getProviderName().compareTo(o2.getProviderName());
}
}
If the list may contain null objects or properties, you must define whether the null values come first or last, and extend the comparator accordingly:
Collections.sort(list, new Comparator<TestObject>() {
#Override public int compare(TestObject o1, TestObject o2) {
// insert null-checks for o1, o2 here
int c = cmp(getTaxIdNumber(), o2.getTaxIdNumber());
if (c != 0) {
return c;
}
return cmp(o1.getProviderName(), o2.getProviderName());
}
private <T extends Comparable<? super T>> cmp(T o1, T o2) {
if (o1 == o2) {
return 0;
else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
} else {
return o1.compareTo(o2);
}
}
}
Now this is quite a bit of repetitive and tricky code, which is why the folks over at Apache wrote the CompareToBuilder. With that API, you can simply write:
#Override int compare(TestObject r1, TestObject r2) {
// insert null checks for r1 and r2 here - if you really need them
return new CompareToBuilder()
.append(r1.getTaxIdNumber(), r2.getTaxIdNumber())
.append(r1.getProviderName(), r2.getProviderName())
.toComparison();
}
}

Categories