Learn Collectors: toList, toSet, toMap, toConcurrentMap, toCollection and to unmodifiable collection with 25 examples

1. Introduction

Collectors is a final class with a private constructor. I am using Azul’s OpenJDK version azul-14.0.1. It has 44 static methods are used to collect data from java.util.stream.Stream. In this article, we will look at a few methods that work with Java Collections.

I have explained Collector interface over here.

Remember, these static methods return a Collector itself. They don’t do any collecting. This responsibility is of java.util.stream.Stream.collect (java.util.stream.Collector) method of Stream interface. If you want to look at implementation, see class java.util.stream.ReferencePipeline’s method collect.

This article is about collecting data in Java Collections like List, Set, Map. To ease your understanding, I have added 26 examples for these methods. In total, we will look at 13 methods that let us add elements into Java Collections.

2. Content

Overview of methods and total methods we will see this in this article.

Collector<T, ?, List<T>> toList()

Collector<T, ?, List<T>> toUnmodifiableList()

Collector<T, ?, Set<T>> toSet()

Collector<T, ?, Set<T>> toUnmodifiableSet()

Collector<T, ?, Map<K,U>>
 toMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper)

Collector<T, ?, Map<K,U>> 
toMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction)

Collector<T, ?, M> 
toMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction,
	Supplier<M> mapFactory)

Collector<T, ?, ConcurrentMap<K,U>>
toConcurrentMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper)

Collector<T, ?, ConcurrentMap<K,U>>
toConcurrentMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction)

Collector<T, ?, M> 
toConcurrentMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction,
	Supplier<M> mapFactory)

Collector<T, ?, Map<K,U>> 
toUnmodifiableMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper)

Collector<T, ?, Map<K,U>> 
toUnmodifiableMap(
	Function<? super T, ? extends K> keyMapper,
	Function<? super T, ? extends U> valueMapper,
	BinaryOperator<U> mergeFunction)

Collector<T, ?, C> toCollection(Supplier<C> collectionFactory)

3. Collector<T, ?, List<T>> toList()

toList() method is used to collect the data from Stream into the List. As of today, the toList() method returns a Collector that uses ArrayList. That means it will collect the data into an ArrayList.

If you want to provide a custom implementation of List, then you need to use the toCollection() method. We will come to that later in this article.

One more thing, the result returned is mutable ArrayList. So we can modify the returned ArrayList.

Below example collects the US country’s transactions into the List.

@Test
public void toList() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	List<Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(Collectors.toList());

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());
}
Input : 
7482489478327505708 :: US
8101284735400700980 :: US
4034452291956665017 :: CA
8666122611577303193 :: AU

Output :
7482489478327505708 :: US
8101284735400700980 :: US

4. Collector<T, ?, List<T>> toUnmodifiableList()

toUnmodifiableList() method was added to Collectors class in Java 10.

It is used to return data in the Immutable List, which cannot be modified once created. 

It does not wrap the result in Collections.unmodifiableList(list). A little of history. The Java 9 List interface was augmented with several static factories that return immutable List. Read about it here.

The result is first collected into ArrayList. And it defines the finisher function of Collector as 

list -> (List<T>)List.of(list.toArray())

List.of methods copies all the elements from this list into a new List, and returns a new immutable List.

If you are using Java 8 and want to convert to an unmodfiableList, then you need to use collectingAndThen method

stream.collect(
	Collectors.collectingAndThen(
			Collectors.toList(), 
			Collections::unmodifiableList));

Below example collects the US country’s transactions into the immutable List.

@Test
public void toUnmodifiableList() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	List<Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(Collectors.toUnmodifiableList());

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
7482489478327505708 :: US
8101284735400700980 :: US
4034452291956665017 :: CA
8666122611577303193 :: AU

Output : 
7482489478327505708 :: US
8101284735400700980 :: US

5. Collector<T, ?, Set<T>> toSet()

toSet() method is used to collect the data from Stream into the Set. As of today, the toSet() method returns a Collector that uses HashSet. That means it will collect the data into HashSet.

If you want to provide a custom implementation ofSet, then you need to use toCollection method.

Remember, Set will filter out duplicate elements.

The result returned is a mutable HashSet. So the returned HashSet can be modified. 

Below example collects the US country’s transactions into the Set. 

@Test
public void toSet() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Set<Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(Collectors.toSet());

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
7482489478327505708 :: US
8101284735400700980 :: US
4034452291956665017 :: CA
8666122611577303193 :: AU

Output : 
7482489478327505708 :: US
8101284735400700980 :: US

