Méthodes Java Convenience Factory pour les collections

1. Vue d'ensemble

Java 9 apporte le sucre syntaxique tant attendu pour créer de petites instances de Collection non modifiables à l' aide d'un code concis one-liner. Conformément à JEP 269, de nouvelles méthodes d'usine de commodité seront incluses dans JDK 9.

Dans cet article, nous allons couvrir son utilisation avec les détails de mise en œuvre.

2. Histoire et motivation

La création d'une petite collection immuable en Java est très détaillée en utilisant la méthode traditionnelle.

Prenons un exemple d' ensemble :

Set set = new HashSet(); set.add("foo"); set.add("bar"); set.add("baz"); set = Collections.unmodifiableSet(set);

C'est beaucoup trop de code pour une tâche simple et cela devrait être possible dans une seule expression.

Ce qui précède est également vrai pour une carte.

Cependant, pour List , il existe une méthode d'usine:

List list = Arrays.asList("foo", "bar", "baz");

Bien que cette création de liste soit meilleure que l'initialisation du constructeur, cela est moins évident car l'intuition courante ne serait pas de rechercher dans la classe Arrays des méthodes pour créer une liste :

Il existe d'autres moyens de réduire la verbosité comme la technique d' initialisation à double accolade :

Set set = Collections.unmodifiableSet(new HashSet() {{ add("foo"); add("bar"); add("baz"); }});

ou en utilisant Java 8 Streams :

Stream.of("foo", "bar", "baz") .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

La technique des doubles accolades est un peu moins verbeuse mais réduit considérablement la lisibilité (et est considérée comme un anti-motif).

La version Java 8, cependant, est une expression sur une ligne et présente également quelques problèmes. Premièrement, ce n'est ni évident ni intuitif. Deuxièmement, c'est toujours verbeux. Troisièmement, cela implique la création d'objets inutiles. Et quatrièmement, cette méthode ne peut pas être utilisée pour créer une carte .

Pour résumer les lacunes, aucune des approches ci-dessus ne traite le cas d'utilisation spécifique en créant un petit problème de première classe non modifiable de Collection .

3. Description et utilisation

Des méthodes statiques ont été fournies pour les interfaces List , Set et Map qui prennent les éléments comme arguments et renvoient une instance de List , Set et Map , respectivement.

Cette méthode est nommée de (…) pour les trois interfaces.

3.1. Lister et définir

La signature et les caractéristiques des méthodes d'usine List et Set sont les mêmes:

static  List of(E e1, E e2, E e3) static  Set of(E e1, E e2, E e3)

utilisation des méthodes:

List list = List.of("foo", "bar", "baz"); Set set = Set.of("foo", "bar", "baz");

Comme nous pouvons le voir, c'est très simple, court et concis.

Dans l'exemple, nous avons utilisé la méthode avec prend exactement trois éléments comme paramètres et renvoie une liste / un ensemble de taille 3.

Mais, il existe 12 versions surchargées de cette méthode - onze avec 0 à 10 paramètres et une avec var-args:

static  List of() static  List of(E e1) static  List of(E e1, E e2) // ....and so on static  List of(E... elems)

Dans la plupart des cas, 10 éléments suffiraient, mais si plus sont nécessaires, la version var-args peut être utilisée.

Maintenant, nous pouvons nous demander quel est l'intérêt d'avoir 11 méthodes supplémentaires s'il existe une version var-args qui peut fonctionner pour n'importe quel nombre d'éléments.

La réponse à cela est la performance. Chaque appel de méthode var-args crée implicitement un tableau. Le fait d'avoir les méthodes surchargées évite la création d'objets inutiles et la surcharge de récupération de place. Au contraire, Arrays.asList crée toujours ce tableau implicite et, par conséquent, est moins efficace lorsque le nombre d'éléments est faible.

Lors de la création d'un Set à l' aide d'une méthode de fabrique, si des éléments dupliqués sont passés en tant que paramètres, alors IllegalArgumentException est levé au moment de l'exécution:

