Java 8 – thenComparing() method for custom/reverse sorting

In this article, we will discuss how to sort list of Objects on multiple field/parameters using static method Comparator.comparing() and default method thenComparing() for custom/reverse sorting

2-level attribute sorting of an Object :

If our requirement is to sort list of objects on 2 different attributes of an Object like Product or Customer in custom sorting order then,

  1. We can use Comparator.comparing() method for 1st level custom/reverse sorting which will return Comparator
  2. Then we can invoke thenComparing() method for 2nd level custom/reverse sorting on the returned Comparator

Overloaded thenComparing() method :

There are 3 overloaded thenComparing() method

1. Comparator.thenComparing() method :

  • This is the 3rd variant which returns a lexicographic-order comparator with a function that extracts a key to be compared with the given Comparator
  • Method signature :-
    • default <U> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)
  • Where
    • T is the type of element to be compared
    • keyExtractor is the function used to extract the sort key
    • U is the type of sort key
    • keyComparator is the Comparator used to compare the sort key
  • Exception :- Throws NullPointerException, if the argument is null
  • Check Java doc

2. Comparator.thenComparing() examples :

  • Product class is defined with 4 attributes namely id, name, quantity and their price
  • Along with 4 attributes, parameterized constructor, getters/setters and toString() method is defined – removed for brevity

Product.java

package net.bench.resources.comparator.thencomparing.custom;

public class Product {

	// member variables
	private int id;
	private String name;
	private long quantity;
	private double price;

	// 4-arg parameterized constructor

	// getters and setters

	// toString() method
}

2.1 Sort Product list first by Name in ascending-order & then by Id by descending-order

  • A list contains Product information for 5 as per insertion-order with some product names are same
  • 2-level attribute Sorting :-
    • First, we are going to sort the Product list according to alphabetial order of its name
    • Second, we are going to compare their Ids and sort in descending-order, if name of the Products are same for 2nd level sorting

SortProductListByNameAscAndThenByIdDesc.java

package net.bench.resources.comparator.thencomparing.custom;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortProductListByNameAscAndThenByIdDesc {

	// List of Products
	private static List<Product> getProductList() {

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(104, "Rice", 208L, 164.75),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(105, "Rice", 303L, 45.50)
				);
	}

	public static void main(String[] args) {

		// 1. get Product list
		List<Product> products = getProductList();


		// 1.1 print to console
		System.out.println("Original Product list as per Insertion-order :-\n");
		products.forEach(System.out::println); // iterate/printing



		// 2. Sort Product list first by Name in ASC & then by Id in DESC
		System.out.println("\n\nSort Product list first by Name in ASC"
				+ " & then by Id in DESC :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream() // 1. get sequential stream
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting ASC
				.thenComparing(
						Product::getId, // id sorting
						(int1, int2) -> int2-int1 // reverse
						)
				)
		.forEach(System.out::println); // 3. iterate/printing
	}
}

Output:

Original Product list as per Insertion-order :-

Product [name=Wheat, id=101, quantity=1089, price=36.89]
Product [name=Rice, id=104, quantity=208, price=164.75]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Wheat, id=102, quantity=502, price=58.19]
Product [name=Rice, id=105, quantity=303, price=45.5]


Sort Product list first by Name in ASC & then by Id in DESC :-

Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Rice, id=105, quantity=303, price=45.5]
Product [name=Rice, id=104, quantity=208, price=164.75]
Product [name=Wheat, id=102, quantity=502, price=58.19]
Product [name=Wheat, id=101, quantity=1089, price=36.89]

2.2 Sort Product list first by Name in ascending-order & then by Quantity in descending-order

  • A list contains Product information for 5 as per insertion-order with some product names are same
  • 2-level attribute Sorting :-
    • First, we are going to sort the Product list according to alphabetial order of its name
    • Second, we are going to compare their quantities and sort in descending-order, if name of the Products are same for 2nd level sorting

SortProductListByNameAscAndThenByQuantityDesc.java

package net.bench.resources.comparator.thencomparing.custom;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortProductListByNameAscAndThenByQuantityDesc {

	// List of Products
	private static List<Product> getProductList() {

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(104, "Rice", 208L, 164.75),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(105, "Rice", 303L, 45.50)
				);
	}

	public static void main(String[] args) {

		// 1. get Product list
		List<Product> products = getProductList();


		// 1.1 print to console
		System.out.println("Original Product list as per Insertion-order :-\n");
		products.forEach(System.out::println); // iterating/printing



		// 2. Sort Product list first by Name in ASC & then by Quantity in DESC
		System.out.println("\n\nSort Product list first by Name in ASC"
				+ " & then by Quantity in DESC :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream()
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting ASC
				.thenComparing(
						Product::getQuantity, // quantity sorting
						(lng1, lng2) -> Long.compare(lng2, lng1) // reverse
						)
				)
		.forEach(product -> System.out.println(
				"Product [name=" + product.getName() 
				+ ", quantity=" + product.getQuantity()
				+ ", id=" + product.getId()
				+ ", price=" + product.getPrice() 
				+ "]"
				)); // customized printing
	}
}

Output:

Original Product list as per Insertion-order :-

