How to Use Comparator to Define a Custom Sort Order

Custom Comparator - Java

Comparison-based sorts distinguish values by determining whether or not they are <, =, or >.

For example, 3 < 4, 4 = 4, 4 > 3.

Java Comparators use the convention that

  • cmp(a, b) < 0 means a < b
  • cmp(a, b) = 0 means a = b
  • cmp(a, b) > 0 means a > b

Note that this means if cmp(x, y) = x - y, then you get the normal ordering for the integers. You can check yourself that cmp(x, y) = -(x- y) = y - x gives you the reverse ordering.

When sorting (or doing something else that moves generic elements around by their order, like a PriorityQueue), the algorithm will (repeatedly) consult the comparator to determine whether or not values it has been given are <, =, or >.

How to create a custom comparator to sort a numbering system

There are few problems with your code:

  1. You are using raw type Comparator

    class VersionComparator implements Comparator // no <Type> here

    so compiler for Comparator<T> will try to use Object as T. This means that it will be compiling compare(T t1, T t2) method as compare(Object t1, Object t2).

    If you want that method to compile as compare(String t1, String o2) so

    • it would only accept String arguments
    • it could internally use String methods like length()

    set <String> as generic type via

    class VersionComparator implements Comparator<String>

    Now compiler will know that T is String.

  2. When you are calling split(".") you are not splitting on dot, but on any character (except line separators) because split is using regular expression (regex), and in regex . represents any character beside line separators. Because of this at start you will get array of empty elements, which will then be cleared because split(regex) removes empty trailing strings. More info here: https://stackoverflow.com/a/21790319/1393766.

    To solve this problem you will need to escape . for instance with "\\.".

This should cover main problems in your code. But if you would like to create nice solution then consider avoiding sorting Strings but rather instances of your own Version class.

Advantage of this approach is that you will not have to parse your String to int each time you want to compare two strings (which can be costly), but you will be able to store parsed version of "1.2.3" as array [1, 2, 3] and reuse it when needed.

Also Version class can implement Comparable<Vector> to provide default comparing method.

So your code can look more like:

class Version implements Comparable<Version> {

private String versionStr;
private int[] arr;

public String getVersionStr() { return versionStr; }

@Override public String toString() { return versionStr; }

@Override
public int compareTo(Version other) {

int result = Integer.compare(arr[0], other.arr[0]);

if (result != other)
return result;

return Integer.compare(arr[1], other.arr[1]);
}

public Version(String versionStr) {
this.versionStr = versionStr;
this.arr = Stream.of(versionStr.split("\\."))
.limit(2)//get only two first elements
.mapToInt(Integer::parseInt)//convert String to int
.toArray();//create array with integers
}

}

class Demo {

public static void main(String[] args) {

List<Version> list = new ArrayList<Version>();

list.add(new Version("3.3.x"));
list.add(new Version("1.2.x"));
list.add(new Version("1.11.x"));
list.add(new Version("2.3.x"));

Collections.sort(list);//with use default order provided by compareTo(Version other)

for (Version str : list)
System.out.println(str);
}
}

Java Using Comparators in combination with custom Comparators

You can do this something like that:

coll.sort(Comparator
.comparingInt((String s) -> s.equals("just") ? 0 : 1) // Words "just" first
.thenComparing(Comparator.naturalOrder())); // Then others

Java custom comparator with different sort options

With some modifications, yes.

Add a constructor with an enum argument to define which field to use:

