Data Setup

@Getter
@AllArgsConstructor
@ToString
class Person {
    String name;
    String city;
    int age;
}
  • Initial Data
int[] arr = {6, 5, 7, 1, 4, 9, 2};
int[][] arr2 = {{6, 5, 7}, {1, 4, 2}, {4, 2, 1}};
 
// Arrays.asList() creates fixed size list
List<Person> list = Arrays.asList(new Person("John", "Boston", 21),
                new Person("Mary", "Boston", 29),
                new Person("Anthony", "Boston", 35),
                new Person("Monica", "Amsterdam", 19),
                new Person("Seth", "São Paulo", 38);
 
Person[] peopleArray = {
    new Person("John", "Boston", 21),
    new Person("Mary", "Boston", 29),
    new Person("Anthony", "Boston", 35),
    new Person("Monica", "Amsterdam", 19),
    new Person("Seth", "São Paulo", 38)
};

Sorting Arrays

Arrays.sort(arr);
Arrays.sort(arr, fromIndex, toIndex);
 
// reverse sorting example
// since custom Comparator not supported
private void reverseArray(int[] arr) {
    int N = arr.length;
    int i = 0;
    int j = N - 1;
    while (i < j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
        i++;
        j--;
    }
}
 
// Alternative: Box, reverse sort, unbox
int[] reversed = Arrays.stream(arr)
                       .boxed()
                       .sorted(Collections.reverseOrder())
                       .mapToInt(Integer::intValue)
                       .toArray();
 
// printing array
System.out.println(Arrays.toString(arr));
  • Object Array
Arrays.sort(T[]);
Arrays.sort(T[], Comparator);
Arrays.sort(T[], fromIndex, toIndex, Comparator);
 
// Examples
Arrays.sort(peopleArray); // need Person class with Comparable implementation
Arrays.sort(peopleArray, Comparator.comparing(Person::getName));
Arrays.sort(peopleArray, 0, 4, Comparator.comparing(Person::getName));
 
// print object array
// need @ToString on Person class
System.out.println(Arrays.toString(peopleArray));
  • 2D Primitive Array
    • Since int[][] is an array of int[] which in fact is an object
    • Object Array sorting can be used
Arrays.sort(arr2, (a, b) -> Integer.compare(a[0], b[0]));
 
// print nested arrays
System.out.println(Arrays.deepToString(arr2));

Sorting Collections

  • Using Collections Utility
Collections.sort(List<>);
Collections.sort(List<>, Comparator);
 
// print
System.out.println(list);
  • Using list.sort() instance method
// requires comparator in argument
list.sort(Comparator);
 
// Pass null to trigger default natural ordering
list.sort(null); 

Wrapper Comparison Utilities

  • Integral Types
    • x - y can fail with extreme values
    • Integer.compare(int x, int y)
    • Long.compare(long x, long y)
    • Short.compare(short x, short y)
    • Byte.compare(byte x, byte y)
    • Character.compare(char x, char y)
  • Floating Types
    • x - y can fail to handle NaN and distinguish positive/negative/zero
    • Float.compare(float x, float y)
    • Double.compare(double x, double y)
  • Logical Type
    • Boolean.compare(boolean x, boolean y)

Comparable

  • package: java.lang
  • Interface: Comparable<T>
    • int compareTo(T o)
  • Return: negative (), zero (), positive ()
  • Defines the natural ordering for objects of a class implementing it
  • String, primitive wrappers (e.g., Integer, Double), and Date/Time classes have implicit Comparable behavior defined
  • Any Comparator utility that does not define a custom comparison strategy implicitly relies on Comparable
class Person implements Comparable<Person> {
    String name;
    String city;
 
    @Override
    public int compareTo(Person o) {
        return this.name.compareTo(o.getName());
    }
}
  • Examples:
Collections.sort(list); // internally naturalOrder
// or 
list.sort(Comparator.naturalOrder());
// or
list.stream().sorted().toList(); // internally naturalOrder
// or
list.stream().sorted(Comparator.naturalOrder()).toList();
 
System.out.println(list);

Comparator

  • package: java.util
  • Functional Interface: Comparator<T>
    • int compare(T o1, T o2)
  • An object that implements the Comparator interface is called a comparator
  • Return: negative (), zero (), positive ()

Syntax

  • class syntax
class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getName().compareTo(p2.getName());
    }
}
 
Comparator<Person> comparator = new NameComparator();
  • lambda syntax
Comparator<Person> comparator = (p1, p2) -> {
    return p1.getName().compareTo(p2.getName());
};
  • comparator utility
Comparator.comparing(Person::getName);

Comparator Utilities

  • These utilities return Comparator object

Static Factory methods

  • These are static factory methods of Comparator interface
    • These are used to start comparator chain
  • General Utilities
    • Comparator.naturalOrder()
    • Comparator.reverseOrder() also same as Collections.reverseOrder()
  • Handling nulls
    • Comparator.nullsFirst(Comparator)
    • Comparator.nullsLast(Comparator)
  • Custom Mapping
    • Comparator.comparing(Function)
    • Comparator.comparing(Function, Comparator)
      • Comparator.comparingInt(ToIntFunction)
      • Comparator.comparingLong(ToLongFunction)
      • Comparator.comparingDouble(ToDoubleFunction)
  • For primitives use specialized classes
// Avoid: int primitive age is boxed internally
list.stream()
    .sorted(Comparator.comparing(p -> p.getAge()))
    .forEach(System.out::println);
 
// Prefer: Directly compares raw int primitive age
list.stream()
    .sorted(Comparator.comparingInt(Person::getAge))
    .forEach(System.out::println);

Instance Methods

  • These are instance methods of Comparator
    • defined as default methods of Comparator interface
    • These can be used to chain after static factory methods or another object methods
    • Example: comparator.thenComparing().thenComparing().reversed()
  • Reverse
    • comparator.reversed()
  • Custom Mapping
    • comparator.thenComparing(Comparator)
    • comparator.thenComparing(Function)
    • comparator.thenComparing(Function, Comparator)
      • comparator.thenComparingInt(ToIntFunction)
      • comparator.thenComparingLong(ToLongFunction)
      • comparator.thenComparingDouble(ToDoubleFunction)

Stream Examples

  • Reverse
list.stream()
    .sorted(Comparator.reverseOrder())
    .forEach(System.out::println);
  • Custom comparator
list.stream()
    .sorted(Comparator.comparing(Person::getCity))
    .map(Person::getName)
    .forEach(System.out::println);
  • Multiple comparing
list.stream()
    .sorted(Comparator.comparing(Person::getCity)
        .thenComparing(Person::getName))
    .map(Person::getName)
    .forEach(System.out::println);
  • Ascending then descending
list.stream()
    .sorted(Comparator.comparing(Person::getCity)
                .thenComparing(Comparator.comparing(Person::getName)
                .reversed()))
    .map(Person::getName)
    .forEach(System.out::println);
 
// OR
list.stream()
    .sorted(Comparator.comparing(Person::getCity)
                .thenComparing(Person::getName, Comparator.reverseOrder()))
    .forEach(System.out::println);
  • Sorting with null objects
// assumes one of the person is null in list
list.stream()
    .sorted(Comparator.nullsFirst(Comparator.comparing(Person::getCity)))
    .forEach(System.out::println);
  • Sorting with null properties
// assumes one of the person is null in list
// and person.getCity() is null for some object
list.stream()
    .sorted(Comparator.nullsFirst( // null-safe
        Comparator.comparing(
            Person::getCity, 
            Comparator.nullsFirst(Comparator.naturalOrder()) // null-safe
        )
    ))
    .forEach(System.out::println);