Java 8 – Comparator.thenComparing() method

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

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 then,

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

Overloaded thenComparing() method :

There are 3 overloaded thenComparing() method

1. Comparator.thenComparing() method :

  • This is the 1st variant which returns a lexicographic-order comparator with a function that extractsComparable sort key
  • Method signature :-
    • default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor)
  • Where
    • U is the the type of the Comparable sort key
    • keyExtractor is the function used to extract the Comparable 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;

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 & then by Id

  • 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, if name of the Products are same for 2nd level sorting

SortProductListByNameAndThenById.java

package net.bench.resources.comparator.thencomparing;

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

public class SortProductListByNameAndThenById {

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

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(104, "Oil", 208L, 164.75),
				new Product(105, "Oil", 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 & then by Id
		System.out.println("\n\nSort Product list first by Name & then by Id :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream() // 1. get sequential stream
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting
				.thenComparing(Product::getId) // 2. Id sorting
				)
		.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=Wheat, id=102, quantity=502, price=58.19]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Oil, id=104, quantity=208, price=164.75]
Product [name=Oil, id=105, quantity=303, price=45.5]


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

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

2.2 Sort Product list first by Name & then by Quantity

  • 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, if name of the Products are same for 2nd level sorting

SortProductListByNameAndThenByQuantity.java

package net.bench.resources.comparator.thencomparing;

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

public class SortProductListByNameAndThenByQuantity {

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

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(104, "Oil", 208L, 164.75),
				new Product(105, "Oil", 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 & then by Quantity
		System.out.println("\n\nSort Product list "
				+ "first by Name & then by Quantity :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream()
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting
				.thenComparing(Product::getQuantity) // 2. quantity sorting
				)
		.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=Wheat, id=102, quantity=502, price=58.19]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Oil, id=104, quantity=208, price=164.75]
Product [name=Oil, 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=Oil, quantity=208, id=104, price=164.75]
Product [name=Oil, quantity=303, id=105, price=45.5]
Product [name=Wheat, quantity=502, id=102, price=58.19]
Product [name=Wheat, quantity=1089, id=101, price=36.89]

2.3 Sort Product list first by Name & then by Price

  • 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 prices, if name of the Products are same for 2nd level sorting

SortProductListByNameAndThenByPrice.java

package net.bench.resources.comparator.thencomparing;

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

public class SortProductListByNameAndThenByPrice {

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

		return Arrays.asList(
				new Product(101, "Wheat", 1089L, 36.89),
				new Product(102, "Wheat", 502L, 58.19),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(104, "Oil", 208L, 164.75),
				new Product(105, "Oil", 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 & then by Price
		System.out.println("\n\nSort Product list first by Name & then by Price :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream()
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting
				.thenComparing(Product::getPrice) // 2. price sorting
				)
		.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=Wheat, id=102, quantity=502, price=58.19]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Oil, id=104, quantity=208, price=164.75]
Product [name=Oil, id=105, quantity=303, price=45.5]


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

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

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;

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(102, "Wheat", 502L, 58.19),
				new Product(103, "Lentils", 803L, 102.45),
				new Product(104, "Oil", 208L, 164.75),
				new Product(105, "Oil", 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 with null value
		System.out.println("\n\nSort Product list :-\n");


		// 2.1 sorting/iterating/printing
		products
		.stream() // 1. get sequential stream
		.sorted(
				Comparator.comparing(Product::getName) // 1. name sorting
				.thenComparing(Product::getId) // 2. Id sorting
				) // 2. alphabetical name
		.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=Wheat, id=102, quantity=502, price=58.19]
Product [name=Lentils, id=103, quantity=803, price=102.45]
Product [name=Oil, id=104, quantity=208, price=164.75]
Product [name=Oil, id=105, quantity=303, price=45.5]


Sort Product list :-

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.SortProductListWithNullPresent
.main(SortProductListWithNullPresent.java:45)

3. Comparator.thenComparing() method :

  • This is the 2nd variant which returns a lexicographic-order comparator with another comparator
  • If this Comparator considers two elements equal, i.e.; compare(a, b) == 0, other is used to determine the order
  • The returned comparator is serializable if the specified function is also serializable
  • Method signature :-
    • default Comparator<T> thenComparing(Comparator<? super T> cmp)
  • Where
    • T is the type of element to be compared
    • cmp is the comparator to be used when this comparator compares two objects that are equal
  • Exception :- Throws NullPointerException, if the argument is null
  • Check Java doc

3.1 Sort List of String elements according to their length

  • In below illustration, we are sorting String elements according to increasing length of String and then ignoring cases for 2nd level sorting

SortStringElementsAccordingToLength.java

package net.bench.resources.comparator.thencomparing;

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

public class SortStringElementsIgnoringCases {

	public static void main(String[] args) {

		// 1. list of Strings
		List<String> names = Arrays.asList(
				"rafael",
				"messi",
				"argus",
				"zen",
				"Martin",
				"Roger",
				"Ben"
				);


		// 1.1 original String elements - insertion-order
		System.out.println("Original String elemetns"
				+ " as per Insertion-order :- \n");
		names.forEach(System.out::println);



		// 2 print to console
		System.out.println("\n\nSorted String list "
				+ "according to length, ignoring cases :- \n");


		// 2.1 sorting/iterate/printing
		names
		.stream()
		.sorted(
				Comparator.comparing(String::length) // 1. length-wise sorting
				.thenComparing(String.CASE_INSENSITIVE_ORDER) // 2. ignore cases
				)
		.forEach(System.out::println);
	}
}

Output:

Original String elemetns as per Insertion-order :- 

rafael
messi
argus
zen
Martin
Roger
Ben


Sorted String list according to length, ignoring cases :- 

Ben
zen
argus
messi
Roger
Martin
rafael

3.2 Throws NullPointerException if argument is NULL

  • If the input argument to thenComparing() method is null then it throws NullPointerException

SortStringElementsWithNullPresent.java

package net.bench.resources.comparator.thencomparing;

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

public class SortStringElementsWithNullPresent {

	public static void main(String[] args) {

		// 1. list of Strings
		List<String> names = Arrays.asList(
				null,
				"rafael",
				"messi",
				"argus",
				"zen",
				"Martin",
				"Roger",
				"Ben"
				);


		// 1.1 original String elements - insertion-order
		System.out.println("Original String elemetns"
				+ " as per Insertion-order :- \n");
		names.forEach(System.out::println);



		// 2 print to console
		System.out.println("\n\nSorted String list "
				+ "according to length, ignoring cases :- \n");


		// 2.1 sorting/iterate/printing
		names
		.stream()
		.sorted(
				Comparator.comparing(String::length) // 1. length-wise sorting
				.thenComparing(String.CASE_INSENSITIVE_ORDER) // 2. ignore cases
				)
		.forEach(System.out::println);
	}
}

Output:

Original String elemetns as per Insertion-order :- 

null
rafael
messi
argus
zen
Martin
Roger
Ben


Sorted String list according to length, ignoring cases :- 

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.SortStringElementsWithNullPresent
.main(SortStringElementsWithNullPresent.java:43)

References:

Happy Coding !!
Happy Learning !!

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