I'm trying to sort a list by offerValues using compareTo method.
It works fine it offerValue is a single digit like 8.00, but if offerValue is 20.00 or 15.00, it's considering them as 2(for 20.00) and 1(for 15.00). Don't understand it.
Comparator<OfferVO> comparatorAsc = (o1, o2) -> {
if (o1.getOfferValue() == null && o2.getOfferValue() == null) {
return 0;
} else if (o1.getOfferValue() != null) {
return o1.getOfferValue().compareTo(o2.getOfferValue());
} else {
return -1;
}
};
Collections.sort(offersList, comparatorAsc);
OfferVO.class:
public class OfferVO extends OfferBaseVO implements Serializable {
private static final long serialVersionUID = 1L;
private String offerValue;
public String getOfferValue() {
return offerValue;
}
public void setOfferValue(String offerValue) {
this.offerValue = offerValue;
}
}
Output sample:
"offerValue": "5.00",
"offerValue": "3.00",
"offerValue": "3.00",
"offerValue": "20.00",
"offerValue": "15.00",
"offerValue": "15.00",
Strings are compared lexicographical (colloquially alphabetically), not numerically. That means "10" will come before "2" and so on. If you want to compare the numerical values you'll need to parse the strings into numbers, for instance with Double.valueOf().
1) To compare String as numeric value, convert them into numbers in your comparator as String.compare() relies on the lexicographical order.
2) Note that your comparator is not symmetric.
The Comparator.compare() specification states that :
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y,
x)) for all x and y.
Here :
else if (o1.getOfferValue() != null) {
return o1.getOfferValue().compareTo(o2.getOfferValue());
}
You rely on the non nullity of only o1.getOfferValue() to compare the two objects :
To write a more precise numerical comparison about the rounding you should use an epsilon as you compare the floating values such as
float epsilon = 0.0001F;
if (o1.getOfferValue() == null && o2.getOfferValue() == null){
return 0;
} else if (o1.getOfferValue() != null && o2.getOfferValue() != null) {
return Math.abs(Float.valueOf(o1.getOfferValue()) - Float.valueOf(o2.getOfferValue())) < epsilon)
}
// TODO
// as last you have to decide here how to sort if only one of the value is not null
If the rounding is not an issue in the comparison : Float.compare() is enough :
if (o1.getOfferValue() == null && o2.getOfferValue() == null){
return 0;
} else if (o1.getOfferValue() != null && o2.getOfferValue() != null) {
return Float.compare(Float.valueOf(o1.getOfferValue()), Float.valueOf(o2.getOfferValue());
}
Used below code:
Comparator<OfferVO> comparatorDesc = (o1, o2) -> {
if (o1.getOfferValue() == null && o2.getOfferValue() == null) {
return 0;
} else if (o2.getOfferValue() != null) {
return
Double.valueOf(o2.getOfferValue()).compareTo(Double.valueOf(o1.getOfferValue()));
} else {
return -1;
}
};
Related
I have a <display:table> with potential null values in the columns, and I'd like to configure the sorting so that when a given <display:column> is sorted alphabetically ("A" strings at top), the null values are at the bottom of the table (after "Z" strings).
To do this, I've written a Comparator implementation that should put null values at the end of the list:
public class DefaultComparatorNullsLast implements Comparator<Object>{
private final Collator collator;
public DefaultComparatorNullsLast(){
this(Collator.getInstance());
} // DefaultComparatorNullsLast
public DefaultComparatorNullsLast(Collator collatorToUse){
this.collator = collatorToUse;
this.collator.setStrength(Collator.PRIMARY);
} // DefaultComparatorNullsLast
public int compare(final Object obj0, final Object obj1){
//similar to NullComparator.compare()
if(obj0 == obj1) return 0;
else if(obj0 == null) return 1;
else if(obj1 == null) return -1;
//similar to DefaultComparator.compare()
if(obj0 instanceof String && obj1 instanceof String) return this.collator.compare(obj0, obj1);
else if(obj0 instanceof Comparable && obj1 instanceof Comparable) return ((Comparable<Object>) obj0).compareTo(obj1);
else return this.collator.compare(obj0.toString(), obj1.toString());
} // compare
} // DefaultComparatorNullsLast
Strangely, though, the null values are still displaying at the top of the table. When I debug, I see that my DefaultComparatorNullsLast.compare() method is never called on any null values. Digging into the displaytag code, I can see that TableModel.sortRowList() actually places my Comparator implementation into a RowSorter, which is then passed to Collections.sort():
private void sortRowList(List list)
{
if (isSorted())
{
HeaderCell sortedHeaderCell = getSortedColumnHeader();
if (sortedHeaderCell != null)
{
// If it is an explicit value, then sort by that, otherwise sort by the property...
if (sortedHeaderCell.getBeanPropertyName() != null
|| (this.sortedColumn != -1 && this.sortedColumn < this.headerCellList.size()))
{
String sorted = (sortedHeaderCell.getSortProperty() != null)
? sortedHeaderCell.getSortProperty()
: sortedHeaderCell.getBeanPropertyName();
Collections.sort(list, new RowSorter(
this.sortedColumn,
sorted,
getTableDecorator(),
this.sortOrderAscending,
sortedHeaderCell.getComparator()));
}
}
}
}
And when I look into RowSorter, it seems the checkNullsAndCompare() method actually handles null values opposite how I want them to be handled and only calls my DefaultComparatorNullsLast.compare() method if both values are non-null:
private int checkNullsAndCompare(Object object1, Object object2)
{
int returnValue;
if (object1 == null && object2 != null)
{
returnValue = -1;
}
else if (object1 != null && object2 == null)
{
returnValue = 1;
}
else if (object1 == null && object2 == null)
{
// both null
returnValue = 0;
}
else
{
returnValue = comparator.compare(object1, object2);
}
int ascendingInt = this.ascending ? 1 : -1;
return ascendingInt * returnValue;
}
Essentially, I'm wondering if there's any way around this. Of course, I know I can sort the list in my action code before the JSP renders and use sort="external", but I also want this special sorting of null values to take place when the user clicks a column header to sort by that column's property. Is there any way to get the displaytag library to call my custom Comparator implementation instead of RowSorter.checkNullsAndCompare()?
I am iterating over two collections and check if both collections contain
the same elements. I can't use Java 8.
edit 1 year after:
I created the method in the question to check if two Collections contain the same elements, without thinking about the fact that I am passing two Collection implementations into the method.
But Collection does not determine how elements are sorted. And I am iterating over the collections. Thus, some implementation of Collection could save elements in random order, while containing the same elements.
Both collections contain elements that are comparable and the content
is defined as equal, if all elements return a x.compareTo(y) with 0.
Two values are defined as different, if one of them is null, but not the other.
I want to find an elegant way to compare on nullity and prevent
a null check on the final compareTo().
My current implementation:
public static <T extends Comparable<T>> boolean isSame(#Nullable Collection<T> a, #Nullable Collection<T> b) {
if (a == null || b == null) {
return (a == null && b == null);
}
if (a.size() != b.size()) {
return false;
}
Iterator<T> aIt = a.iterator();
Iterator<T> bIt = b.iterator();
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
//both null, don't compare, continue looping...
} else if (aValue.compareTo(bValue) != 0) {
return false;
}
}
return true;
}
I want to continue the while loop, if both values are null, because that is
defined as equal.
But I am struggling with this part:
if (aValue == null || bValue == null) {
if (aValue == null ^ bValue == null) {
return false;
}
}
Question:
Is there a more elegant and readable way to compare on nullity, do a further compare if both are not null, return false if only one is null, and continue the loop, if both values are null?
The sequence as follows should work well:
if(aValue == null && bValue == null) continue; // both null; continue
if(aValue == null || bValue == null) return false; // any null; return false
if(aValue.compareTo(bValue) != 0) { // both non-null; compare
return false;
}
In Java8, you can build a Comparator that would replace comparison sequence at cost of creating an extra object (you will need to decide if you care about that):
Comparator<T> cmp = Comparator.nullsLast(Comparator.naturalOrder());
The compararor will take care of null comparison for you (since you assume that two nulls are equal):
while (aIt.hasNext()) {
T aValue = aIt.next();
T bValue = bIt.next();
if (cmp.compare(aValue, bValue) != 0) {
return false;
}
}
I need to check if all values in a map are equal. I have a method to perform this task but would like to use a library or native methods. Limitations: Java 5 + Apache Commons libraries.
public static boolean isUnique(Map<Dboid,?> aMap){
boolean isUnique = true;
Object currValue = null;
int iteration = 0;
Iterator<?> it = aMap.entrySet().iterator();
while(it.hasNext() && isUnique){
iteration++;
Object value = it.next();
if(iteration > 1){
if (value != null && currValue == null ||
value == null && currValue != null ||
value != null && currValue != null & !value.equals(currValue)) {
isUnique = false;
}
}
currValue = value;
}
return isUnique;
}
What about this something like this:
Set<String> values = new HashSet<String>(aMap.values());
boolean isUnique = values.size() == 1;
how about
return (new HashSet(aMap.values()).size() == 1)
I know the original questions asks for solutions in Java 5, but in case someone else searching for an answer to this question is not limited to Java 5 here is a Java 8 approach.
return aMap.values().stream().distinct().limit(2).count() < 2
You could store the values in a Bidirectional Map and always have this property.
public static boolean isUnique(Map<Dboid,?> aMap) {
Set<Object> values = new HashSet<Object>();
for (Map.Entry<Dboid,?> entry : aMap.entrySet()) {
if (!values.isEmpty() && values.add(entry.getValue())) {
return false;
}
}
return true;
}
This solution has the advantage to offer a memory-saving short cut if there are many differences in the map. For the special case of an empty Map you might choose false as return value, change it appropriately for your purpose.
Or even better without a Set (if your Map does not contain null-values):
public static boolean isUnique(Map<Dboid,?> aMap) {
Object value = null;
for (Object entry : aMap.values()) {
if (value == null) {
value = entry;
} else if (!value.equals(entry)) {
return false;
}
}
return true;
}
As my comment above:
//think in a more proper name isAllValuesAreUnique for example
public static boolean isUnique(Map<Dboid,?> aMap){
if(aMap == null)
return true; // or throw IlegalArgumentException()
Collection<?> c = aMap.getValues();
return new HashSet<>(c).size() <= 1;
}
What is the best way to deal with null values, when doing Collections.sort() on nested objects?
I'd like to sort a couple of objects, basically applying this rule:
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
However, any of these objects can be null (i.e. invoice position, invoice and invoice number).
public class InvoicePos {
private Invoice invoice = null;
// ...
}
public class Invoice {
private String invoiceNo = "";
// ...
}
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
For clarification: I'm aware that my above example may raise NullPointerExceptions. Currently I'm doing the following and basically, I questioned myself, if there is any smarter approach.
Collections.sort(allInvoicePositions, new Comparator<InvoicePos>() {
#Override
public int compare(final InvoicePos invoicePosOne, final InvoicePos invoicePosTwo) {
if (null == invoicePosOne && null == invoicePosTwo) {
return 0;
}
if (null == invoicePosOne) {
return -1;
}
if (null == invoicePosTwo) {
return 1;
}
if (null == invoicePosOne.getInvoice() && null == invoicePosTwo.getInvoice()) {
return 0;
}
if (null == invoicePosOne.getInvoice()) {
return -1;
}
if (null == invoicePosTwo.getInvoice()) {
return 1;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo() && null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 0;
}
if (null == invoicePosOne.getInvoice().getInvoiceNo()) {
return -1;
}
if (null == invoicePosTwo.getInvoice().getInvoiceNo()) {
return 1;
}
return invoicePosOne.getInvoice().getInvoiceNo().compareTo(invoicePosTwo.getInvoice().getInvoiceNo());
}
});
There is something called as NullComparator in org.apache.commons.collections.jar.
This might help you https://commons.apache.org/proper/commons-collections/javadocs/api-2.1.1/org/apache/commons/collections/comparators/NullComparator.html.
Do I have do do explicit null-checks on all my objects or is there an approach with less writing?
If these values don't represent anything in your collection, then the best thing you can do is avoid them; don't allow inserting them, so you won't have to handle them when comparing items.
If you insist to have them, then you must check if they're null to avoid NullPointerException.
If you have null values then you need to handle them explicitly and in a consistent way so to have a valid ordering relation. That is, something like:
compare (a, b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return +1;
return comp(a,b);
}
Don't be tempted to do something like:
compare (a, b) {
if (a == null || b == null) return -1;
return comp(a,b);
}
which would break the ordering relation.
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();
}
}