Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we'll explore various ways of sorting a list alphabetically in Java.

First, we'll start with the Collections class and then use the Comparator interface. We'll also use List's API to sort alphabetically followed by the streams and finally use TreeSet.

Additionally, we'll be expanding our examples to explore several different scenarios, including sorting lists based on a specific locale, sorting accented lists, and using RuleBasedCollator to define our custom sorting rules.

2. Sorting Using Collections Class

First, let's see how we can sort a list using the Collections class.

The Collections class provides a static, overloaded method sort that can take the list and sort it either in natural order or we can provide the custom sorting logic using a Comparator.

2.1. Sorting in Natural/Lexicographic Order

First, we'll define the input list:

private static List<String> INPUT_NAMES = Arrays.asList("john", "mike", "usmon", "ken", "harry");

Now we'll use the Collections class first to sort the list in the natural order, also known as lexicographic order:

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSorted() {
    Collections.sort(INPUT_NAMES);
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_NATURAL_ORDER);
}

where the EXPECTED_NATURAL_ORDER is:

private static List<String> EXPECTED_NATURAL_ORDER = Arrays.asList("harry", "john", "ken", "mike", "usmon");

Some important points to note here are:

  • The list is sorted in ascending order according to the natural ordering of its elements
  • We can pass any Collection to sort method whose elements are Comparable (implement Comparable interface)
  • Here, we are passing a List of String class which is a Comparable class, and hence sorting works
  • Collection class changes the state of the List object being passed to sort. Therefore we don't need to return the list

2.2. Sorting in Reverse Order

Let's see how we can sort the same list in reverse alphabetical order.

Let's use the sort method again but now provide a Comparator:

Comparator<String> reverseComparator = (first, second) -> second.compareTo(first);

Alternately we can simply use this static method from the Comparator interface:

Comparator<String> reverseComparator = Comparator.reverseOrder();

Once we have the reverse Comparator, we can simply pass it to sort:

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSortedInReverse() {
    Comparator<String> reverseComparator = Comparator.reverseOrder();
    Collections.sort(INPUT_NAMES, reverseComparator); 
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER); 
}

where the EXPECTED_REVERSE_ORDER is:

private static List<String> EXPECTED_REVERSE_ORDER = Arrays.asList("usmon", "mike", "ken", "john", "harry");

A few relevant takeaways from this are:

  • since the String class is final, we cannot extend and override the compareTo method of the Comparable interface for reverse sorting
  • we can use the Comparator interface to implement a customized sorting strategy which is sorting in descending alphabetical order
  • since Comparator is a functional interface, we can use a lambda expression

3. Custom Sorting Using Comparator Interface

Often, we have to sort a list of Strings that need some custom sorting logic. That's when we implement the Comparator interface and provide our desired sorting criteria.

3.1. Comparator to Sort List With Upper and Lower Case Strings

A typical scenario that can call for a custom sort can be a mixed list of Strings, starting with upper and lower cases.

Let's set up and test this scenario:

@Test
void givenListOfStringsWithUpperAndLowerCaseMixed_whenCustomComparator_thenListIsSortedCorrectly() {
    List<String> movieNames = Arrays.asList("amazing SpiderMan", "Godzilla", "Sing", "Minions");
    List<String> naturalSortOrder = Arrays.asList("Godzilla", "Minions", "Sing", "amazing SpiderMan");
    List<String> comparatorSortOrder = Arrays.asList("amazing SpiderMan", "Godzilla", "Minions", "Sing");

    Collections.sort(movieNames);
    assertThat(movieNames).isEqualTo(naturalSortOrder);

    Collections.sort(movieNames, Comparator.comparing(s -> s.toLowerCase()));
    assertThat(movieNames).isEqualTo(comparatorSortOrder);
}

Note here:

  • the result of the sort before Comparator will be incorrect: [“Godzilla, “Minions” “Sing”, “amazing SpiderMan”]
  • String::toLowerCase is the key extractor that extracts a Comparable sort key from a type String and returns a Comparator<String>
  • the correct result after the sort with a custom Comparator will be: [“amazing SpiderMan”, “Godzilla”, “Minions”, “Sing”]

3.2. Comparator to Sort Special Characters

Let's consider another example of a list with some names starting with a special character ‘@'.