@Test(expected = IllegalArgumentException.class) public void onDuplicateElem_IfIllegalArgExp_thenSuccess() { Set.of("foo", "bar", "baz", "foo"); }

Un point important à noter ici est que puisque les méthodes de fabrique utilisent des génériques, les types primitifs sont autoboxés.

If an array of primitive type is passed, a List of array of that primitive type is returned.

For example:

int[] arr = { 1, 2, 3, 4, 5 }; List list = List.of(arr);

In this case, a List of size 1 is returned and the element at index 0 contains the array.

3.2. Map

The signature of Map factory method is:

static  Map of(K k1, V v1, K k2, V v2, K k3, V v3)

and the usage:

Map map = Map.of("foo", "a", "bar", "b", "baz", "c");

Similarly to List and Set, the of(…) method is overloaded to have 0 to 10 key-value pairs.

In the case of Map, there is a different method for more than 10 key-value pairs:

static  Map ofEntries(Map.Entry... entries)

and it's usage:

Map map = Map.ofEntries( new AbstractMap.SimpleEntry("foo", "a"), new AbstractMap.SimpleEntry("bar", "b"), new AbstractMap.SimpleEntry("baz", "c"));

Passing in duplicate values for Key would throw an IllegalArgumentException:

@Test(expected = IllegalArgumentException.class) public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() { Map.of("foo", "a", "foo", "b"); }

Again, in the case of Map too, the primitive types are autoboxed.

4. Implementation Notes

The collections created using the factory methods are not commonly used implementations.

For example, the List is not an ArrayList and the Map is not a HashMap. Those are different implementations that are introduced in Java 9. These implementations are internal and their constructors have restricted access.

In this section, we'll see some important implementation differences which are common to all the three types of collections.

4.1. Immutable

The collections created using factory methods are immutable, and changing an element, adding new elements, or removing an element throws UnsupportedOperationException:

@Test(expected = UnsupportedOperationException.class) public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() { Set set = Set.of("foo", "bar"); set.add("baz"); }
@Test(expected = UnsupportedOperationException.class) public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() { List list = List.of("foo", "bar"); list.set(0, "baz"); } 

On the other hand, the collection returned from Arrays.asListis mutable. Therefore, it's possible to change or remove the existing elements. Similar to List.of, we can't add new elements to a list returned from Arrays.asList.

@Test(expected = UnsupportedOperationException.class) public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() { Map map = Map.of("foo", "a", "bar", "b"); map.remove("foo"); }

4.2. No null Element Allowed

In the case of List and Set, no elements can be null. In the case of a Map, neither keys nor values can be null. Passing null argument throws a NullPointerException:

@Test(expected = NullPointerException.class) public void onNullElem_ifNullPtrExpnThrown_thenSuccess() { List.of("foo", "bar", null); }

As opposed to List.of, the Arrays.asList method accepts null values.

4.3. Value-Based Instances

The instances created by factory methods are value-based. This means that factories are free to create a new instance or return an existing instance.

Hence, if we create Lists with same values, they may or may not refer to the same object on the heap:

List list1 = List.of("foo", "bar"); List list2 = List.of("foo", "bar");

Dans ce cas, list1 == list2 peut ou peut ne pas être évalué à vrai en fonction de la JVM.

4.4. Sérialisation

Les collections créées à partir de méthodes d'usine sont sérialisables si les éléments de la collection sont sérialisables.

5. Conclusion

Dans cet article, nous avons présenté les nouvelles méthodes d'usine pour les collections introduites dans Java 9.

Nous avons conclu pourquoi cette fonctionnalité est un changement bienvenu en passant en revue certaines méthodes antérieures de création de collections non modifiables. Nous avons couvert son utilisation et souligné les points clés à prendre en compte lors de leur utilisation.

Enfin, nous avons précisé que ces collections sont différentes des implémentations couramment utilisées et souligné les principales différences.

Le code source complet et les tests unitaires de cet article sont disponibles à l'adresse over sur GitHub.