public class ItemComparator implements Comparator<Item>{
public enum Field {
ID, TIMESTAMP;
}

private Field field;

public ItemComparator(Field field) {
this.field = field;
}

Then in the compare method, switch on the field chosen:

@Override
public int compare(Item mdi1, Item mdi2) {
int comparison = 0;
switch(field) {
case TIMESTAMP:
comparison = mdi1.getTimestamp().compareTo(mdi2.getTimestamp());
case ID:
comparison = mdi1.getID() - mdi2.getID();
}
return comparison;
}

Sort ArrayList of custom Objects by property

Since Date implements Comparable, it has a compareTo method just like String does.

So your custom Comparator could look like this:

public class CustomComparator implements Comparator<MyObject> {
@Override
public int compare(MyObject o1, MyObject o2) {
return o1.getStartDate().compareTo(o2.getStartDate());
}
}

The compare() method must return an int, so you couldn't directly return a boolean like you were planning to anyway.

Your sorting code would be just about like you wrote:

Collections.sort(Database.arrayList, new CustomComparator());

A slightly shorter way to write all this, if you don't need to reuse your comparator, is to write it as an inline anonymous class:

Collections.sort(Database.arrayList, new Comparator<MyObject>() {
@Override
public int compare(MyObject o1, MyObject o2) {
return o1.getStartDate().compareTo(o2.getStartDate());
}
});


Since java-8

You can now write the last example in a shorter form by using a lambda expression for the Comparator:

Collections.sort(Database.arrayList, 
(o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

And List has a sort(Comparator) method, so you can shorten this even further:

Database.arrayList.sort((o1, o2) -> o1.getStartDate().compareTo(o2.getStartDate()));

This is such a common idiom that there's a built-in method to generate a Comparator for a class with a Comparable key:

Database.arrayList.sort(Comparator.comparing(MyObject::getStartDate));

All of these are equivalent forms.

Sorting an ArrayList of objects using a custom sorting order

Here's a tutorial about ordering objects:

  • The Java Tutorials - Collections - Object Ordering

Although I will give some examples, I would recommend to read it anyway.


There are various way to sort an ArrayList. If you want to define a natural (default) ordering, then you need to let Contact implement Comparable. Assuming that you want to sort by default on name, then do (nullchecks omitted for simplicity):

public class Contact implements Comparable<Contact> {

private String name;
private String phone;
private Address address;

@Override
public int compareTo(Contact other) {
return name.compareTo(other.name);
}

// Add/generate getters/setters and other boilerplate.
}

so that you can just do

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

Collections.sort(contacts);

If you want to define an external controllable ordering (which overrides the natural ordering), then you need to create a Comparator:

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

// Now sort by address instead of name (default).
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.getAddress().compareTo(other.getAddress());
}
});

You can even define the Comparators in the Contact itself so that you can reuse them instead of recreating them everytime:

public class Contact {

private String name;
private String phone;
private Address address;

// ...

public static Comparator<Contact> COMPARE_BY_PHONE = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.phone.compareTo(other.phone);
}
};

public static Comparator<Contact> COMPARE_BY_ADDRESS = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.address.compareTo(other.address);
}
};

}

which can be used as follows:

List<Contact> contacts = new ArrayList<Contact>();
// Fill it.

// Sort by address.
Collections.sort(contacts, Contact.COMPARE_BY_ADDRESS);

// Sort later by phone.
Collections.sort(contacts, Contact.COMPARE_BY_PHONE);

And to cream the top off, you could consider to use a generic javabean comparator:

public class BeanComparator implements Comparator<Object> {

private String getter;

public BeanComparator(String field) {
this.getter = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}

public int compare(Object o1, Object o2) {
try {
if (o1 != null && o2 != null) {
o1 = o1.getClass().getMethod(getter, new Class[0]).invoke(o1, new Object[0]);
o2 = o2.getClass().getMethod(getter, new Class[0]).invoke(o2, new Object[0]);
}
} catch (Exception e) {
// If this exception occurs, then it is usually a fault of the developer.
throw new RuntimeException("Cannot compare " + o1 + " with " + o2 + " on " + getter, e);
}

return (o1 == null) ? -1 : ((o2 == null) ? 1 : ((Comparable<Object>) o1).compareTo(o2));
}

}

which you can use as follows:

// Sort on "phone" field of the Contact bean.
Collections.sort(contacts, new BeanComparator("phone"));

(as you see in the code, possibly null fields are already covered to avoid NPE's during sort)

How to define custom sorted comparator in java 8 Stream to compare on the key and the value

You are looking for a custom Comparator such as this:

.sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()) == 0 ?
o1.getKey().compareTo(o2.getKey()) : o2.getValue().compareTo(o1.getValue()))

Theoretically,

  • compare the values first in descending order o2.getValue().compareTo(o1.getValue()) and

  • if they are equal compare the keys in the ascending order o1.getKey().compareTo(o2.getKey()).



Related Topics



Leave a reply



Submit