6. Collector<T, ?, Set<T>> toUnmodifiableSet()

toUnmodifiableSet() method was added to Collectors class in Java 10.

It is used to return data in Immutable Set. The result is not wrapped in Collections.unmodifiableSet(set). A little of history. The Java 9 Set interface was augmented with several static factories that returns immutable Set. Read about it here.

The result is first collected into HashSet. And it defines the finisher function of Collector as 

set -> (Set<T>)Set.of(set.toArray())

Set.of methods copies all the elements from this set into a new Set and returns a new immutable Set.

If you are using Java 8 and want to convert to an unmodfiableSet then you need to use collectingAndThen method

stream.collect(
	Collectors.collectingAndThen(
			Collectors.toSet(), 
			Collections::unmodifiableSet);

Below example collects the US country’s transactions into the immutable Set.

@Test
public void toUnmodifiableSet() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Set<Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(Collectors.toUnmodifiableSet());
	
	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());
}

Input : 
7482489478327505708 :: US
8101284735400700980 :: US
4034452291956665017 :: CA
8666122611577303193 :: AU

Output : 
7482489478327505708 :: US
8101284735400700980 :: US

7. Collector<T, ?, Map<K,U>>
toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)

toMap() method is used to collect the data from Stream into the Map. As of today, the toMap() method returns a Collector that uses HashMap. That means it will collect the data into a HashMap.

If you want to provide a custom implementation ofMap, then you need to use the overloaded method toMap() and provide a Supplier<> of Map.

Supplier<Map<Integer, String>> linkedMapSupplier = LinkedHashMap::new;

The result returned is a mutable HashMap. So, the returned HashMap can be modified.

Below example collects the US country’s invoices into the Map. Key – transaction_id and Value – Invoice.

The mandatory parameter is to extract the Key and Value out of the Stream elements. As we cannot stream on Map type, so it forces us to extract the Key and Value out of elements.

In our example, for Key we are getting the transaction_id and for Value we are getting the invoice.

@Test
public void toMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " 
							+ val.invoice().date()));

	Map<Long, Invoice> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
									Transaction::transactionId, 
									Transaction::invoice));

	Assert.assertEquals(2, result.size());
}

Input : 
433631846846915964 :: 2020-09-25
768133369282396940 :: 2020-09-21
612644842461335882 :: 2020-09-25
2166209783504112846 :: 2020-09-30

Output : 
433631846846915964 :: 2020-09-25
768133369282396940 :: 2020-09-21

What happens if the key is duplicate?

Let us look at an interesting condition of collecting data to Map. What will happen if a duplicate key gets inserted into the Map during collection of data elements? The method throws IllegalStateException.

Below example shows this.

We get the transaction data set. Then we add existing transactions into the data set. This means we have two transactions with the same transaction_id. This will cause the toMap method to throw IllegalStateException.

If you want to handle this condition without throwing an exception, then you need to provide a key-merge function.

Let us  look at that.

@Test(expected = IllegalStateException.class)
public void toMapDuplicateKey() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
									Transaction::transactionId, 
									Function.identity()));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());
}

If you are collecting elements into a Map and the keys are duplicate; then the method will throw an IllegalStateException. To handle this condition without throwing the exception, we need to provide behavior for handling this collision. 

The behaviors we have to specify to tell the toMap method which value to use if there is a collision.

You can do this with the use of toMap method that accepts BinaryOperator parameter.

8. Collector<T, ?, Map<K,U>>
toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)

This toMap method is an overloaded version that accepts mergeFunction. This merge function is of type BinaryOperator that is used to resolve the key collision.

The parameter that does this is BinaryOperator.We can use it as below:

BinaryOperator<Transaction> mergeFunc = (Transaction first, Transaction second) -> first;

It means that given two values of the same Key; keep the first one in the Map. It ignores the second value

@Test
public void toMapBinaryOpReplace() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
								Transaction::transactionId, 
								Function.identity(), 
								(o1, o2) -> o1));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());

}

Input :
1300934346833116662 :: US
7561204648258278496 :: US
8212151879682707275 :: CA
6806781174908877649 :: AU
1300934346833116662 :: US

Output :
7561204648258278496 :: US
1300934346833116662 :: US

If you delete this entry from Map in case of key collision, then you can provide a BinaryOperator like this.

BinaryOperator<Transaction> mergeFunc = (Transaction first, Transaction second) -> null;

If the new value is null, then the entry gets removed from Map. The merge method of Map interface uses the merge function. merge() method in the Map interface was added in Java 8.