We want them to be sorted at the end of the list, and the rest should be sorted in the natural order:

@Test
void givenListOfStringsIncludingSomeWithSpecialCharacter_whenCustomComparator_thenListIsSortedWithSpecialCharacterLast() {
    List<String> listWithSpecialCharacters = Arrays.asList("@laska", "blah", "jo", "@sk", "foo");

    List<String> sortedNaturalOrder = Arrays.asList("@laska", "@sk", "blah", "foo", "jo");
    List<String> sortedSpecialCharacterLast = Arrays.asList("blah", "foo", "jo", "@laska", "@sk");

    Collections.sort(listWithSpecialCharacters);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedNaturalOrder);

    Comparator<String> specialSignComparator = Comparator.<String, Boolean>comparing(s -> s.startsWith("@"));
    Comparator<String> specialCharacterComparator = specialSignComparator.thenComparing(Comparator.naturalOrder());

    listWithSpecialCharacters.sort(specialCharacterComparator);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedSpecialCharacterLast);
}

Finally, some key points are:

  • The output of the sort without the Comparator is: [“@alaska”, “@ask”, “blah”, “foo”, “jo”] which is not correct for our scenario
  • Like before, we have a key extractor that extracts a Comparable sort key from a type String and returns a specialCharacterLastComparator
  • We can chain the specialCharacterLastComparator do a secondary sort using thenComparing expecting another Comparator
  • Comparator.naturalOrder compares Comparable objects in the natural order.
  • final output after the Comparator in the sort is : [“blah”, “foo”, “jo”, “@alaska”, “@ask”]

4. Sorting Using Streams

Now, let's use Java 8 Streams to sort the list of String.

4.1. Sorting in Natural Order

Let's sort in the natural order first:

@Test
void givenListOfStrings_whenSortWithStreams_thenListIsSortedInNaturalOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted()
      .collect(Collectors.toList());

    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

Important, here the sorted method returns a stream of String sorted according to the natural order.

Additionally, elements of this Stream are Comparable.

4.2. Sorting in Reverse Order

Next, let's pass a Comparator to sorted, which defines the  reverse sorting strategy:

@Test
void givenListOfStrings_whenSortWithStreamsUsingComparator_thenListIsSortedInReverseOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted((element1, element2) -> element2.compareTo(element1))
      .collect(Collectors.toList());
    assertThat(sortedList).isEqualTo(EXPECTED_REVERSE_ORDER);
}

Note, here we are using the Lamda function to define the Comparator

Alternately, we can get the same Comparator:

Comparator<String> reverseOrderComparator = Comparator.reverseOrder();

Then we'll simply pass this reverseOrderComparator to sorted:

List<String> sortedList = INPUT_NAMES.stream()
  .sorted(reverseOrder)
  .collect(Collectors.toList());

5. Sorting Using TreeSet

TreeSet stores object in sorted and ascending order using a Comparable interface.

We can simply convert our unsorted list into TreeSet  and then collect it back as a List:

