Building a String Comparator in Java
What is the Comparator in Java
A Comparator in java is an interface seen here, where a developer can leverage the comparator functional interface to build a custom compare method. We can use the custom compare method to sort our objects. In this post I will be showing an example of sorting using custom compare functions.
Simple comparator example to sort by string length
We have a basic main method that passes in a sentence to a our function parseWords(String input)
. In this function we get each word from the sentence into a string array by splitting on spaces String[] strArray = input.split(" ");
. Once we have the String array we convert it to an ArrayList and call Collections.sort(wordList, new SimpleCompareStringLength());
.
public class SimpleComparator {
static void parseWords(String input) {
String[] strArray = input.split(" ");
List<String> wordList = Arrays.asList(strArray);
Collections.sort(wordList, new SimpleCompareStringLength());
// Arrays.sort(str, Comparator.comparingInt(String::length));
for(int i = 0; i < wordList.size(); i++){
System.out.println(" " + wordList.get(i));
}
}
public static void main(String[] args) {
parseWords("So that his place shall never be with those cold and timid souls who neither know victory nor defeat");
}
}
If we take a look at the collections.sort method from java.util.Collections
class, we can see that it takes a list of generic items and a Comparator of the same generic items.
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
The list.sort(c)
calls several methods down the stack but ultimately ends up at a merge sort implementation on the java.util.Arrays
in this method signature:
private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off, Comparator c)
The mergesort implementation can be seen in the code here but I think for now it’s sufficient for us to know that it exists and this is what is happening during the Collections.sort()
call.
Below we have our simple compare string length class which implements the functional interface Comparator
class SimpleCompareStringLength implements Comparator<String> {
public int compare(String o1, String o2) {
int result = Integer.compare(o1.length(), o2.length());
System.out.println("compared: " + o1 +" -> "+ o2 + " = " + result);
return result;
}
}
Inside the compare method we can see that we call an existing implementation of Integer.compare
. Below we see the implmentation of that method and it’s a simple next teranary operator. There are three simple cases:
- x == y returns 0
- x < y returns -1
- x > y returns 1
public static int compare(int x, int y) {
return x < y ? -1 : (x == y ? 0 : 1);
}
We can run the code above as a simple java main execution and the output is as follows.
compared: that -> So = 1
compared: his -> that = -1
compared: his -> that = -1
compared: his -> So = 1
compared: place -> his = 1
compared: place -> that = 1
compared: shall -> that = 1
compared: shall -> place = 0
compared: never -> that = 1
compared: never -> shall = 0
compared: be -> place = -1
compared: be -> his = -1
compared: be -> So = 0
compared: with -> that = 0
compared: with -> shall = -1
compared: with -> place = -1
compared: those -> with = 1
compared: those -> shall = 0
compared: those -> never = 0
compared: cold -> with = 0
compared: cold -> never = -1
compared: cold -> shall = -1
compared: cold -> place = -1
compared: and -> cold = -1
compared: and -> his = 0
compared: and -> with = -1
compared: and -> that = -1
compared: timid -> with = 1
compared: timid -> shall = 0
compared: timid -> those = 0
compared: souls -> cold = 1
compared: souls -> never = 0
compared: souls -> timid = 0
compared: who -> cold = -1
compared: who -> and = 0
compared: who -> with = -1
compared: who -> that = -1
compared: neither -> cold = 1
compared: neither -> those = 1
compared: neither -> souls = 1
compared: know -> cold = 0
compared: know -> those = -1
compared: know -> shall = -1
compared: know -> place = -1
compared: victory -> know = 1
compared: victory -> those = 1
compared: victory -> souls = 1
compared: victory -> neither = 0
compared: nor -> know = -1
compared: nor -> who = 0
compared: nor -> with = -1
compared: nor -> that = -1
compared: defeat -> know = 1
compared: defeat -> timid = 1
compared: defeat -> neither = -1
compared: defeat -> souls = 1
So
be
his
and
who
nor
that
with
cold
know
place
shall
never
those
timid
souls
defeat
neither
victory
What if we want to chain our sorts
Imagine that you wanted to do several stages of sorting.
- sort by length (we already have this implemented above)
- sort by alphabet and capitals
We will want to do chaining in this case. With chaining we can call thenComparing(Comparator)
as seen below:
Collections.sort(wordList, new SimpleCompareStringLength()
.thenComparing(String::compareTo));
Here we can see the code for the String.compareTo
public int compareTo(String anotherString) {
byte[] v1 = this.value;
byte[] v2 = anotherString.value;
if (this.coder() == anotherString.coder()) {
return this.isLatin1() ? StringLatin1.compareTo(v1, v2) : StringUTF16.compareTo(v1, v2);
} else {
return this.isLatin1() ? StringLatin1.compareToUTF16(v1, v2) : StringUTF16.compareToLatin1(v1, v2);
}
}
If we run our code with the new changes to our collections sort function call we get:
So
be
and
his
nor
who
cold
know
that
with
never
place
shall
souls
those
timid
defeat
neither
victory
Ending remarks
I hope that this explanation sheds some light on the usage of comparator’s and the collections.sort function. I also wanted to point out as our example text I used part of the quote from Theodore Roosevelt titled “The man in the arena” originally taken from his “Citizenship in a Republic” speech.
Code for these examples can be found on my github here
Thanks for reading!