web-dev-qa-db-fra.com

Pourquoi Set.of () lève-t-il une exception IllegalArgumentException si les éléments sont en double?

En Java 9, de nouvelles méthodes de fabrique statique ont été introduites dans l'interface Set, appelée of (), qui accepte plusieurs éléments, voire un tableau d'éléments.

Je voulais transformer une liste en un ensemble pour supprimer toutes les entrées en double de cet ensemble, ce qui peut être fait (avant Java 9) en utilisant:

Set<String> set = new HashSet<>();
set.addAll(list);

Mais j’ai pensé que ce serait cool d’utiliser cette nouvelle méthode de fabrique statique Java 9 en faisant:

Set.of(list.toArray())

list est une liste de chaînes, définie précédemment.

Mais hélas, Java a jeté une IllegalArgumentException quand les éléments étaient des doublons, a également déclaré dans le Javadoc de la méthode. Pourquoi est-ce?

Edit : cette question n'est pas une copie d'une autre question sur un sujet équivalent sur le plan conceptuel, la méthode Map.of (), mais elle est nettement différente. Toutes les méthodes statiques de fabrique de () ne se comportent pas de la même manière. En d'autres termes, lorsque je demande quelque chose à propos de la méthode Set.of (), je ne clique pas sur une question traitant de la méthode Map.of ().

17
Benjamin

Les méthodes de fabrique Set.of() produisent immuable Setname__s pour un donné nombre d'éléments. 

Dans les variantes qui prennent en charge un nombre fixe d'arguments (static <E> Set<E> of​(), static <E> Set<E> of​(E e1), static <E> Set<E> of​(E e1,E e2), etc ...), il est plus facile de comprendre la nécessité de ne pas avoir de doublons. Lorsque vous appelez la méthode Set.of(a,b,c), vous indiquez que vous souhaitez créer un Setnom__ immuable. de exactement 3 éléments, donc si les arguments contiennent des doublons, il est logique de rejeter votre entrée au lieu de produire un Setplus petit.

Alors que la variante Set<E> of​(E... elements) est différente (si permet de créer un Setd'un nombre arbitraire d'éléments), elle suit la même logique que les autres variantes. Si vous transmettez des éléments nà cette méthode, vous indiquez que vous souhaitez créer un _Setimmuable de exactement néléments, les doublons ne sont donc pas autorisés.

Vous pouvez toujours créer un Setà partir d'un List(avec des doublons potentiels) dans une ligne en utilisant:

Set<String> set = new HashSet<>(list);

qui était déjà disponible avant Java 9.

17
Eran

Set.of() est un moyen rapide de créer manuellement une petite Set. Dans ce cas, ce serait une erreur de programmation flagrante si vous lui donniez des valeurs en double, car vous êtes censé écrire vous-même les éléments. C'est à dire. Set.of("foo", "bar", "baz", "foo"); est clairement une erreur de la part du programmeur.