@Test
void givenNames_whenUsingTreeSet_thenListIsSorted() {
    SortedSet<String> sortedSet = new TreeSet<>(INPUT_NAMES);
    List<String> sortedList = new ArrayList<>(sortedSet);
    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

A constraint of this approach is that our original list shouldn't have any duplicate values that it wants to preserve in the sorted list.

6. Sorting Using sort on List

We can also sort a list using the sort method of the List:

@Test
void givenListOfStrings_whenSortOnList_thenListIsSorted() {
    INPUT_NAMES.sort(Comparator.reverseOrder());
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}

Note that the sort method sorts this list according to the order dictated by the specified Comparator.

7. Locale Sensitive List Sorting

Depending on the locale, the result of the alphabetical sorting can vary due to language rules.

Let's take an example of a List of String:

 List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

Let's sort them as normal first:

 Collections.sort(accentedStrings);

The output would be: [“cosas”, “fútbol”, “árbol”, “único”].

However, we want them to be sorted using specific language rules.

Let's create an instance of a Collator with the specific locale:

Collator esCollator = Collator.getInstance(new Locale("es"));

Note, here we created an instance of Collator using es locale.

We can then pass this Collator as a Comparator being used for sorting either on the list, Collection, or using Streams:

accentedStrings.sort((s1, s2) -> {
    return esCollator.compare(s1, s2);
});

The result of the sort would now be: [árbol, cosas, fútbol, único].

Finally, let's show this all together:

@Test
void givenListOfStringsWithAccent_whenSortWithTheCollator_thenListIsSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");
    List<String> sortedNaturalOrder = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> sortedLocaleSensitive = Arrays.asList("árbol", "cosas", "fútbol", "único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(sortedNaturalOrder);

    Collator esCollator = Collator.getInstance(new Locale("es"));

    accentedStrings.sort((s1, s2) -> {
        return esCollator.compare(s1, s2);
    });

    assertThat(accentedStrings).isEqualTo(sortedLocaleSensitive);
}

8. Sorting List With Accented Characters

Characters with accents or other adornments can be encoded in several different ways in Unicode and thus sorted differently.

We can either normalize the accented characters before sorting or can remove the accents from the characters.

Let's look into both of these ways of sorting accented lists.

8.1. Normalize Accented List and Sort

To sort accurately such a list of Strings, let's normalize accented characters using java.text.Normalizer

Let's start with a list of accented Strings:

List<String> accentedStrings = Arrays.asList("único","árbol", "cosas", "fútbol");

Next, let's define a Comparator with a Normalize function and pass it to our sort method:

Collections.sort(accentedStrings, (o1, o2) -> {
    o1 = Normalizer.normalize(o1, Normalizer.Form.NFD);
    o2 = Normalizer.normalize(o2, Normalizer.Form.NFD);
    return o1.compareTo(o2);
});

The sorted list after normalization will be: [árbol, cosas, fútbol, único]

Importantly, here we are normalizing data using the form Normalizer.Form.NFD uses Canonical decomposition for accented characters. There are a few other forms that can also be used for normalization.

8.2. Strip Accents and Sort

Let's use the StringUtils stripAccents method in the Comparator and pass it to the sort method:

@Test
void givenListOfStrinsWithAccent_whenComparatorWithNormalizer_thenListIsNormalizedAndSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

    List<String> naturalOrderSorted = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> stripAccentSorted = Arrays.asList("árbol","cosas", "fútbol","único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(naturalOrderSorted);
    Collections.sort(accentedStrings, Comparator.comparing(input -> StringUtils.stripAccents(input)));
    assertThat(accentedStrings).isEqualTostripAccentSorted); 
}

9. Sorting Using Rule-Based Collator

We can also define our custom rules to sort alphabetically using java text.RuleBasedCollator.

Here, let's define our custom sorting rule and pass it to RuleBasedCollator:

@Test
void givenListofStrings_whenUsingRuleBasedCollator_then ListIsSortedUsingRuleBasedCollator() throws ParseException {
    List<String> movieNames = Arrays.asList(
      "Godzilla","AmazingSpiderMan","Smurfs", "Minions");

    List<String> naturalOrderExpected = Arrays.asList(
      "AmazingSpiderMan", "Godzilla", "Minions", "Smurfs");
    Collections.sort(movieNames);

    List<String> rulesBasedExpected = Arrays.asList(
      "Smurfs", "Minions", "AmazingSpiderMan", "Godzilla");

    assertThat(movieNames).isEqualTo(naturalOrderExpected);

    String rule = "< s, S < m, M < a, A < g, G";

    RuleBasedCollator rulesCollator = new RuleBasedCollator(rule);
    movieNames.sort(rulesCollator);

    assertThat(movieNames).isEqualTo(rulesBasedExpected);
}

Some of the important points to consider are:

  • RuleBasedCollator maps characters to sort keys
  • the rule defined above is of form <relations> but there are other forms too in rules
  • character s is smaller than m, and M is smaller than a, where A is smaller than g as per the rule definition
  • a format exception will be thrown if the build process of the rules fails
  • RuleBasedCollator implements the Comparator interface, hence can be passed to sort

10. Conclusion

In this article, we looked into various techniques for sorting lists alphabetically in java.

First, we used Collections, then we explained the Comparator interface with some common examples.

After Comparator, we looked into the sort method of the List followed by TreeSet.

We also explored how we can handle sorting the list of String in a different locale, normalizing and sorting accented lists, as well as the use of RuleBasedCollator with custom rules

As always complete source code for this article can be found over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments