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,
- We can use Comparator.comparing() method for 1st level sorting which will return Comparator
- Then we can invoke thenComparing() method for 2nd level sorting on the returned Comparator
Overloaded thenComparing() method :
There are 3 overloaded thenComparing() method
- We are going to discuss 1st variant in this section
- 2nd variant in the last section
- for 3rd variant check this article
1. Comparator.thenComparing() method :
- This is the 1st variant which returns a lexicographic-order comparator with a function that extracts a
Comparable
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
- U is the the type of the
- 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:
- https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
- https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#thenComparing-java.util.function.Function-
- https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#thenComparing-java.util.Comparator-
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
- https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html
Happy Coding !!
Happy Learning !!