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
meansa < b
cmp(a, b) = 0
meansa = b
cmp(a, b) > 0
meansa > 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:
You are using raw type Comparator
class VersionComparator implements Comparator // no <Type> here
so compiler for
Comparator<T>
will try to useObject
asT
. This means that it will be compilingcompare(T t1, T t2)
method ascompare(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 viaclass VersionComparator implements Comparator<String>
Now compiler will know that
T
isString
.- it would only accept
When you are calling
split(".")
you are not splitting on dot, but on any character (except line separators) becausesplit
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 becausesplit(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 Comparator
s 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())
andif they are equal compare the keys in the ascending order
o1.getKey().compareTo(o2.getKey())
.
Related Topics
What's Java Hybrid - Applet + Application
Calling a Servlet from Jsp File on Page Load
How to Set Selected Item of Spinner by Value, Not by Position
Foreign Key Constraints in Android Using SQLite? on Delete Cascade
Android: Difference Between Onintercepttouchevent and Dispatchtouchevent
How to Store Unix Permissions in a Zip File (Built with Apache Ant)
A Simple Way of Embedding a Video in My Swing Gui
Why Does Killing Jvm Also Terminates Its Child Process If Waitfor Has Been Used
Why Does Java Rmi Keep Connecting to 127.0.1.1. When Ip Is 192.168.X.X
Multiple Row Selection in Jtable
MySQLsyntaxerrorexception Near "" When Trying to Execute Preparedstatement
Android Recyclerview Addition & Removal of Items
Android Download Binary File Problems
Change File Owner Group Under Linux with Java.Nio.Files