java - ¿Por qué Set.of() lanza una excepción IllegalArgumentException si los elementos son duplicados?
collections java-9 (5)
El objetivo principal de diseño de los métodos de fábrica estática List.of
, Set.of
, Map.of
y Map.ofEntries
es permitir a los programadores crear estas colecciones enumerando los elementos explícitamente en el código fuente. Naturalmente, existe un sesgo hacia un pequeño número de elementos o entradas, porque son más comunes, pero la característica relevante aquí es que los elementos se enumeran en el código fuente.
¿Cuál debería ser el comportamiento si se proporcionan elementos duplicados a Set.of
o se proporcionan claves duplicadas a Map.of
o Map.ofEntries
? Suponiendo que los elementos se enumeran explícitamente en el código fuente, es probable que se trate de un error de programación. Las alternativas como las primeras victorias o las últimas victorias parecen cubrir los errores en silencio, por lo que decidimos que hacer que los duplicados sean un error era la mejor forma de actuar. Si los elementos se enumeran explícitamente, sería bueno si se tratara de un error en tiempo de compilación. Sin embargo, la detección de duplicados no se detecta hasta el tiempo de ejecución, * por lo que lanzar una excepción en ese momento es lo mejor que podemos hacer.
* En el futuro, si todos los argumentos son expresiones constantes o se pueden plegar a una constante, la creación de Conjunto o Mapa también podría evaluarse en el momento de la compilación y también plegarse de forma constante. Esto podría permitir que se detecten duplicados en el momento de la compilación.
¿Qué pasa con el caso de uso en el que tiene una colección de elementos y desea deduplicarlos? Ese es un caso de uso diferente , y no está bien manejado por Set.of
y Map.ofEntries
. Primero debes crear una matriz intermedia, que es bastante engorroso:
Set<String> set = Set.of(list.toArray());
Esto no se compila, porque list.toArray()
devuelve un Object[]
. Esto producirá un Set<Object>
que no se puede asignar a Set<String>
. Quieres que toArray
te dé una String[]
lugar:
Set<String> set = Set.of(list.toArray(new String[0]));
Este tipo se verifica, pero todavía lanza una excepción para duplicados! Se sugirió otra alternativa:
Set<String> set = new HashSet<>(list);
Esto funciona, pero recuperas un HashSet
, que es mutable, y que ocupa mucho más espacio que el conjunto devuelto por Set.of
Podría deduplicar los elementos a través de un HashSet
, obtener una matriz de eso y luego pasarlo a Set.of
Eso funcionaría, pero bleah.
Afortunadamente, esto está solucionado en Java 10. Ahora puede escribir:
Set<String> set = Set.copyOf(list);
Esto crea un conjunto no modificable a partir de los elementos de la colección de origen, y los duplicados no generan una excepción. En su lugar, se utiliza uno arbitrario de los duplicados. Hay métodos similares List.copyOf
y Map.copyOf
. Como beneficio adicional, estos métodos omiten la creación de una copia si la colección de origen ya es una colección no modificable del tipo correcto.
En Java 9, se introdujeron nuevos métodos de fábrica estáticos en la interfaz Set, llamada de (), que aceptan múltiples elementos, o incluso una matriz de elementos.
Quería convertir una lista en un conjunto para eliminar cualquier entrada duplicada en el conjunto, lo que se puede hacer (antes de Java 9) usando:
Set<String> set = new HashSet<>();
set.addAll(list);
Pero pensé que sería genial usar este nuevo método de fábrica estático Java 9 haciendo:
Set.of(list.toArray())
donde la list
es una lista de cadenas, previamente definida.
Pero, por desgracia, Java lanzó una IllegalArgumentException
cuando los elementos eran duplicados, también se indica en el Javadoc del método. ¿Por qué es esto?
Edición : esta pregunta no es un duplicado de otra pregunta sobre un tema conceptualmente equivalente, el método Map.of (), pero es claramente diferente. No todos los métodos estáticos de fábrica de () se comportan igual. En otras palabras, cuando estoy preguntando algo sobre el método Set.of () no hago clic en una pregunta relacionada con el método Map.of ().
Los métodos de fábrica Set.of()
producen Set
inmutables para un número dado de elementos.
En las variantes que admiten un número fijo de argumentos ( static <E> Set<E> of()
, static <E> Set<E> of(E e1)
, static <E> Set<E> of(E e1,E e2)
, etc ...) el requisito de no tener duplicados es más fácil de entender: cuando llama al método Set.of(a,b,c)
, está indicando que desea crear un Set
inmutable de exactamente 3 elementos, por lo que si los argumentos contienen duplicados, tiene sentido rechazar su entrada en lugar de producir un Set
más pequeño.
Si bien la Variante del Set<E> of(E... elements)
es diferente (si permite crear un Set
de un número arbitrario de elementos), sigue la misma lógica de las otras variantes. Si pasa n
elementos a ese método, está indicando que desea crear un Set
inmutable de exactamente n
elementos, por lo que no se permiten duplicados.
Aún puede crear un Set
partir de una List
(con posibles duplicados) en una sola línea usando:
Set<String> set = new HashSet<>(list);
que ya estaba disponible antes de Java 9.
Usted espera que esto sea un "último triunfo", al igual que HashSet
, supongo, pero esta fue una decisión deliberada (como explica Stuart Marks, el creador de estos). Incluso tiene un ejemplo como este:
Map.ofEntries(
"!", "Eclamation"
.... // lots of other entries
""
"|", "VERTICAL_BAR"
);
La opción es que dado que esto podría ser propenso a errores, deberían prohibirlo.
También Set.of()
cuenta que Set.of()
devuelve un Set
inmutable, por lo que podría ajustar su Set
en:
Collections.unmodifiableCollection(new HashSet<>(list))
El tipo de elemento del conjunto resultante será el tipo de componente de la matriz, y el tamaño del conjunto será igual a la longitud de la matriz.
Tiros
IllegalArgumentException - if there are any duplicate elements
Esto está claro que esto no está haciendo ninguna prueba duplicada ya que el tamaño del Set
será la longitud de la matriz.
El método solo está aquí para poder obtener un Set
poblado en una línea
Set.of("A","B","C");
Pero tienes que tener cuidado con el duplicado. (Esto simplemente iterará los varargs y los agregará al nuevo Set
.
Set.of()
es una forma corta de crear manualmente un Set
pequeño. En este caso, sería un error de programación flagrante si le asignara valores duplicados, ya que se supone que debe escribir los elementos usted mismo. Es decir, Set.of("foo", "bar", "baz", "foo");
Es claramente un error por parte del programador.
Tu manera fresca es realmente una manera realmente mala. Si desea convertir una List
en un Set
, puede hacerlo con Set<Foo> foo = new HashSet<>(myList);
, o de cualquier otra forma que desee (como con las transmisiones y la recopilación de toSet()
). Las ventajas incluyen no hacer un toArray()
inútil, la elección de su propio Set
(es posible que desee que LinkedHashSet
orden), etc. Las desventajas incluyen tener que escribir algunos caracteres más del código.
Set.of()
la idea de diseño original detrás de los Set.of()
, List.of()
y Map.of()
(y sus numerosas sobrecargas). ¿Cuál es el punto de los Métodos de fábrica de conveniencia sobrecargados para colecciones en Java 9 y here donde se menciona que la atención se centra en pequeñas colecciones , que es algo muy común en la API interna, por lo que se pueden obtener ventajas de rendimiento. Aunque actualmente los métodos se delegan al método varargs que no ofrece ninguna ventaja de rendimiento, esto se puede cambiar fácilmente (aunque no estoy seguro de en qué consiste el retraso).