| <!DOCTYPE html> |
| <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> |
| <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> |
| <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> |
| <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head> |
| <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='comparators, functional, gquery, ginq, groovy, lambdas, records, sorting'/><meta name='description' content='This post looks at Groovy functionality for making your classes comparable and/or sortable.'/><title>The Apache Groovy programming language - Blogs - Comparators and Sorting in Groovy</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/> |
| </head><body> |
| <div id='fork-me'> |
| <a href='https://github.com/apache/groovy'> |
| <img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/> |
| </a> |
| </div><div id='st-container' class='st-container st-effect-9'> |
| <nav class='st-menu st-effect-9' id='menu-12'> |
| <h2 class='icon icon-lab'>Socialize</h2><ul> |
| <li> |
| <a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a> |
| </li><li> |
| <a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a> |
| </li><li> |
| <a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a> |
| </li><li> |
| <a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a> |
| </li><li> |
| <a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a> |
| </li><li> |
| <a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a> |
| </li><li> |
| <a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a> |
| </li> |
| </ul> |
| </nav><div class='st-pusher'> |
| <div class='st-content'> |
| <div class='st-content-inner'> |
| <!--[if lt IE 7]> |
| <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> |
| <![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'> |
| <div class='container'> |
| <div class='navbar-header'> |
| <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'> |
| <span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span> |
| </button><a class='navbar-brand' href='../index.html'> |
| <i class='fa fa-star'></i> Apache Groovy |
| </a> |
| </div><div class='navbar-collapse collapse'> |
| <ul class='nav navbar-nav navbar-right'> |
| <li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li> |
| <a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a> |
| </li><li class=''> |
| <a href='../search.html'> |
| <i class='fa fa-search'></i> |
| </a> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>Comparators and Sorting in Groovy</a></li><li><a href='#_the_java_comparator_story_recap' class='anchor-link'>The Java comparator story recap</a></li><li><a href='#_the_groovy_comparator_story' class='anchor-link'>The Groovy comparator story</a></li><li><a href='#_mixing_in_some_language_integrated_queries' class='anchor-link'>Mixing in some language integrated queries</a></li><li><a href='#_conclusion' class='anchor-link'>Conclusion</a></li></ul><br/><ul class='nav-sidebar'><li style='padding: 0.35em 0.625em; background-color: #eee'><span>Related posts</span></li><li><a href='./zipping-collections-with-groovy'>Zipping Collections with Groovy</a></li><li><a href='./groovy-list-processing-cheat-sheet'>Groovy List Processing Cheat Sheet</a></li><li><a href='./parsing-json-with-groovy'>Parsing JSON with Groovy</a></li><li><a href='./groovy-dauphine'>Processing Results for the Critérium du Dauphiné</a></li><li><a href='./deep-learning-and-eclipse-collections'>Deep Learning and Eclipse Collections</a></li><li><a href='./reading-and-writing-csv-files'>Reading and Writing CSV files with Groovy</a></li><li><a href='./deck-of-cards-with-groovy'>Deck of cards with Groovy, JDK collections and Eclipse Collections</a></li><li><a href='./groovy-record-performance'>Groovy Record Performance</a></li><li><a href='./groovy-records'>Groovy Records</a></li><li><a href='./using-groovy-with-apache-wayang'>Using Groovy with Apache Wayang and Apache Spark</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>Comparators and Sorting in Groovy</h1><p><span>Author: <i>Paul King</i></span><br/><span>Published: 2022-07-21 03:51PM</span></p><hr/><div id="preamble"> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p><span class="image right"><img src="img/cher_record.png" alt="Cher" width="179" height="179"></span> |
| This blog post is inspired by the Comparator examples in the excellent |
| <em>Collections Refuelled</em> <a href="https://www.youtube.com/watch?v=q6zF3vf114M&t=13s">talk</a> and <a href="https://blogs.oracle.com/java/post/collections-refueled">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> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="paragraph"> |
| <p>As with the original blog, we’ll cater for nulls, |
| e.g. a celebrity known by a single name.</p> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_the_java_comparator_story_recap">The Java comparator story recap</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p><span class="image right"><img src="img/JavaLogo.png" alt="Java logo" width="95" height="128"></span> |
| Our <code>Celebrity</code> class if we wrote it in Java would look something like:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">public class Celebrity { // Java |
| private String firstName; |
| private String lastName; |
| private int age; |
| |
| public Celebrity(String firstName, int age) { |
| this(firstName, null, age); |
| } |
| |
| public Celebrity(String firstName, String lastName, int age) { |
| this.firstName = firstName; |
| this.lastName = lastName; |
| this.age = age; |
| } |
| |
| public int getAge() { |
| return age; |
| } |
| |
| public void setAge(int age) { |
| this.age = age; |
| } |
| |
| public String getFirstName() { |
| return firstName; |
| } |
| |
| public void setFirstName(String firstName) { |
| this.firstName = firstName; |
| } |
| |
| public String getLastName() { |
| return lastName; |
| } |
| |
| public void setLastName(String lastName) { |
| this.lastName = lastName; |
| } |
| |
| @Override |
| public String toString() { |
| return "Celebrity{" + |
| "firstName='" + firstName + |
| (lastName == null ? "" : "', lastName='" + lastName) + |
| "', age=" + age + |
| '}'; |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="paragraph"> |
| <p>On JDK 8 with the old-style comparator coding, a main application which created |
| and sorted some celebrities might look like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">import java.util.ArrayList; // Java |
| import java.util.Collections; |
| import java.util.List; |
| |
| public class Main { |
| public static void main(String[] args) { |
| List<Celebrity> celebrities = new ArrayList<>(); |
| celebrities.add(new Celebrity("Cher", "Wang", 63)); |
| celebrities.add(new Celebrity("Cher", "Lloyd", 28)); |
| celebrities.add(new Celebrity("Alex", "Lloyd", 47)); |
| celebrities.add(new Celebrity("Alex", "Lloyd", 37)); |
| celebrities.add(new Celebrity("Cher", 76)); |
| Collections.sort(celebrities, (c1, c2) -> { |
| String f1 = c1.getLastName(); |
| String f2 = c2.getLastName(); |
| int r1; |
| if (f1 == null) { |
| r1 = f2 == null ? 0 : -1; |
| } else { |
| r1 = f2 == null ? 1 : f1.compareTo(f2); |
| } |
| if (r1 != 0) { |
| return r1; |
| } |
| int r2 = c1.getFirstName().compareTo(c2.getFirstName()); |
| if (r2 != 0) { |
| return r2; |
| } |
| return Integer.compare(c1.getAge(), c2.getAge()); |
| }); |
| System.out.println("Celebrities:"); |
| celebrities.forEach(System.out::println); |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>When we run this example, the output looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <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> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="java">import java.util.Arrays; // Java |
| import java.util.List; |
| |
| import static java.util.Comparator.comparing; |
| import static java.util.Comparator.naturalOrder; |
| import static java.util.Comparator.nullsFirst; |
| |
| public class Main { |
| public static void main(String[] args) { |
| List<Celebrity> celebrities = Arrays.asList( |
| new Celebrity("Cher", "Wang", 63), |
| new Celebrity("Cher", "Lloyd", 28), |
| new Celebrity("Alex", "Lloyd", 47), |
| new Celebrity("Alex", "Lloyd", 37), |
| new Celebrity("Cher", 76)); |
| celebrities.sort(comparing(Celebrity::getLastName, nullsFirst(naturalOrder())). |
| thenComparing(Celebrity::getFirstName).thenComparing(Celebrity::getAge)); |
| System.out.println("Celebrities:"); |
| celebrities.forEach(System.out::println); |
| } |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_the_groovy_comparator_story">The Groovy comparator story</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <p><span class="image right"><img src="img/groovy_logo.png" alt="Groovy logo" width="180" height="90"></span> |
| 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> |
| </div> |
| <div class="paragraph"> |
| <p>First off, let’s create a Groovy <code>Celebrity</code> record:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">@Sortable(includes = 'last,first,age') |
| @ToString(ignoreNulls = true, includeNames = true) |
| record Celebrity(String first, String last = null, int age) {}</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>And create our list of celebrities:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">var celebrities = [ |
| new Celebrity("Cher", "Wang", 63), |
| new Celebrity("Cher", "Lloyd", 28), |
| new Celebrity("Alex", "Lloyd", 47), |
| new Celebrity("Alex", "Lloyd", 37), |
| new Celebrity(first: "Cher", age: 76) |
| ]</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="paragraph"> |
| <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:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">celebrities.sort { c1, c2 -> |
| c1.last <=> c2.last ?: c1.first <=> c2.first ?: c1.age <=> c2.age |
| } |
| println 'Celebrities:\n' + celebrities.join('\n')</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>And the output looks like this:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <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> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="paragraph"> |
| <p>We can alternatively, make use of the "new in JDK8" methods mentioned earlier:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">celebrities.sort(comparing(Celebrity::last, nullsFirst(naturalOrder())). |
| thenComparing(c -> c.first).thenComparing(c -> c.age))</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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 <em>transform</em> 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 a <code>excludes</code> annotation attribute and just mention that properties we |
| don’t want included.</p> |
| </div> |
| <div class="paragraph"> |
| <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:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">celebrities.sort()</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">celebrities.sort(Celebrity.comparatorByAge())</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Which gives this output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <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> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">var celebritiesByFirst = celebrities.toSorted(Celebrity.comparatorByFirst())</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Which, if output in a similar way to previous examples, gives:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <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> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <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> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_mixing_in_some_language_integrated_queries">Mixing in some language integrated queries</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <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> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">println GQ { |
| from c in celebrities |
| select c.first, c.last, c.age |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Which has this output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>+-------+-------+-----+ |
| | first | last | age | |
| +-------+-------+-----+ |
| | Cher | | 76 | |
| | Alex | Lloyd | 37 | |
| | Alex | Lloyd | 47 | |
| | Cher | Lloyd | 28 | |
| | Cher | Wang | 63 | |
| +-------+-------+-----+</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>In this case, it’s using the natural ordering which <code>@Sortable</code> gives us.</p> |
| </div> |
| <div class="paragraph"> |
| <p>Or we can sort by age:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">println GQ { |
| from c in celebrities |
| orderby c.age |
| select c.first, c.last, c.age |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Which has this output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>+-------+-------+-----+ |
| | first | last | age | |
| +-------+-------+-----+ |
| | Cher | Lloyd | 28 | |
| | Alex | Lloyd | 37 | |
| | Alex | Lloyd | 47 | |
| | Cher | Wang | 63 | |
| | Cher | | 76 | |
| +-------+-------+-----+</pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Or we can sort by last name descending and then age:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre class="prettyprint highlight"><code data-lang="groovy">println GQ { |
| from c in celebrities |
| orderby c.last in desc, c.age |
| select c.first, c.last, c.age |
| }</code></pre> |
| </div> |
| </div> |
| <div class="paragraph"> |
| <p>Which has this output:</p> |
| </div> |
| <div class="listingblock"> |
| <div class="content"> |
| <pre>+-------+-------+-----+ |
| | first | last | age | |
| +-------+-------+-----+ |
| | Cher | Wang | 63 | |
| | Cher | Lloyd | 28 | |
| | Alex | Lloyd | 37 | |
| | Alex | Lloyd | 47 | |
| | Cher | | 76 | |
| +-------+-------+-----+</pre> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="sect1"> |
| <h2 id="_conclusion">Conclusion</h2> |
| <div class="sectionbody"> |
| <div class="paragraph"> |
| <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> |
| </div> |
| </div> |
| </div></div></div></div></div><footer id='footer'> |
| <div class='row'> |
| <div class='colset-3-footer'> |
| <div class='col-1'> |
| <h1>Groovy</h1><ul> |
| <li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li> |
| </ul> |
| </div><div class='col-2'> |
| <h1>About</h1><ul> |
| <li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li> |
| </ul> |
| </div><div class='col-3'> |
| <h1>Socialize</h1><ul> |
| <li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li> |
| </ul> |
| </div><div class='col-right'> |
| <p> |
| The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community. |
| </p><div text-align='right'> |
| <img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/> |
| </div><p>Apache® and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p> |
| </div> |
| </div><div class='clearfix'>© 2003-2023 the Apache Groovy project — Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div> |
| </div> |
| </footer></div> |
| </div> |
| </div> |
| </div> |
| </div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-257558-10', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body></html> |