Votre façon {cool} _ est en réalité une très mauvaise façon. Si vous voulez convertir une List en une Set, vous pouvez le faire avec Set<Foo> foo = new HashSet<>(myList); ou de toute autre manière (par exemple, avec les flux et la collecte de toSet()). Les avantages incluent le fait de ne pas utiliser une toArray() inutile, de choisir votre propre Set (vous pouvez vouloir une LinkedHashSet pour préserver l'ordre), etc. Inconvénients: devoir saisir quelques caractères de code supplémentaires.

L'idée de conception originale des méthodes Set.of(), List.of() et Map.of() (et leurs nombreuses surcharges) est expliquée ici Quel est l'intérêt des méthodes surchargées de Convenience Factory pour les collections en Java 9 et ici , où il est mentionné que L'accent est mis sur les petites collections , ce qui est très courant dans l'API interne, ce qui permet d'optimiser les performances. Bien que les méthodes délèguent actuellement à la méthode varargs n'offrant aucun avantage en termes de performances, ceci peut être facilement modifié (vous ne pouvez pas savoir quel est le blocage).

25
Kayaman

Vous vous attendez à ce que ce soit une "dernière victoire", tout comme HashSet je suppose, mais c'était une délibération décision (comme Stuart Marks - le créateur de ces explications). Il a même un exemple comme celui-ci:

Map.ofEntries(
   "!", "Eclamation"
   .... // lots of other entries
   ""
   "|", "VERTICAL_BAR"
);

Le choix est que, puisque cela pourrait être sujet à des erreurs, ils devraient l'interdire.

Notez également que Set.of() renvoie une Set immuable, afin que vous puissiez envelopper votre Set dans:

Collections.unmodifiableCollection(new HashSet<>(list))
6
Eugene

L’objectif principal de la conception des méthodes de fabrique statique List.of, Set.of, Map.of et Map.ofEntries est d’autoriser les programmeurs à créer ces collections en répertoriant explicitement les éléments dans le code source. Naturellement, il existe un biais en faveur d'un petit nombre d'éléments ou d'entrées, car ils sont plus courants, mais la caractéristique pertinente ici est que les éléments sont répertoriés dans le code source.

Quel devrait être le comportement si des éléments en double sont fournis à Set.of ou des clés en double fournies à Map.of ou Map.ofEntries? En supposant que les éléments soient listés explicitement dans le code source, il s'agit probablement d'une erreur de programmation. Des solutions telles que les premières victoires ou les dernières victoires risquent de dissimuler les erreurs en silence. Nous avons donc décidé que faire des doublons une erreur était la meilleure solution. Si les éléments sont explicitement listés, ce serait bien s'il s'agissait d'une erreur de compilation. Cependant, la détection des doublons n’est détectée qu’au moment de l’exécution, * il est donc préférable de lancer une exception à ce moment-là.

* À l'avenir, si tous les arguments sont des expressions constantes ou sont pliables de manière constante, la création de jeux ou de cartes peut également être évaluée au moment de la compilation et également pliée de manière constante. Cela peut permettre de détecter les doublons lors de la compilation.

Qu'en est-il du cas d'utilisation où vous avez une collection d'éléments et que vous souhaitez les dédupliquer? C'est un différent cas d'utilisation, et ce n'est pas bien géré par Set.of et Map.ofEntries. Vous devez d'abord créer un tableau intermédiaire, ce qui est assez lourd:

Set<String> set = Set.of(list.toArray());

Cela ne compile pas, car list.toArray() renvoie un Object[]. Cela produira un Set<Object> qui ne peut pas être attribué à Set<String>. Vous voulez que toArray vous donne un String[] à la place:

Set<String> set = Set.of(list.toArray(new String[0]));

Cette typechecks, mais il jette toujours une exception pour les doublons! Une autre alternative a été suggérée:

Set<String> set = new HashSet<>(list);

Cela fonctionne, mais vous récupérez une HashSet, qui est modifiable et qui occupe beaucoup plus d'espace que l'ensemble renvoyé par Set.of. Vous pouvez dédupliquer les éléments par le biais d'une HashSet, en obtenir un tableau, puis le transmettre à Set.of. Ça marcherait, mais bon.

Heureusement, ceci est corrigé dans Java 10. Vous pouvez maintenant écrire:

Set<String> set = Set.copyOf(list);

Cela crée un ensemble non modifiable à partir des éléments de la collection source et les doublons font pas jettent une exception. Au lieu de cela, un des doublons est utilisé. Il existe des méthodes similaires List.copyOf et Map.copyOf. En prime, ces méthodes ignorent la création d'une copie si la collection source est déjà une collection non modifiable du type approprié.

2
Stuart Marks

Set.of (E ... elements)

Le type d'élément de l'ensemble résultant sera le type de composant du tableau, et la taille de l'ensemble sera égale à la longueur du tableau. 

Lance:

IllegalArgumentException - if there are any duplicate elements

Il est clair qu'il ne s'agit pas d'un test en double puisque la taille de la variable Set sera la longueur du tableau.

La méthode est juste ici pour pouvoir obtenir une Set peuplée sur une ligne

Set.of("A","B","C");

Mais vous devez faire attention à la copie vous-même. (Ceci va simplement itérer les varargs et les ajouter dans la nouvelle Set.

1
AxelH