Java 9 brings a lot of big new features, tools and API enhancements. But somehow the most excitement comes from a few simple additions to collections at which we will look today.
JEP 269: Convenience Factory Methods for Collections
So why so many people are so excited about JEP 269? Creating a collection from a number of elements is actually an operation very frequently used in the code. How many times did you wrote something like:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Set<String> fruits = new HashSet<>(); | |
fruits.add( "banana" ); | |
fruits.add( "pineapple" ); | |
fruits.add( "kiwi" ); | |
fruits = Collections.unmodifiableSet( fruits ); | |
//or using a "initializer block" of anonymous class that extends your set: | |
fruits = Collections.unmodifiableSet( new HashSet<String>() { | |
{ | |
add( "banana" ); | |
add( "pineapple" ); | |
add( "kiwi" ); | |
} | |
} ); | |
// or even more weirder construction of multiple collections to just get a Set | |
fruits = Collections.unmodifiableSet( new HashSet<>( Arrays.asList( "banana", "pineapple", "kiwi" ) ) ); | |
//or using streams: | |
fruits = Stream.of( "banana", "pineapple", "kiwi" ) | |
.collect( | |
Collectors.collectingAndThen( | |
Collectors.toSet(), | |
Collections::unmodifiableSet | |
) | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static <E> Set<E> of(); | |
static <E> Set<E> of(E e1); | |
static <E> Set<E> of(E e1, E e2); | |
static <E> Set<E> of(E e1, E e2, E e3); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9); | |
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10); | |
static <E> Set<E> of(E... elements); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static <E> List<E> of(); | |
static <E> List<E> of(E e1); | |
static <E> List<E> of(E e1, E e2); | |
static <E> List<E> of(E e1, E e2, E e3); | |
static <E> List<E> of(E e1, E e2, E e3, E e4); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9); | |
static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10); | |
static <E> List<E> of(E... elements); |
Such variety of methods is based on performance reasons. Having just one var-arg method would mean that the unnecessary array will be created all the time. Here's how the code from examples shown above would look like using Java 9:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Set<String> fruits = Set.of( "banana", "pineapple", "kiwi" ); |
Properties of Set created with static factory methods
Here's a few interesting properties for such Sets. They
- are structurally immutable, which means that no element can be added or removed from it.
- disallow null as values. If null is passed a NullPointerException will be thrown.
- disallow duplicates. If duplicate elements are passed to such factory method - an IlligalArgumentException will be thrown.
- do not guarantee the iteration order through elements.
Properties of List created with static factory methods
Properties of lists are very similar to the ones that Set has. Lists created with factory methods:
- are structurally immutable, which means that no element can be added or removed from it.
- disallow null as values. If null is passed a NullPointerException will be thrown.
- guarantee the iteration order through elements will be the same as they were passed to a factory method.
And the examples showing these properties:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<String> fruits = List.of( "banana", "pineapple", "kiwi" ); | |
// immutability properties: | |
assertThatExceptionOfType( UnsupportedOperationException.class ) | |
.isThrownBy( () -> fruits.add( "orange" ) ); | |
assertThatExceptionOfType( UnsupportedOperationException.class ) | |
.isThrownBy( () -> fruits.remove( "kiwi" ) ); | |
assertThat( fruits ).containsOnly( "banana", "pineapple", "kiwi" ); | |
// null is disallowed: | |
assertThatExceptionOfType( NullPointerException.class ) | |
.isThrownBy( () -> List.of( null ) ); | |
//guarantee of the order | |
assertThat( fruits ).containsExactly( "banana", "pineapple", "kiwi" ); |
Properties of Map created with static factory methods
Map also did receive an update in form of static factory methods for immutable map creation. There are two kinds of methods present for it Map#of and Map#ofEntries :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static <K, V> Map<K, V> of(); | |
static <K, V> Map<K, V> of(K k1, V v1); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, k6, V v6); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, k6, V v6, K k7, V v7); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, k6, V v6, K k7, V v7, K k8, V v8); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9); | |
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10); | |
static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries); |
First variation of methods - Map#of - receives a sequence of key/value pairs, while the second variation receives a sequence of Map.Entry elements. For ease of use of Map#ofEntries there's also a Map#entry which creates an entry.
Keep in mind that even though Map#ofEntries is much more readable it uses a var-arg approach.
Now as for the properties of such immutable maps. Such Maps:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Map<String, Integer> numberOfFruits = Map.of( | |
"banana", 1, | |
"pineapple", 2, | |
"kiwi", 3 | |
); | |
//or using entries: | |
numberOfFruits = Map.ofEntries( | |
Map.entry( "banana", 1 ), | |
Map.entry( "pineapple", 2 ), | |
Map.entry( "kiwi", 3 ) | |
); |
Now as for the properties of such immutable maps. Such Maps:
- are structurally immutable, which means that no element can be added, removed or updated in it. Keep in mind though that if the object in the map itself is mutable you would be able to change it's state. This may cause inconsistent behavior of the map.
- disallow null as either keys or values. If null is passed a NullPointerException will be thrown.
- disallow duplicates. If duplicate elements are passed to such factory method - an IlligalArgumentException will be thrown.
- do not guarantee the iteration order through elements.
And here are a few examples that show these properties:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Map<String, Integer> numberOfFruits = Map.ofEntries( | |
Map.entry( "banana", 1 ), | |
Map.entry( "pineapple", 2 ), | |
Map.entry( "kiwi", 3 ) | |
); | |
// immutability properties: | |
assertThatExceptionOfType( UnsupportedOperationException.class ) | |
.isThrownBy( () -> numberOfFruits.put( "orange", 10 ) ); | |
assertThatExceptionOfType( UnsupportedOperationException.class ) | |
.isThrownBy( () -> numberOfFruits.remove( "kiwi" ) ); | |
assertThat( numberOfFruits ).containsOnlyKeys( "banana", "pineapple", "kiwi" ); | |
// null is disallowed: | |
assertThatExceptionOfType( NullPointerException.class ) | |
.isThrownBy( () -> Map.of( null, 1 ) ); | |
assertThatExceptionOfType( NullPointerException.class ) | |
.isThrownBy( () -> Map.of( "test", null ) ); | |
//duplicates are disallowed: | |
assertThatExceptionOfType( IllegalArgumentException.class ) | |
.isThrownBy( () -> Map.of( "apple", 1, "apple", 20 ) ); |
Having these static factory methods on collections is definitely a nice addition to the language. They allow to write nicer and cleaner code without adding additional dependencies which could provide such capabilities (like for example Guava) or creating your own set of utility methods to cover these cases.
To be continued...
Useful post. Thanks!
ReplyDelete