@Test
public void toMapBinaryOpDelete() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	// deleted the entry if BinaryOperator returns null.
	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
								Transaction::transactionId, 
								Function.identity(), 
								(o1, o2) -> null));
	
	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(1, result.size());

}

Input : 
5770948657081402357 :: US
603125687480333281 :: US
6114057403621473112 :: CA
6628482366941401624 :: AU
5770948657081402357 :: US

Output :
603125687480333281 :: US

9. Collector<T, ?, M>
toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory)

Let us look at the toMap() method with Supplier. This method accepts Key extractor, Value extractor, BinaryOperator and Supplier and input. 

To give a different perspective, we are providing the Supplier as LinkedHashMap.

@Test
public void toMapBinaryOpReplaceNewMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));
	
	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
								txn -> txn.transactionId(), 
								txn -> txn, 
								(o1, o2) -> o1, 
								LinkedHashMap::new));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());
}

Input :
7758481441245849624 :: US
5281308370039956039 :: US
3843378916866803093 :: CA
25791815609413232 :: AU
7758481441245849624 :: US

Output :
7758481441245849624 :: US
5281308370039956039 :: US

In this example, we are deleting the entry if there is a key collision. We are collecting data in LinkedHashMap.

@Test
public void toMapBinaryOpDeleteNewMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	// deleted the entry if BinOp returns null.
	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
								txn -> txn.transactionId(), 
								txn -> txn, 
								(o1, o2) -> null, 
								LinkedHashMap::new));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));
	
	Assert.assertEquals(1, result.size());
}

Input :
7206575591552660383 :: US
4509757237920256190 :: US
7692030021379736999 :: CA
44414986093641643 :: AU
7206575591552660383 :: US

Output :
4509757237920256190 :: US

10. Collector<T, ?, ConcurrentMap<K,U>>
toConcurrentMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)

toConcurrentMap() collects data into the ConcurrentHashMap. Same as toMap() method we need to provide Key extractor and Value extractor.

Below example extracts Key as transaction_id and Value as Invoice.

@Test
public void toConcurrentMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " 
				+ val.invoice().date()));

	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toMap(
								txn -> txn.transactionId(), 
								txn -> txn));

	Assert.assertEquals(2, result.size());

	ConcurrentMap<Long, Invoice> txnInvoice = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toConcurrentMap(
								txn -> txn.transactionId(), 
								txn -> txn.invoice()));


	txnInvoice.forEach((k, v) -> 
	System.out.println(k + " :: " + v.date()));

	Assert.assertEquals(2, txnInvoice.size());

}

Input : 
6091633266815415185 :: 2020-09-25
7632743695427296392 :: 2020-09-21
8820943713079316047 :: 2020-09-25
2570878894224584059 :: 2020-09-30

Output :
6091633266815415185 :: 2020-09-25
7632743695427296392 :: 2020-09-21

11. Collector<T, ?, ConcurrentMap<K,U>>
toConcurrentMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)

If there is a duplicate key i.e. key collisionoccurs, then we get IllegalStateException.

@Test(expected = IllegalStateException.class)
public void toConcurrentMapDuplicateKey() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	ConcurrentMap<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toConcurrentMap(
								txn -> txn.transactionId(), 
								txn -> txn));


	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
6091633266815415185 :: 2020-09-25
7632743695427296392 :: 2020-09-21
8820943713079316047 :: 2020-09-25
2570878894224584059 :: 2020-09-30

Output : 
6091633266815415185 :: 2020-09-25
7632743695427296392 :: 2020-09-21

To avoid the IllegalStateException, we need to provide additional behavior toConcurrentMap. This behavior will dictate what needs to happen to two values for that same Key.

For example, we can keep the first key that was already present in the Map. to do that use the below merge function.

BinaryOperator<Transaction> mergeFunc = 
		(currentValue, newValue) -> currentValue);
@Test
public void toConcurrentMapBinaryOpReplace() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	ConcurrentMap<Long, Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toConcurrentMap(
							txn -> txn.transactionId(), 
							txn -> txn, 
							(currentValue, newValue) -> currentValue));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));
	
	Assert.assertEquals(2, result.size());

}

Input : 
4608515452306303739 :: US
7537840583078296102 :: US
3248191239812497194 :: CA
2629611802260565706 :: AU
4608515452306303739 :: US

Output :
4608515452306303739 :: US
7537840583078296102 :: US

You can delete the entry if the Key collision happens. You can do so by writing below BinaryOperator. This method is an overloaded method of toConcurrentMap and accepts BinaryOperator as input, besides Key extractor and Value extractor.

BinaryOperator<Transaction> mergeFunc = 
			(currentValue, newValue) -> null;
