Guide de EnumSet

1. Introduction

Dans ce didacticiel, nous explorerons la collection EnumSet du package java.util et discuterons de ses particularités.

Nous allons d'abord montrer les principales fonctionnalités de la collection et ensuite, nous passerons en revue les éléments internes de la classe afin de comprendre ses avantages.

Enfin, nous couvrirons les principales opérations qu'il fournit et implémenterons quelques exemples de base.

2. Qu'est-ce qu'un EnumSet

Un EnumSet est une collection Set spécialisée pour travailler avec des classes enum . Il implémente l' interface Set et s'étend de AbstractSet :

Même si AbstractSet et AbstractCollection fournissent des implémentations pour presque toutes les méthodes des interfaces Set et Collection , EnumSet remplace la plupart d'entre elles.

Lorsque nous prévoyons d'utiliser un EnumSet, nous devons prendre en considération certains points importants:

  • Il ne peut contenir que des valeurs d' énumération et toutes les valeurs doivent appartenir à la même énumération
  • Il ne permet pas d'ajouter des valeurs nulles , lançant une exception NullPointerException pour tenter de le faire
  • Ce n'est pas thread-safe , nous devons donc le synchroniser en externe si nécessaire
  • Les éléments sont stockés dans l'ordre dans lequel ils sont déclarés dans l' énumération
  • Il utilise un itérateur de sécurité qui fonctionne sur une copie, il ne lèvera donc pas d'exception ConcurrentModificationException si la collection est modifiée lors de son itération

3. Pourquoi utiliser EnumSet

En règle générale, EnumSet doit toujours être préféré à toute autre implémentation de Set lorsque nous stockons des valeurs d' énumération .

Dans les sections suivantes, nous verrons ce qui rend cette collection meilleure que d'autres. Pour ce faire, nous montrerons brièvement les éléments internes de la classe pour mieux comprendre.

3.1. Détails d'implémentation

EnumSet est une classe abstraite publique qui contient plusieurs méthodes de fabrique statiques qui nous permettent de créer des instances. Le JDK fournit 2 implémentations différentes - sont privées du package et soutenues par un vecteur de bits:

  • RegularEnumSet et
  • JumboEnumSet

RegularEnumSet utilise un seul long pour représenter le vecteur de bits. Chaque bit de l'élément long représente une valeur de l' énumération . La i-ème valeur de l'énumération sera stockée dans le i-ème bit, il est donc assez facile de savoir si une valeur est présente ou non. Étant donné que long est un type de données 64 bits, cette implémentation peut stocker jusqu'à 64 éléments.

D'autre part, JumboEnumSet utilise un tableau d' éléments longs comme vecteur de bits. Cela permet à cette implémentation de stocker plus de 64 éléments. Cela fonctionne à peu près comme RegularEnumSet mais en effectuant des calculs supplémentaires pour trouver l'index du tableau où la valeur est stockée.

Sans surprise, le premier élément long du tableau stockera les 64 premières valeurs de l' énumération , le deuxième élément les 64 suivants, et ainsi de suite.

Les méthodes de fabrique EnumSet créent des instances d'une implémentation ou d'une autre en fonction du nombre d'éléments de l' énumération :

if (universe.length <= 64) return new RegularEnumSet(elementType, universe); else return new JumboEnumSet(elementType, universe);

Gardez à l'esprit qu'il ne prend en compte que la taille de la classe enum , pas le nombre d'éléments qui seront stockés dans la collection.

3.2. Avantages de l'utilisation d'un EnumSet

En raison de l'implémentation d'un EnumSet que nous avons décrit ci-dessus, toutes les méthodes d'un EnumSet sont implémentées à l'aide d'opérations arithmétiques au niveau du bit. Ces calculs sont très rapides et donc toutes les opérations de base sont exécutées dans un temps constant.

Si nous comparons EnumSet avec d'autres implémentations de Set comme HashSet , la première est généralement plus rapide car les valeurs sont stockées dans un ordre prévisible et un seul bit doit être examiné pour chaque calcul. Contrairement à HashSet , il n'est pas nécessaire de calculer le hashcode pour trouver le bon compartiment .

