java - tipos - ¿Usar HashSet sobre ArrayList para transmitir la intención?
investiga las estructuras de datos: arraylist, list, queue, dictionary y table en java. (10)
Imagine que necesito crear una Colección de elementos, donde el orden podría o no importar. Efectivamente, todo lo que planeo hacer es usar el iterador. Noto que la mayoría de mis colegas usan ArrayList vs LinkedHashSet / HashSet. Mi pregunta es, si sé que estos elementos deberían ser únicos, ¿debería utilizar un conjunto o una lista? Efectivamente, realmente no hace la diferencia, pero no establece de manera más efectiva que los elementos son únicos.
Encuentro que esta es una pregunta interesante para las aplicaciones de grandes empresas por algunas razones: 1) Si no puede garantizar la calidad del código en general, usar un conjunto puede ser peligroso. ¿Por qué? Debido a que equals () y hashcode pueden ser anulados incorrectamente y, por lo tanto, usar un Set podría causar algunos problemas realmente desagradables. 2) El uso de una lista es más resistente a los cambios futuros. Si se duplican por alguna razón, no es necesario preocuparse.
Básicamente se reduce a: si sé que debería esperar elementos únicos, ¿debería favorecer la opción Establecer sobre la lista en todos los casos?
Editar: Supongo que también estoy preguntando: ¿Debería usarse el conjunto para asegurar que no se agreguen los duplicados, o también puede usarse con el único propósito de ilustrar que no existen duplicados para facilitar la comprensión?
import java.util.*;
public class Test {
public void testHashSetAddition() {
for(int mod=10; mod <= 100; mod=mod+10 ) {
Set s = new HashSet();
long start = new Date().getTime();
for(int i=0; i<100000; i++) {
s.add(new Foo(i % mod));
}
System.out.println(s.size());
long end = new Date().getTime();
System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
}
}
public void testAddingToArrayList() {
for(int mod=100; mod >= 10; mod=mod-10 ) {
List l = new ArrayList();
long start = new Date().getTime();
for(int i=0; i<100000; i++) {
l.add(new Foo(i % mod));
}
System.out.println(l.size());
long end = new Date().getTime();
System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
}
}
public static void main(String...a){
new Test().testHashSetAddition();
new Test().testAddingToArrayList();
}
class Foo {
private int hc;
public Foo(int i) {
this.hc = i;
}
public int hashCode() {
return hc;
}
public int getHc(){
return hc;
}
public boolean equals(Object o){
if(!(o instanceof Foo)) return false;
Foo fo = (Foo)o;
return fo.getHc() == this.hc;
}
}
}
/*
10
Mod: 10 - 31ms
20
Mod: 20 - 16ms
30
Mod: 30 - 15ms
40
Mod: 40 - 16ms
50
Mod: 50 - 0ms
60
Mod: 60 - 16ms
70
Mod: 70 - 0ms
80
Mod: 80 - 15ms
90
Mod: 90 - 0ms
100
Mod: 100 - 0ms
100000
Mod: 100 - 32ms
100000
Mod: 90 - 31ms
100000
Mod: 80 - 31ms
100000
Mod: 70 - 31ms
100000
Mod: 60 - 32ms
100000
Mod: 50 - 15ms
100000
Mod: 40 - 31ms
100000
Mod: 30 - 32ms
100000
Mod: 20 - 15ms
100000
Mod: 10 - 32ms
*/
1) es totalmente falso. No solucione los errores, arrégleselos. Por lo tanto, use cualquier implementación de Set si el orden no importa, o SortedSet si el orden es importante. Si los elementos no tienen que ser únicos (y debe determinarlo ahora, y por lo general no debería cambiar nunca), puede usar una Lista .
Alguien dijo que HashSet ofrece un rendimiento de tiempo constante en agregar, eliminar, contener y tamaño.
La declaración real en los JavaDocs es "Esta clase ofrece un rendimiento de tiempo constante para las operaciones básicas (agregar, eliminar, contener y tamaño), asumiendo que la función hash dispersa los elementos correctamente entre los cubos ".
Esto significa que puede obtener tiempos de adición lentos al agregar algo al conjunto si tiene un método hashCode mal implementado.
El siguiente código demuestra lo que puede suceder dependiendo de la implementación de hashCode.
public void testHashSetAddition() {
for(int mod=10; mod <= 100; mod=mod+10 ) {
Set s = new HashSet();
long start = new Date().getTime();
for(int i=0; i<100000; i++) {
s.add(new Foo(i % mod));
}
long end = new Date().getTime();
System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
}
}
class Foo {
private int hc;
public Foo(int i) {
this.hc = i;
}
public int hashCode() {
return hc;
}
}
Los resultados de tiempo fueron:
Mod: 10 - 22683ms
Mod: 20 - 14200ms
Mod: 30 - 10486ms
Mod: 40 - 8562ms
Mod: 50 - 7761ms
Mod: 60 - 6740ms
Mod: 70 - 5778ms
Mod: 80 - 5268ms
Mod: 90 - 4716ms
Mod: 100 - 3966ms
Luego, haciendo exactamente la misma prueba para una ArrayList:
public void testAddingToArrayList() {
for(int mod=100; mod >= 10; mod=mod-10 ) {
List l = new ArrayList();
long start = new Date().getTime();
for(int i=0; i<100000; i++) {
l.add(new Foo(i % mod));
}
long end = new Date().getTime();
System.out.println("Mod: " + mod + " - " + (end - start) + "ms");
}
}
Da:
Mod: 100 - 50ms
Mod: 90 - 30ms
Mod: 80 - 40ms
Mod: 70 - 30ms
Mod: 60 - 30ms
Mod: 50 - 40ms
Mod: 40 - 20ms
Mod: 30 - 30ms
Mod: 20 - 30ms
Mod: 10 - 30ms
Considere la legibilidad del código también.
Si espera y quiere un conjunto único, entonces use una estructura de datos "SET", las cosas serán mucho más claras a largo plazo. Y por lo tanto, esto también promoverá una mejor codificación.
Establezca si es preferible, ya que impondrá la exclusividad y le mostrará dónde se equivoca.
Puede tener algunos problemas cuando los métodos son anulados incorrectamente pero la opción correcta es no rezar y evitar llamarlos. ¡Detecta errores y corrigelos!
Editar: Y sí, es más claro cuando ve un conjunto, se necesitan valores únicos e incluso mejores: se imponen valores únicos. Nunca adivine / confíe en el uso de su código;)
No creo que ninguna de las dos opciones deba considerarse como una intención: se debe declarar que su método debe devolver simplemente una Collection
con un parámetro genérico apropiado, tanto por su flexibilidad como porque, como ha dicho, los consumidores de ella deberían ser capaces de iterar sobre él sin preocuparse de qué tipo es. Esto brinda la ventaja adicional de que si los requisitos cambian más tarde, o resulta que, por alguna razón, su elección inicial fue incorrecta, debe cambiar el código en un solo lugar (la llamada inicial al constructor).
La intención debería especificarse más bien en la documentación del método, que debe detallar si el iterador de la colección devolverá los elementos en cualquier orden particular y si aparecerán elementos duplicados.
Y también estoy de acuerdo con las publicaciones anteriores que dicen que su razonamiento sobre el punto 1) está desactivado: si hay clases con implementaciones incorrectas de equals
y / o hashcode
que desea colocar en un conjunto, las arregla y luego usa un conjunto.
Si necesita pensar en elementos únicos, use Establecer. Pero si no confías en que tus usuarios implementen equals / hashCode correctamente, te sugiero que documentes que si hay algún problema con la iteración, ¡comprueba tu equal / hashCode! Pero realmente depende del caso de uso del modelo de datos.
Usar una implementación de Conjunto sobre una implementación de la Lista podría degradar el rendimiento. Al insertar un elemento en un conjunto, debe verificar que no sea un duplicado. Si planea usar solo el iterador, use la implementación más simple posible (ArrayList).
No creo que sea una buena idea usar un Set solo para transmitir información. Si agrega los elementos usted mismo y puede garantizar que no se agregarán duplicados, no tiene sentido utilizar un conjunto. Use un nombre propio para transmitir información sobre la colección. Además, es una buena idea exponerlo a través de la interfaz de Colección, especialmente si las personas que llaman de su clase solo necesitan recorrer la colección.
@Andrzej Doyle - No creo que cuando agregue un elemento a un conjunto, se realice la comparación duplicada. Un conjunto interno usa hashMap y, por lo tanto, cualquier clave duplicada será anulada y no habrá comprobación específica
@Andrzej Doyle - No creo que cuando agregue un elemento a un conjunto, se realice la comparación duplicada. Un conjunto interno usa hashMap y, por lo tanto, cualquier clave duplicada será anulada y no habrá comprobación específica