@Test
public void toConcurrentMapBinaryOpDelete() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	// deleted the entry if BinOp returns null.
	ConcurrentMap<Long, Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toConcurrentMap(
							txn -> txn.transactionId(), 
							txn -> txn, 
							(currentValue, newValue) -> null));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(1, result.size());

}

Input : 
9010630412049533779 :: US
8067625205465917872 :: US
1082222962391832061 :: CA
1928788104358166456 :: AU
9010630412049533779 :: US

Output : 
8067625205465917872 :: US

12. Collector<T, ?, M>
toConcurrentMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory)

toConcurrentMap() can accept Supplier of Map too. So in all this method accepts 4 parameters i.e. Key extractor, Value extractor, BinaryOperator(merge function) and Supplier of ConcurrentMap.

The merge function that we have in this example will not replace the old value. It will ignore the new value for the key.

@Test
public void toConcurrentMapBinaryOpReplaceNewMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));
	
ConcurrentNavigableMap<Long, Transaction> result =
		dataSet.stream()
				.filter(txn  -> txn.country() == CountryCode.US)
				.collect(
					Collectors.toConcurrentMap(
						txn -> txn.transactionId(), 
						txn -> txn,
						(currentValue, newValue) -> currentValue, 
						ConcurrentSkipListMap::new));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
6429442359232556587 :: US
9185928392532482037 :: US
8704377771148094548 :: CA
1329271221509667234 :: AU
6429442359232556587 :: US

Output :
6429442359232556587 :: US
9185928392532482037 :: US

In this example, we will delete the entry if there is a key collision.

@Test
public void toConcurrentMapBinaryOpDeleteNewMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	// deleted the entry if BinOp returns null.
	ConcurrentNavigableMap<Long, Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toConcurrentMap(
							txn -> txn.transactionId(), 
							txn -> txn,
							(currentValue, newValue) -> null, 
							ConcurrentSkipListMap::new));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(1, result.size());

}

Input : 
6429442359232556587 :: US
9185928392532482037 :: US
8704377771148094548 :: CA
1329271221509667234 :: AU
6429442359232556587 :: US

Output : 
6429442359232556587 :: US
9185928392532482037 :: US

13. Collector<T, ?, Map<K,U>>
toUnmodifiableMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)

toUnmodifiableMap() method returns an immutable Map. It creates the immutable Map using Map.ofEntries static method in Map interface. ofEntries method was added to the Map interface in Java 9.

@Test
public void toUnmodifiableMap() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Map<Long, Transaction> result = 
				dataSet.stream()
						.filter(txn -> txn.country() == CountryCode.US)
						.collect(
							Collectors.toUnmodifiableMap(
								txn -> txn.transactionId(), 
								txn -> txn));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
3051642719957784337 :: US
2533857059185949560 :: US
4765974210258269337 :: CA
4464183771022156191 :: AU

Output :
3051642719957784337 :: US
2533857059185949560 :: US

14. Collector<T, ?, Map<K,U>>
toUnmodifiableMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction)

toUnmodifiableMap() using BinaryOperator to provide a merge function. If there is a key collision in Map, then merge function behavior will dictate what needs to happen to two values for that same Key.

The merge function that we have in this example will not replace the old Value. It will ignore the new value for the Key.

@Test
public void toUnmodifiableMapBinaryOpReplace() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Map<Long, Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toUnmodifiableMap(
							txn -> txn.transactionId(), 
							txn -> txn,
							(currentValue, newValue) -> currentValue));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
7697594161795646453 :: US
5725113496376609370 :: US
1657301930558677399 :: CA
4243677480537995415 :: AU
7697594161795646453 :: US

Output : 
5725113496376609370 :: US
7697594161795646453 :: US

In this example, we will delete the entry if there is a key collision.

@Test
public void toUnmodifiableMapBinaryOpRemove() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.add(dataSet.get(0));
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Map<Long, Transaction> result = 
				dataSet.stream()
				.filter(txn -> txn.country() == CountryCode.US)
				.collect(
					Collectors.toUnmodifiableMap(
						txn -> txn.transactionId(), 
						txn -> txn, 
						(currentValue, newValue) -> null));

	result.forEach((k, v) -> 
	System.out.println(k + " :: " + v.country()));

	Assert.assertEquals(1, result.size());

}

Input : 
5164956910106797629 :: US
3088394990600950444 :: US
2142522914285019420 :: CA
89181877150191957 :: AU
5164956910106797629 :: US

Output : 
3088394990600950444 :: US