Product [name=Wheat, id=101, quantity=1089, price=36.89]
Product [name=Rice, id=104, quantity=208, price=164.75]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Wheat, id=102, quantity=502, price=58.19]
Product [name=Rice, id=105, quantity=303, price=45.5]


Sort Product list first by Name & then by Quantity :-

Product [name=Lentils, quantity=803, id=103, price=102.45]
Product [name=Rice, quantity=303, id=105, price=45.5]
Product [name=Rice, quantity=208, id=104, price=164.75]
Product [name=Wheat, quantity=1089, id=101, price=36.89]
Product [name=Wheat, quantity=502, id=102, price=58.19]

2.3 Sort Product list first by Name in descending-order & then by Price in descending-order

  • A list contains Product information for 5 as per insertion-order with some product names are same
  • 2-level attribute Sorting :-
    • First, we are going to sort the Product list according to reverse alphabetial order of its name
    • Second, we are going to compare their prices and sort in descending-order, if name of the Products are same for 2nd level sorting

SortProductListByNameDescAndThenByPriceDesc.java

package net.bench.resources.comparator.thencomparing.custom;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortProductListByNameDescAndThenByPriceDesc {

	// List of Products
	private static List<Product> getProductList() {

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(104, "Rice", 208L, 164.75),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(105, "Rice", 303L, 45.50)
				);
	}

	public static void main(String[] args) {

		// 1. get Product list
		List<Product> products = getProductList();


		// 1.1 print to console
		System.out.println("Original Product list as per Insertion-order :-\n");
		products.forEach(System.out::println); // iterating/printing



		// 2. Sort Product list first by Name in DESC & then by Price in DESC
		System.out.println("\n\nSort Product list first by Name in DESC"
				+ " & then by Price in DESC :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream()
		.sorted(
				Comparator.comparing(
						Product::getName, // name sorting
						(str1, str2) -> str2.compareTo(str1) // reverse
						) // 1. 
				.thenComparing(
						Product::getPrice, // price sorting
						(dbl1, dbl2) -> Double.compare(dbl2, dbl1) // reverse
						)
				)
		.forEach(product -> System.out.println(
				"Product [name=" + product.getName() 
				+ ", price=" + product.getPrice()
				+ ", id=" + product.getId()
				+ ", quantity=" + product.getQuantity()
				+ "]"
				)); // customized printing
	}
}

Output:

Original Product list as per Insertion-order :-

Product [name=Wheat, id=101, quantity=1089, price=36.89]
Product [name=Rice, id=104, quantity=208, price=164.75]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Wheat, id=102, quantity=502, price=58.19]
Product [name=Rice, id=105, quantity=303, price=45.5]


Sort Product list first by Name in DESC & then by Price in DESC :-

Product [name=Wheat, price=58.19, id=102, quantity=502]
Product [name=Wheat, price=36.89, id=101, quantity=1089]
Product [name=Rice, price=164.75, id=104, quantity=208]
Product [name=Rice, price=45.5, id=105, quantity=303]
Product [name=Lentils, price=102.45, id=103, quantity=803]

2.4 Throws NullPointerException if argument is NULL

  • If the input argument to thenComparing() method is null then it throws NullPointerException as shown in the below illustration
  • Check How to sort List and Arrays with null values to handle list with null values while sorting

SortProductListWithNullPresent.java

package net.bench.resources.comparator.thencomparing.custom;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortProductListWithNullPresent {

	// List of Products
	private static List<Product> getProductList() {

		return Arrays.asList(
				null,
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(104, "Rice", 208L, 164.75),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(105, "Rice", 303L, 45.50)
				);
	}

	public static void main(String[] args) {

		// 1. get Product list
		List<Product> products = getProductList();


		// 1.1 print to console
		System.out.println("Original Product list as per Insertion-order :-\n");
		products.forEach(System.out::println); // iterate/printing



		// 2. Sort Product list first by Name in ASC & then by Id in DESC
		System.out.println("\n\nSort Product list first by Name in ASC"
				+ " & then by Id in DESC :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream() // 1. get sequential stream
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting ASC
				.thenComparing(
						Product::getId, // id sorting
						(int1, int2) -> int2-int1 // reverse
						)
				)
		.forEach(System.out::println); // 3. iterate/printing
	}
}

Output:

Original Product list as per Insertion-order :-

null
Product [name=Wheat, id=101, quantity=1089, price=36.89]
Product [name=Rice, id=104, quantity=208, price=164.75]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Wheat, id=102, quantity=502, price=58.19]
Product [name=Rice, id=105, quantity=303, price=45.5]


Sort Product list first by Name in ASC & then by Id in DESC :-

Exception in thread "main" java.lang.NullPointerException
	at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
	at java.util.Comparator.lambda$thenComparing$36697e65$1(Comparator.java:216)
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
	at java.util.TimSort.sort(TimSort.java:220)
	at java.util.Arrays.sort(Arrays.java:1512)
	at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:348)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.util.stream.ForEachOps$ForEachOp$OfRef
.evaluateSequential(ForEachOps.java:174)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
	at net.bench.resources.comparator.thencomparing.custom.SortProductListWithNullPresent
.main(SortProductListWithNullPresent.java:48)

References:

Happy Coding !!
Happy Learning !!

Java 8 – Comparator.thenComparingInt() method
Java 8 – Comparator.thenComparing() method