De plus, en raison de la nature des vecteurs de bits, un EnumSet est très compact et efficace. Par conséquent, il utilise moins de mémoire, avec tous les avantages qu'il apporte.

4. Opérations principales

La majorité des méthodes d'un EnumSet fonctionnent comme n'importe quel autre Set , à l'exception des méthodes de création d'instances.

Dans les sections suivantes, nous montrerons en détail toutes les méthodes de création et nous couvrirons brièvement le reste des méthodes.

Dans nos exemples, nous allons travailler avec une énumération Color :

public enum Color { RED, YELLOW, GREEN, BLUE, BLACK, WHITE }

4.1. Méthodes créatives

Les méthodes les plus simples pour créer un EnumSet sont allOf () et noneOf () . De cette façon, nous pouvons facilement créer un EnumSet contenant tous les éléments de notre énumération Color :

EnumSet.allOf(Color.class);

De même, nous pouvons utiliser noneOf () pour faire le contraire et créer une collection vide de Color :

EnumSet.noneOf(Color.class);

Si nous voulons créer un EnumSet avec un sous-ensemble des éléments enum , nous pouvons utiliser les méthodes surchargées de () . Il est important de faire la différence entre les méthodes avec un nombre fixe de paramètres jusqu'à 5 différents et celle qui utilise varargs :

Le Javadoc indique que les performances de la version varargs peuvent être plus lentes que les autres en raison de la création du tableau. Par conséquent, nous ne devons l'utiliser que si nous devons initialement ajouter plus de 5 éléments.

Une autre façon de créer un sous-ensemble d'une énumération consiste à utiliser la méthode range () :

EnumSet.range(Color.YELLOW, Color.BLUE);

Dans l'exemple ci-dessus, le EnumSet contient tous les éléments du jaune au bleu. Ils suivent l'ordre défini dans l' énumération :

[YELLOW, GREEN, BLUE]

Notez qu'il inclut à la fois le premier et le dernier élément spécifié.

Une autre méthode de fabrique utile est le complémentOf () qui nous permet d'exclure les éléments passés en paramètres . Créons un EnumSet avec tous les éléments Color sauf le noir et blanc:

EnumSet.complementOf(EnumSet.of(Color.BLACK, Color.WHITE));

Si nous imprimons cette collection, nous pouvons voir qu'elle contient tous les autres éléments:

[RED, YELLOW, GREEN, BLUE]

Enfin, nous pouvons créer un EnumSet en copiant tous les éléments d'un autre EnumSet :

EnumSet.copyOf(EnumSet.of(Color.BLACK, Color.WHITE));

En interne, il appelle la méthode clone .

De plus, nous pouvons également copier tous les éléments de n'importe quelle collection qui contient des éléments enum . Utilisons-le pour copier tous les éléments d'une liste:

List colorsList = new ArrayList(); colorsList.add(Color.RED); EnumSet listCopy = EnumSet.copyOf(colorsList);

Dans ce cas, le listCopy contient uniquement la couleur rouge.

4.2. Autres opérations

Le reste des opérations fonctionne exactement de la même manière que toute autre implémentation de Set et il n'y a aucune différence dans la façon de les utiliser.

Par conséquent, nous pouvons facilement créer un EnumSet vide et ajouter des éléments:

EnumSet set = EnumSet.noneOf(Color.class); set.add(Color.RED); set.add(Color.YELLOW)

Vérifiez si la collection contient un élément spécifique:

set.contains(Color.RED);

Parcourez les éléments:

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

Ou supprimez simplement des éléments:

set.remove(Color.RED);

Ceci, bien sûr, parmi toutes les autres opérations prises en charge par un ensemble .

5. Conclusion

Dans cet article, nous avons montré les principales fonctionnalités d' EnumSet , son implémentation interne et les avantages de son utilisation.

Nous avons également couvert les principales méthodes qu'il propose et implémenté quelques exemples pour montrer comment nous pouvons les utiliser.

Comme toujours, le code source complet des exemples est disponible à l'adresse over sur GitHub.