15. Collector<T, ?, C>
toCollection(Supplier<C> collectionFactory)

Now let’s look at the toCollection method. This method can be used to supply any Collection that extends java.util.Collection<> interface.

We saw the toSet() method that collects data in HashSet. What if you want to add data to LinkedHashSet or TreeSet? For those Collections, you can use the toCollection() method.

Below are several Collectors that data in different Collection implementations.

15.1 Collect data in LinkedHashSet

Add transactions done in the US into LinkedHashSet.

@Test
public void toCollectionLinkedHashSet() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Set<Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toCollection(LinkedHashSet::new));

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input :
5513121588669798085 :: US
4735581914562822139 :: US
8416264764795143423 :: CA
6845566038303007718 :: AU

Output :
5513121588669798085 :: US
4735581914562822139 :: US

15.2 Collect data in TreeSet

Add transactions done in the US into TreeSet.

@Test
public void toCollectionTreeSet() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Set<Long> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.map(txn -> txn.transactionId())
					.collect(Collectors.toCollection(TreeSet::new));

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
4082695768686963318 :: US
3619392019003136805 :: US
1468704494012885244 :: CA
7975712278016633197 :: AU

Output : 
3619392019003136805 :: US
4082695768686963318 :: US

15.3 Collect data in TreeSet with reverse Comparator

Add transactions done in the US into TreeSet using reverse Comparator. The Comparator interface went through a major change in Java 8. You can download the Comparator eBook here. It has over 60 examples of Comparators implemented in Java 8, Spring Comparators, Apache Comparators and Google Guava Comparators.

@Test
public void toCollectionTreeSetReverseOrder() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Set<Long> result = 
		dataSet.stream()
			.filter(txn -> txn.country() == CountryCode.US)
			.map(txn -> txn.transactionId())
			.collect(
				Collectors.toCollection(
				() -> new TreeSet<Long>(Comparator.reverseOrder())));

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input : 
2078872918145390269 :: US
2435791106398236131 :: US
5059398070143340037 :: CA
1470706773020550641 :: AU

Output :
2435791106398236131 :: US
2078872918145390269 :: US

15.4 Collect data in PriorityQueue(min-heap)

Add transactions done in the US into PriorityQueue. PriorityQueue is by default a min-heap implementation.

@Test
public void toCollectionPriorityQueue() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	PriorityQueue<Long> result = 
		dataSet.stream()
			.filter(txn -> txn.country() == CountryCode.US)
			.map(txn -> txn.transactionId())
			.collect(Collectors.toCollection(PriorityQueue::new));
	
	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}

Input :
8007942348471877720 :: US
3086631914168691401 :: US
7573289423215147467 :: CA
6543227037199712074 :: AU

Output :
3086631914168691401 :: US
8007942348471877720 :: US

15.5 Collect data in PriorityQueue(max-heap) reverse Comparator

Add transactions done in the US into PriorityQueue. The PriorityQueue will be max-heap as we are using a reverse order Comparator.

@Test
public void toCollectionPriorityQueueReverseOrder() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	PriorityQueue<Long> result = 
	dataSet.stream()
		.filter(txn -> txn.country() == CountryCode.US)
		.map(txn -> txn.transactionId())
		.collect(
			Collectors.toCollection(
			() -> new PriorityQueue<Long>(Comparator.reverseOrder())));

	result.forEach(System.out::println);

	Assert.assertEquals(2, result.size());

}

Input :
2539916540954269709 :: US
4426330993743932226 :: US
4901011246105137055 :: CA
7849643950442998894 :: AU

Output :
4426330993743932226
2539916540954269709

15.6 Collect data in ArrayList with initial capacity

If you intend to use initial capacity constructor, for example: new ArrayList<>(initialCapacity), then you can use toCollection method as below:

@Test
public void toCollectionArrayListWithCapacity() {

	List<Transaction> dataSet = Transactions.getDataSet();
	dataSet.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	List<Transaction> result = 
			dataSet.stream()
					.filter(txn -> txn.country() == CountryCode.US)
					.collect(
						Collectors.toCollection(
							() -> new ArrayList<Transaction>(20)));

	result.forEach(val -> 
	System.out.println(val.transactionId() + " :: " + val.country()));

	Assert.assertEquals(2, result.size());

}


Input : 
7100358093933206697 :: US
1951480043525331548 :: US
4701649714320771724 :: CA
5120994872907506398 :: AU

Output :
7100358093933206697 :: US
1951480043525331548 :: US

16. Conclusion

That is all on Collecting data into a Collection. 

Leave a Reply

Your email address will not be published. Required fields are marked *