blob: d520fdbf301f275371d83d4b622aef7acf43c262 [file] [log] [blame]
---
layout: post
status: PUBLISHED
published: true
title: Comparators and Sorting in Groovy
id: 0e546548-0c7e-4abc-8fa4-d6cd19c919dd
date: '2022-07-21 15:51:31 -0400'
categories: groovy
tags:
- groovy
- sorting
- functional
- comparators
- lambdas
- gquery
- records
permalink: groovy/entry/comparators-and-sorting-in-groovy
---
<p><img src="https://blogs.apache.org/groovy/mediaresource/2eb621f3-0419-437e-950b-0c9e5e15804e" align="right" style="width:15%;" alt="2022-07-22 01_05_29-s-l300.webp (300&times;291).png">This blog post is inspired by the Comparator examples in the excellent <i>Collections Refuelled</i> <a href="https://www.youtube.com/watch?v=q6zF3vf114M&amp;t=13s" target="_blank">talk</a> and <a href="https://blogs.oracle.com/java/post/collections-refueled" target="_blank">blog</a> by Stuart Marks. That blog from 2017 highlights improvements in the Java collections library in Java 8 and 9 including numerous Comparator improvements. It is now 5 years old but still highly recommended for anyone using the Java collections library.</p>
<p>Rather than have a <code>Student</code> class as per the original blog example, we'll have a <code>Celebrity</code> class (and later record) which has the same <code>first</code> and <code>last</code> name fields and an additional <code>age</code> field. We'll compare initially by <code>last</code> name with nulls before non-nulls and then by <code>first</code> name and lastly by <code>age</code>.</p>
<p>As with the original blog, we'll cater for nulls, e.g. a celebrity known by a single name.</p>
<h3>The Java comparator story recap</h3>
<p><img src="https://blogs.apache.org/groovy/mediaresource/97dd3c82-aae5-481a-a158-701c8244d5c1" style="width:10%;" align="right" alt="JavaTransparent.png">Our <code>Celebrity</code> class if we wrote it in Java would look something like:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#0033b3;">public class </span><span style="color:#000000;">Celebrity </span>{ <span style="color:#777777;">// Java</span><br> <span style="color:#0033b3;">private </span><span style="color:#000000;">String </span><span style="color:#871094;">firstName</span>;<br> <span style="color:#0033b3;">private </span><span style="color:#000000;">String </span><span style="color:#871094;">lastName</span>;<br> <span style="color:#0033b3;">private int </span><span style="color:#871094;">age</span>;<br><br> <span style="color:#0033b3;">public </span><span style="color:#00627a;">Celebrity</span>(<span style="color:#000000;">String </span>firstName, <span style="color:#0033b3;">int </span>age) {<br> <span style="color:#0033b3;">this</span>(firstName, <span style="color:#0033b3;">null</span>, age);<br> }<br><br> <span style="color:#0033b3;">public </span><span style="color:#00627a;">Celebrity</span>(<span style="color:#000000;">String </span>firstName, <span style="color:#000000;">String </span>lastName, <span style="color:#0033b3;">int </span>age) {<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">firstName </span>= firstName;<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">lastName </span>= lastName;<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">age </span>= age;<br> }<br><br> <span style="color:#0033b3;">public int </span><span style="color:#00627a;">getAge</span>() {<br> <span style="color:#0033b3;">return </span><span style="color:#871094;">age</span>;<br> }<br><br> <span style="color:#0033b3;">public void </span><span style="color:#00627a;">setAge</span>(<span style="color:#0033b3;">int </span>age) {<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">age </span>= age;<br> }<br><br> <span style="color:#0033b3;">public </span><span style="color:#000000;">String </span><span style="color:#00627a;">getFirstName</span>() {<br> <span style="color:#0033b3;">return </span><span style="color:#871094;">firstName</span>;<br> }<br><br> <span style="color:#0033b3;">public void </span><span style="color:#00627a;">setFirstName</span>(<span style="color:#000000;">String </span>firstName) {<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">firstName </span>= firstName;<br> }<br><br> <span style="color:#0033b3;">public </span><span style="color:#000000;">String </span><span style="color:#00627a;">getLastName</span>() {<br> <span style="color:#0033b3;">return </span><span style="color:#871094;">lastName</span>;<br> }<br><br> <span style="color:#0033b3;">public void </span><span style="color:#00627a;">setLastName</span>(<span style="color:#000000;">String </span>lastName) {<br> <span style="color:#0033b3;">this</span>.<span style="color:#871094;">lastName </span>= lastName;<br> }<br><br> <span style="color:#9e880d;">@Override<br></span><span style="color:#9e880d;"> </span><span style="color:#0033b3;">public </span><span style="color:#000000;">String </span><span style="color:#00627a;">toString</span>() {<br> <span style="color:#0033b3;">return </span><span style="color:#067d17;">"Celebrity{" </span>+<br> <span style="color:#067d17;">"firstName='" </span>+ <span style="color:#871094;">firstName </span>+<br> (<span style="color:#871094;">lastName </span>== <span style="color:#0033b3;">null </span>? <span style="color:#067d17;">"" </span>: <span style="color:#067d17;">"', lastName='" </span>+ <span style="color:#871094;">lastName</span>) +<br> <span style="color:#067d17;">"', age=" </span>+ <span style="color:#871094;">age </span>+<br> <span style="color:#067d17;">'}'</span>;<br> }<br>}<br></pre>
<p>It would look much nicer as a Java record (JDK16+) but we'll keep with the spirit of the original blog example for now. This is fairly standard boilerplate and in fact was mostly generated by IntelliJ IDEA. The only slightly interesting aspect is that we tweaked the <code>toString</code> method to not display null last names.</p>
<p>On JDK 8 with the old-style comparator coding, a main application which created and sorted some celebrities might look like this:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#0033b3;">import </span><span style="color:#000000;">java.util.ArrayList</span>; <span style="color:#777777;">// Java</span><br><span style="color:#0033b3;">import </span><span style="color:#000000;">java.util.Collections</span>;<br><span style="color:#0033b3;">import </span><span style="color:#000000;">java.util.List</span>;<br><br><span style="color:#0033b3;">public class </span><span style="color:#000000;">Main </span>{<br> <span style="color:#0033b3;">public static void </span><span style="color:#00627a;">main</span>(<span style="color:#000000;">String</span>[] args) {<br> <span style="color:#000000;">List</span><<span style="color:#000000;">Celebrity</span>> <span style="color:#000000;">celebrities </span>= <span style="color:#0033b3;">new </span>ArrayList<>();<br> <span style="color:#000000;">celebrities</span>.add(<span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Wang"</span>, <span style="color:#1750eb;">63</span>));<br> <span style="color:#000000;">celebrities</span>.add(<span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">28</span>));<br> <span style="color:#000000;">celebrities</span>.add(<span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">47</span>));<br> <span style="color:#000000;">celebrities</span>.add(<span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">37</span>));<br> <span style="color:#000000;">celebrities</span>.add(<span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#1750eb;">76</span>));<br> <span style="color:#000000;">Collections</span>.<span style="font-style:italic;">sort</span>(<span style="color:#000000;">celebrities</span>, (c1, c2) -> {<br> <span style="color:#000000;">String f1 </span>= c1.getLastName();<br> <span style="color:#000000;">String f2 </span>= c2.getLastName();<br> <span style="color:#0033b3;">int </span><span style="color:#000000;">r1</span>;<br> <span style="color:#0033b3;">if </span>(<span style="color:#000000;">f1 </span>== <span style="color:#0033b3;">null</span>) {<br> <span style="color:#000000;">r1 </span>= <span style="color:#000000;">f2 </span>== <span style="color:#0033b3;">null </span>? <span style="color:#1750eb;">0 </span>: -<span style="color:#1750eb;">1</span>;<br> } <span style="color:#0033b3;">else </span>{<br> <span style="color:#000000;">r1 </span>= <span style="color:#000000;">f2 </span>== <span style="color:#0033b3;">null </span>? <span style="color:#1750eb;">1 </span>: <span style="color:#000000;">f1</span>.compareTo(<span style="color:#000000;">f2</span>);<br> }<br> <span style="color:#0033b3;">if </span>(<span style="color:#000000;">r1 </span>!= <span style="color:#1750eb;">0</span>) {<br> <span style="color:#0033b3;">return </span><span style="color:#000000;">r1</span>;<br> }<br> <span style="color:#0033b3;">int </span><span style="color:#000000;">r2 </span>= c1.getFirstName().compareTo(c2.getFirstName());<br> <span style="color:#0033b3;">if </span>(<span style="color:#000000;">r2 </span>!= <span style="color:#1750eb;">0</span>) {<br> <span style="color:#0033b3;">return </span><span style="color:#000000;">r2</span>;<br> }<br> <span style="color:#0033b3;">return </span><span style="color:#000000;">Integer</span>.<span style="font-style:italic;">compare</span>(c1.getAge(), c2.getAge());<br> });<br> <span style="color:#000000;">System</span>.<span style="color:#871094;font-style:italic;">out</span>.println(<span style="color:#067d17;">"Celebrities:"</span>);<br> <span style="color:#000000;">celebrities</span>.forEach(<span style="color:#000000;">System</span>.<span style="color:#871094;font-style:italic;">out</span>::println);<br> }<br>}<br></pre>
<p>When we run this example, the output looks like this:</p>
<pre>Celebrities:
Celebrity{firstName='Cher', age=76}
Celebrity{firstName='Alex', lastName='Lloyd', age=37}
Celebrity{firstName='Alex', lastName='Lloyd', age=47}
Celebrity{firstName='Cher', lastName='Lloyd', age=28}
Celebrity{firstName='Cher', lastName='Wang', age=63}
</pre>
<p>As pointed out in the original blog, this code is rather tedious and error-prone and can be improved greatly with comparator improvements in JDK8:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#0033b3;">import </span><span style="color:#000000;">java.util.Arrays</span>; // Java<br><span style="color:#0033b3;">import </span><span style="color:#000000;">java.util.List</span>;<br><br><span style="color:#0033b3;">import static </span><span style="color:#000000;">java.util.Comparator</span>.<span style="font-style:italic;">comparing</span>;<br><span style="color:#0033b3;">import static </span><span style="color:#000000;">java.util.Comparator</span>.<span style="font-style:italic;">naturalOrder</span>;<br><span style="color:#0033b3;">import static </span><span style="color:#000000;">java.util.Comparator</span>.<span style="font-style:italic;">nullsFirst</span>;<br><br><span style="color:#0033b3;">public class </span><span style="color:#000000;">Main </span>{<br> <span style="color:#0033b3;">public static void </span><span style="color:#00627a;">main</span>(<span style="color:#000000;">String</span>[] args) {<br> <span style="color:#000000;">List</span><<span style="color:#000000;">Celebrity</span>> <span style="color:#000000;">celebrities </span>= <span style="color:#000000;">Arrays</span>.<span style="font-style:italic;">asList</span>(<br> <span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Wang"</span>, <span style="color:#1750eb;">63</span>),<br> <span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">28</span>),<br> <span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">47</span>),<br> <span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">37</span>),<br> <span style="color:#0033b3;">new </span>Celebrity(<span style="color:#067d17;">"Cher"</span>, <span style="color:#1750eb;">76</span>));<br> <span style="color:#000000;">celebrities</span>.sort(<span style="font-style:italic;">comparing</span>(<span style="color:#000000;">Celebrity</span>::getLastName, <span style="font-style:italic;">nullsFirst</span>(<span style="font-style:italic;">naturalOrder</span>())).<br> thenComparing(<span style="color:#000000;">Celebrity</span>::getFirstName).thenComparing(<span style="color:#000000;">Celebrity</span>::getAge));<br> <span style="color:#000000;">System</span>.<span style="color:#871094;font-style:italic;">out</span>.println(<span style="color:#067d17;">"Celebrities:"</span>);<br> <span style="color:#000000;">celebrities</span>.forEach(<span style="color:#000000;">System</span>.<span style="color:#871094;font-style:italic;">out</span>::println);<br> }<br>}<br></pre>
<p>The original blog also points out the convenience factory methods from JDK9 for list creation which you might be tempted to consider here. For our case, we will be sorting in place, so the immutable lists returned by those methods won't help us here but <code>Arrays.asList</code> isn't much longer than <code>List.of</code> and works well for this example.</p>
<p>As well as being much shorter, the <code>comparing</code> and <code>thenComparing</code> methods and built-in comparators like <code>nullsFirst</code> and <code>naturalOrdering</code> allow for far simpler composability. The sort within array list is also more efficient than the sort that would have been used with the <code>Collections.sort</code> method on earlier JDKs. The output when running the example is the same as previously.</p>
<h3>The Groovy comparator story</h3>
<p><img src="https://blogs.apache.org/groovy/mediaresource/c5ba5e59-737e-4ebf-91c9-08fa67dc8f70" align="right" style="width:15%;" alt="groovy.png">At about the same time that Java was evolving its comparator story Groovy added some complementary features to tackle many of the same problems. We'll look at some of those features and also how the JDK improvements we saw above features can be used instead if preferred.</p>
<p>First off, let's create a Groovy <code>Celebrity</code> record:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#9e880d;">@Sortable</span>(includes = <span style="color:#067d17;">'last,first,age'</span>)<br><span style="color:#9e880d;">@ToString</span>(ignoreNulls = <span style="color:#0033b3;">true</span>, includeNames = <span style="color:#0033b3;">true</span>)<br><span style="color:#0033b3;">record </span><span style="color:#000000;">Celebrity</span>(<span style="color:#000000;">String </span>first, <span style="color:#000000;">String </span>last = <span style="color:#0033b3;">null</span>, <span style="color:#0033b3;">int </span>age) {}</pre>
<p>And create our list of celebrities:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#0033b3;">var </span><span style="color:#000000;">celebrities </span>= [<br> <span style="color:#0033b3;">new </span><span style="color:#000000;">Celebrity</span>(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Wang"</span>, <span style="color:#1750eb;">63</span>),<br> <span style="color:#0033b3;">new </span><span style="color:#000000;">Celebrity</span>(<span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">28</span>),<br> <span style="color:#0033b3;">new </span><span style="color:#000000;">Celebrity</span>(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">47</span>),<br> <span style="color:#0033b3;">new </span><span style="color:#000000;">Celebrity</span>(<span style="color:#067d17;">"Alex"</span>, <span style="color:#067d17;">"Lloyd"</span>, <span style="color:#1750eb;">37</span>),<br> <span style="color:#0033b3;">new </span><span style="color:#000000;">Celebrity</span>(<span style="color:#067d17;">first</span>: <span style="color:#067d17;">"Cher"</span>, <span style="color:#067d17;">age</span>: <span style="color:#1750eb;">76</span>)<br>]<br></pre>
<p>The record definition is nice and concise. It would look good in recent Java versions too. A nice aspect of the Groovy solution is that it will provide emulated records on earlier JDKs and it also has some nice declarative transforms to tweak the record definition. We could leave off the <code>@ToString</code> annotation and we'd get a standard record-style <code>toString</code>. Or we could add a <code>toString</code> method to our record definition similar to what was done in the Java example. Using <code>@ToString</code> allows us to remove null last names from the <code>toString</code> in a declarative way. We'll cover the <code>@Sortable</code> annotation a little later.</p>
<p>First off, Groovy's spaceship operator <code><=></code> allows us to write a nice compact version of the "tedious" code in the first Java version. It looks like this:<br></p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#000000;">celebrities</span>.sort <span style="font-weight:bold;">{ </span>c1, c2 <span style="font-weight:bold;">-><br></span><span style="font-weight:bold;"> </span>c1.<span style="color:#871094;">last </span><=> c2.<span style="color:#871094;">last </span>?: c1.<span style="color:#871094;">first </span><=> c2.<span style="color:#871094;">first </span>?: c1.<span style="color:#871094;">age </span><=> c2.<span style="color:#871094;">age<br></span><span style="font-weight:bold;">}<br></span>println <span style="color:#067d17;">'Celebrities:</span><span style="color:#0037a6;">\n</span><span style="color:#067d17;">' </span>+ <span style="color:#000000;">celebrities</span>.join(<span style="color:#067d17;">'</span><span style="color:#0037a6;">\n</span><span style="color:#067d17;">'</span>)<br></pre>
<p>And the output looks like this:<br></p>
<pre>Celebrities:
Celebrity(first:Cher, age:76)
Celebrity(first:Alex, last:Lloyd, age:37)
Celebrity(first:Alex, last:Lloyd, age:47)
Celebrity(first:Cher, last:Lloyd, age:28)
Celebrity(first:Cher, last:Wang, age:63)</pre>
<p>We'd have a tiny bit more work to do if we wanted nulls last but the defaults work well for the example at hand.</p>
<p>We can alternatively, make use of the "new in JDK8" methods mentioned earlier:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#000000;">celebrities</span>.sort(<span style="font-style:italic;">comparing</span>(<span style="color:#000000;">Celebrity</span>::last, <span style="font-style:italic;">nullsFirst</span>(<span style="font-style:italic;">naturalOrder</span>())).<br> thenComparing(c -> c.first).thenComparing(c -> c.age))<br></pre>
<p>But this is where we should come back and further explain the <code>@Sortable</code> annotation. That annotation is associated with an Abstract Syntax Tree (AST) transformation, or just <i>transform</i> for short, which provides us with an automatic <code>compareTo</code> method that takes into account the record's properties (and likewise if it was a class). Since we provided an <code>includes</code> annotation attribute and provided a list of property names, the order of those names determines the priority of the properties used in the comparator. We could equally include just some of the names in that list or alternatively provide an <code>excludes</code> annotation attribute and just mention that properties we don't want included.</p>
<p>It also adds <code>Comparable<Celebrity></code> to the list of implemented interfaces for our record. So, what does all this mean? It means we can just write:<br></p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#000000;">celebrities</span>.sort()<br></pre>
<p>The transform associated with the <code>@Sortable</code> annotation also provides some additional comparators for us. To sort by age, we can use one of those comparators:<br></p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#000000;">celebrities</span>.sort(<span style="color:#000000;">Celebrity</span>.comparatorByAge())<br></pre>
<p>Which gives this output:</p>
<pre>Celebrities:
Celebrity(first:Cher, last:Lloyd, age:28)
Celebrity(first:Alex, last:Lloyd, age:37)
Celebrity(first:Alex, last:Lloyd, age:47)
Celebrity(first:Cher, last:Wang, age:63)
Celebrity(first:Cher, age:76)</pre>
<p>In addition to the <code>sort</code> method, Groovy provides a <code>toSorted</code> method which sorts a copy of the list, leaving the original unchanged. So, to create a list sorted by first name we can use this code:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#0033b3;">var </span><span style="color:#000000;">celebritiesByFirst </span>= <span style="color:#000000;">celebrities</span>.toSorted(<span style="color:#000000;">Celebrity</span>.comparatorByFirst())</pre>
<p>Which if output in a similar way to previous examples gives:</p>
<pre>Celebrities:
Celebrity(first:Alex, last:Lloyd, age:37)
Celebrity(first:Alex, last:Lloyd, age:47)
Celebrity(first:Cher, last:Lloyd, age:28)
Celebrity(first:Cher, last:Wang, age:63)
Celebrity(first:Cher, age:76)</pre>
<p>If you are a fan of functional style programming, you might consider using <code>List.of</code> to define the original list and then only <code>toSorted</code> method calls in further processing.</p>
<h3>Mixing in some language integrated queries</h3>
<p>Groovy also has a GQuery (aka GINQ) capability which provides a SQL inspired DSL for working with collections. We can use GQueries to examine and order our collection. Here is an example:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println <span style="font-style:italic;">GQ </span><span style="font-weight:bold;">{<br></span> from c <span style="color: rgb(0, 51, 179);">in </span><span style="color: rgb(0, 0, 0);">celebrities<br></span><span style="color: rgb(0, 0, 0);"> </span>select c.first, c.last, c.age<br><span style="font-weight:bold;">}</span></pre>
<p>Which has this output:</p>
<pre>+-------+-------+-----+
| first | last&nbsp; | age |
+-------+-------+-----+
| Cher&nbsp; |&nbsp; &nbsp; &nbsp; &nbsp;| 76&nbsp; |
| Alex&nbsp; | Lloyd | 37&nbsp; |
| Alex&nbsp; | Lloyd | 47&nbsp; |
| Cher&nbsp; | Lloyd | 28&nbsp; |
| Cher&nbsp; | Wang&nbsp; | 63&nbsp; |
+-------+-------+-----+</pre>
<p>In this case, it's using the natural ordering which <code>@Sortable</code> gives us.</p>
<p>Or we can sort by age:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println <span style="font-style:italic;">GQ </span><span style="font-weight:bold;">{<br></span> from c <span style="color: rgb(0, 51, 179);">in </span><span style="color: rgb(0, 0, 0);">celebrities<br></span><span style="color: rgb(0, 0, 0);"> </span>orderby c.age<br> select c.first, c.last, c.age<br><span style="font-weight:bold;">}<br></span></pre>
<p>Which has this output:</p>
<pre>+-------+-------+-----+
| first | last&nbsp; | age |
+-------+-------+-----+
| Cher&nbsp; | Lloyd | 28&nbsp; |
| Alex&nbsp; | Lloyd | 37&nbsp; |
| Alex&nbsp; | Lloyd | 47&nbsp; |
| Cher&nbsp; | Wang&nbsp; | 63&nbsp; |
| Cher&nbsp; |&nbsp; &nbsp; &nbsp; &nbsp;| 76&nbsp; |
+-------+-------+-----+
</pre>
<p>Or we can sort by last name descending and then age:</p>
<pre style="background-color:#ffffff;color:#080808;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println <span style="font-style:italic;">GQ </span><span style="font-weight:bold;">{<br></span> from c <span style="color: rgb(0, 51, 179);">in </span><span style="color: rgb(0, 0, 0);">celebrities<br></span><span style="color: rgb(0, 0, 0);"> </span>orderby c.last <span style="color: rgb(0, 51, 179);">in </span>desc, c.age<br> select c.first, c.last, c.age<br><span style="font-weight:bold;">}<br></span></pre>
<p>Which has this output:</p>
<pre>+-------+-------+-----+
| first | last&nbsp; | age |
+-------+-------+-----+
| Cher&nbsp; | Wang&nbsp; | 63&nbsp; |
| Cher&nbsp; | Lloyd | 28&nbsp; |
| Alex&nbsp; | Lloyd | 37&nbsp; |
| Alex&nbsp; | Lloyd | 47&nbsp; |
| Cher&nbsp; |&nbsp; &nbsp; &nbsp; &nbsp;| 76&nbsp; |
+-------+-------+-----+</pre>
<h3>Conclusion</h3>
<p>We have seen a little example of using comparators in Groovy. All the great JDK capabilities are available as well as the spaceship operator, the <code>sort</code> and <code>toSorted</code> methods, and the <code>@Sortable</code> AST transformation.</p>