Start learning free

LESSON 263 OF 671 · RECORDS, SORTING & BUILT-INS

Custom Sorting in Java: List.sort with a Lambda

Lesson Read the full explanation

TL ; DR:

To sort by your own rule, give sort a lambda. It gets two items, a and b. A negative result puts a first.


List<Integer> nums = new ArrayList<>(List.of(25, 13, 5, 41, 33));

nums.sort((a, b) -> {
    if (a % 10 != b % 10) {
        return a % 10 - b % 10;
    }

    return a - b;
});
// [41, 13, 33, 5, 25]

List<Integer> scores = new ArrayList<>(List.of(70, 95, 82));

scores.sort((a, b) -> b - a);
// [95, 82, 70]


Full lesson:

You met Collections.sort in the Shopping Cart project. It sorts from small to large, or from A to Z. Sometimes you need another order. The shortest word may have to come first, or the highest score.

For that, give the list's sort method a lambda. A lambda is a small function written right where you need it. It has no name. The lambda (a, b) -> a - b gets two values, a and b, and returns a - b.

The sort method calls it on two items at a time. The result says which one goes first.

  • A negative number puts a first.
  • A positive number puts b first.
  • Zero means they are equal.

Some rules you will use often:

  • Low to high: (a, b) -> a - b.
  • High to low: (a, b) -> b - a.
  • Shortest first: (a, b) -> a.length() - b.length().

For plain numbers or text, list.sort(Comparator.reverseOrder()) also sorts from high to low.

For two rules, the lambda needs a body in braces. Check the first rule. Use the second one only on a tie, as the if above does.

Above, the first rule compares the last digits, a % 10 and b % 10. Numbers with the same last digit then go from small to large.

You met compareTo in Top Scorer. It gives a negative number when a comes first from A to Z.

The sort method changes the list itself. To keep the original, sort a copy made with new ArrayList<>(words).


Your task

A pet shop sells toys. Their prices are in prices. Sort prices from high to low.

The shop's pets are in names. Sort names by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 4
    Ctrl + Enter to check

    JAVA · RECORDS, SORTING & BUILT-INS

    Custom Sorting in Java, explained

    Sorting by your own rule

    Collections.sort puts numbers from small to large and text from A to Z. Often you need another order: the highest score first, the shortest word first, or names ordered by their last letter. For that, give the list's sort method a lambda.

    A lambda is a small function written right where you need it, with no name. The lambda (a, b) -> a - b gets two values, a and b, and returns a - b. The sort method calls it on two items at a time, and the number it returns says which one goes first:

    • A negative number puts a first.
    • A positive number puts b first.
    • Zero means they are equal.

    Rules you will use often:

    • Low to high: (a, b) -> a - b
    • High to low: (a, b) -> b - a
    • Shortest first: (a, b) -> a.length() - b.length()

    For plain numbers or text, list.sort(Comparator.reverseOrder()) also sorts from high to low.

    Two rules

    When one rule isn't enough, the lambda needs a body in braces. Check the first rule. Use the second one only on a tie.

    List<Integer> nums = new ArrayList<>(List.of(25, 13, 5, 41, 33));
    
    nums.sort((a, b) -> {
        if (a % 10 != b % 10) {
            return a % 10 - b % 10;
        }
        return a - b;
    });
    System.out.println(nums);
    
    [41, 13, 33, 5, 25]
    

    The first rule compares the last digits, a % 10 and b % 10. Only when they are the same does the lambda reach return a - b, which puts the smaller number first.

    Comparing text and characters

    Strings can't be subtracted, so use compareTo. x.compareTo(y) is negative when x comes before y from A to Z, positive when it comes after, and 0 when they are equal.

    A char is stored as a number, so two chars can be subtracted. 'x' - 'a' is 23, and lastA - lastB is negative when lastA comes earlier in the alphabet. The last letter of a string s is s.charAt(s.length() - 1).

    Copies and fixed lists

    The sort method changes the list itself. To keep the original, sort a copy made with new ArrayList<>(words):

    List<String> words = new ArrayList<>(List.of("pear", "fig", "banana", "kiwi"));
    List<String> copy = new ArrayList<>(words);
    copy.sort((a, b) -> a.length() - b.length());
    System.out.println(copy + " " + words);
    
    [fig, pear, kiwi, banana] [pear, fig, banana, kiwi]
    

    Items that compare as equal keep their original order, because the sort is stable. Note also that List.of(...) alone makes a list that can't change, and calling sort on it throws UnsupportedOperationException. That is why the lesson wraps it in new ArrayList<>(...).

    The lesson's task

    A pet shop has prices and names. Sort prices from high to low, and sort names by their last letter, with names that share a last letter going from A to Z. The loops that print both are already in the starter code.

    The prices need one rule, b - a. The names need two. Inside the lambda, keep the last letter of each name in a char. If the two chars differ, return lastA - lastB. Otherwise return a.compareTo(b), which puts Max before Rex.

    class Solution {
        public void mainFunction() {
            List<Integer> prices = new ArrayList<>(List.of(12, 5, 30, 8));
            List<String> names = new ArrayList<>(List.of("Rex", "Luna", "Bella", "Max", "Bo", "Coco"));
    
            prices.sort((a, b) -> b - a);
            for (int price : prices) {
                System.out.println(price);
            }
    
            names.sort((a, b) -> {
                char lastA = a.charAt(a.length() - 1);
                char lastB = b.charAt(b.length() - 1);
                if (lastA != lastB) {
                    return lastA - lastB;
                }
                return a.compareTo(b);
            });
            for (String name : names) {
                System.out.println(name);
            }
        }
    }
    

    It prints:

    30
    12
    8
    5
    Bella
    Luna
    Bo
    Coco
    Max
    Rex
    

    Common mistakes

    • Swapping a and b. a - b sorts low to high, b - a high to low.
    • Dropping the second rule. With only the last-letter rule, ties keep their original order and the names come out as [Luna, Bella, Bo, Coco, Rex, Max]. Rex precedes Max only because it was first in the list.
    • Subtracting huge numbers. b - a is fine for small prices, but int subtraction can overflow. With Integer.MIN_VALUE and 5, (a, b) -> b - a leaves the list as [-2147483648, 5], while Integer.compare(b, a) gives the right [5, -2147483648]. For values that can be that large, use Integer.compare.
    • Sorting a list made with List.of. It can't change, so sort throws an exception.

    Key takeaways

    • Give list.sort a lambda of a and b; a negative result puts a first.
    • Use a - b for low to high and b - a for high to low.
    • For two rules, return the first rule's result when it isn't a tie, and fall back to the second rule otherwise.
    • Compare strings with compareTo and chars by subtracting them.
    • sort changes the list; copy it first to keep the original.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free