¿Por qué las matrices en Java no anulan equals ()?

8

Estaba trabajando con otro HashSetel otro día, que tiene esto escrito en la especificación:

[add ()] agrega el elemento e especificado a este conjunto si este conjunto no contiene ningún elemento e2 tal que (e == null? e2 == null: e.equals (e2))

Yo estaba usando char[]en el HashSethasta que me di cuenta de que, sobre la base de este contrato, no era mejor que una ArrayList! Dado que está utilizando los no anulados .equals(), mis matrices solo se verificarán para la igualdad de referencia, lo que no es particularmente útil. Sé que Arrays.equals()existe, pero eso no ayuda cuando uno está usando colecciones como HashSet.

Entonces mi pregunta es, ¿por qué las matrices Java no anulan iguales?

Azar
fuente
3
mezclar matrices nativas y colección no es realmente aconsejable
fanático del trinquete
1
Es el principio del asunto, creo. Una matriz es la segunda estructura de datos imperativa más primitiva después de las variables mutables. Se trata simplemente de nranuras de memoria lo suficientemente grandes como para que cada una tenga algún valor de tipo Tpegado secuencialmente. Son los bloques de construcción para colecciones efímeras más sofisticadas. Lo que querías era un List.
Doval
1
@ratchetfreak A menudo escuché a la gente decir eso, pero nunca por qué. por qué es una mala idea?
Richard Tingle
@RichardTingle Las matrices no siempre se mezclan bien con los genéricos, las matrices no son Iterables, su toString()representación es en su mayoría inútil, y casi no hay ninguna ventaja en usar una sobre una ArrayList.
Doval
@Doval Pero esas son todas las razones para no usar matrices (lo cual estaría de acuerdo con el 95% del tiempo). A veces necesito usar matrices porque funcionan bien con gráficos 3D, pero quiero asignarles una clave. Por eso Map<Key, int[]>siempre se ha sentido natural. Pero siempre estoy nervioso de que haya algo horrible esperándome
Richard Tingle

Respuestas:

8

Hubo una decisión de diseño que tomar desde el principio en Java:

¿Son las matrices primitivas? o son objetos?

La respuesta es, tampoco realmente ... o ambas si lo miras de otra manera. Trabajan bastante estrechamente con el sistema en sí y el backend de la jvm.

Un ejemplo de esto es el método java.lang.System.arraycopy () que necesita tomar una matriz de cualquier tipo. Por lo tanto, la matriz necesita poder heredar algo y eso es un Objeto. Y arraycopy es un método nativo.

Las matrices son también divertido, ya que pueden contener primitivas ( int, char, double, etc ... mientras que las otras colecciones sólo pueden contener objetos. Mira, por ejemplo, en java.util.Arrays y lo feo de los Iguales métodos. Este se puso en como un pensamiento posterior, deepEquals (Object [], Object []) no se agregó hasta 1.5 mientras que el resto de la clase Arrays se agregó en 1.2.

Debido a que estos objetos son matrices , le permiten hacer algunas cosas en la memoria o cerca del nivel de memoria, algo que Java a menudo oculta del codificador. Esto permite que ciertas cosas se hagan más rápido a expensas de romper principalmente el modelo de objetos.

Al principio del sistema hubo una compensación entre flexibilidad y cierto rendimiento. El rendimiento ganó y la falta de flexibilidad se vio envuelta en varias colecciones. Las matrices en Java son un objeto poco implementado sobre un tipo primitivo (originalmente) destinado a trabajar con el sistema cuando lo necesita.

En su mayor parte, los arreglos en bruto fueron cosas que parece que los diseñadores originales intentaron ignorar y guardar solo en el sistema. Y querían que fuera rápido (los primeros Java tenían algunos problemas con la velocidad). Fue una verruga en el diseño que las matrices no son matrices agradables, pero era necesaria cuando se quería exponer algo lo más cerca posible del sistema. Para el caso, los lenguajes contemporáneos de los primeros Java también tienen esta verruga: no se puede hacer nada .equals()en la matriz de C ++.

Java y C ++ tomaron la misma ruta para las matrices: una biblioteca externa que realiza las operaciones según sea necesario en las matrices en lugar de las matrices ... y sugiere a los codificadores que usen tipos nativos mejores a menos que realmente sepan lo que están haciendo y por qué lo hacen. haciéndolo de esa manera.

Por lo tanto, el enfoque de implantación de .equals en una matriz es incorrecto, pero es el mismo error que los codificadores procedentes de C ++ conocían. Por lo tanto, elija la cosa menos incorrecta en términos de rendimiento: déjelo como la implementación de Object: dos objetos son iguales si y solo se refieren al mismo objeto.

Necesita que la matriz sea una estructura primitiva como para poder comunicarse con enlaces nativos, algo lo más cercano posible a la matriz C clásica. Pero a diferencia de las otras primitivas, necesita que la matriz se pueda pasar como referencia y, por lo tanto, como un Objeto. Por lo tanto, es más primitivo con algunos hacks de objetos en el lateral y algunas comprobaciones de límites.


fuente
+1 y gracias por la respuesta. Esto era lo que realmente estaba buscando, la perspectiva de diseño detrás de la decisión, no solo que es una mala idea.
Azar
1
@Azar hay una discusión en C2: Las matrices de Java deberían ser objetos de primera clase que tiene un código que muestra algunos de los hackers que se producen detrás de escena con una matriz ... junto con otros lamentando que las matrices no sean objetos agradables.
2
Afortunadamente para C ++, hacer una clase de matriz más inteligente no requiere boxeo. Java no es tan afortunado.
Thomas Eding
3

En Java, las matrices son pseudoobjetos. Las referencias a objetos pueden contener matrices, y tienen los métodos Object estándar, pero son muy livianos en comparación con una verdadera colección. Las matrices hacen lo suficiente para cumplir con el contrato de un elemento y utilizar las implementaciones por defecto de equals, hashCodey toStringdeliberadamente.

Considera una Object[]. Un elemento de esta matriz puede ser cualquier cosa que encaje en un objeto, que incluye otra matriz. Podría ser un primitivo en caja, un enchufe, cualquier cosa. ¿Qué significa igualdad en ese caso? Bueno, depende de lo que esté realmente en la matriz. Eso no es algo conocido en el caso general cuando se estaba diseñando el lenguaje. La igualdad se define tanto por la matriz en sí como por sus contenidos .

Esta es la razón por la cual hay una Arraysclase auxiliar que tiene métodos para calcular la igualdad (incluyendo iguales profundos), códigos hash, etc. Sin embargo, esos métodos están bien definidos en cuanto a lo que hacen. Si necesita una funcionalidad diferente, escriba su propio método para comparar dos matrices de igualdad en función de las necesidades de su programa.


Si bien no es estrictamente una respuesta a su pregunta, creo que es relevante decir que realmente debería usar colecciones en lugar de matrices. Solo convierta a una matriz cuando interactúe con una API que requiera matrices. De lo contrario, las colecciones ofrecen una mejor seguridad de tipos, contratos más bien definidos y, en general, son más fáciles de usar que las matrices.


fuente
Las matrices son objetos reales. El hecho de que sean el único tipo agregado de elementos múltiples significa que cualquier otra colección de tamaño variable debe estar respaldada por matrices u objetos O (N) fuera de sí misma, por lo que, en comparación, las matrices serán "livianas". Creo que el problema más fundamental no es que las matrices sean livianas, sino que hay muchas formas de usarlas. Vea mi respuesta a continuación.
supercat
1
"¿Qué significa igualdad en ese caso?", ¿Qué tal si las matrices tienen la misma longitud, además de que cada objeto en cada índice, tanto en el origen como en el destino, debe ser igual de acuerdo con el contrato de iguales ()? parece lo que esperarías, y es el contrato exacto implementado por Arrays.equals ().
Jeffrey Blattman
@JeffreyBlattman: ¿qué sucede si una matriz Object se contiene a sí misma?
Jules
@JeffreyBlattman luego habla con los autores del lenguaje que implementaron la igualdad de referencia para las matrices, pero proporcionaron Arrays.equals()una igualdad profunda.
@Jules "¿qué sucede, entonces, si una matriz Object se contiene a sí misma? lo mismo que sucede si un objeto se contiene a sí mismo. Si implementa un ingenuo igual, entonces obtendrá desbordamiento de pila.
Jeffrey Blattman
1

La dificultad fundamental con la anulación de matrices equalses que una variable de un tipo como int[]se puede usar en al menos tres formas fundamentalmente diferentes, y el significado de equalsdebe variar según el uso. En particular, un campo de tipo int[]...

  1. ... puede encapsular una secuencia de valores en una matriz que nunca se modificará, pero se puede compartir libremente con un código que no lo modificará.

  2. ... puede encapsular la propiedad exclusiva de un contenedor de números enteros que su propietario puede mutar a voluntad.

  3. ... puede identificar un contenedor de retención de enteros que otra entidad está utilizando para encapsular su estado y, por lo tanto, servir como una conexión con el estado de esa otra entidad.

Si una clase tiene un int[]campo fooque se utiliza para cualquiera de los dos primeros propósitos, entonces instancias xy ydeben considerar x.fooy y.foocomo encapsular el mismo estado si tienen la misma secuencia de números; si el campo se usa para el tercer propósito, entonces, x.fooy y.foosolo encapsularía el mismo estado si identificaran la misma matriz [es decir, son iguales de referencia]. Si Java hubiera incluido diferentes tipos para los tres usos anteriores, y si hubiera equalstomado un parámetro que identificara cómo se estaba utilizando la referencia, entonces hubiera sido apropiado int[]usar la secuencia de igualdad para los dos primeros usos y la igualdad de referencia para el tercero. No existe tal mecanismo, sin embargo.

Tenga en cuenta también que el int[]caso era el tipo más simple de matriz. Para las matrices que contienen referencias a clases distintas Objecto tipos de matriz, habría posibilidades adicionales.

  1. Una referencia a una matriz inmutable que se puede compartir que encapsula cosas que nunca cambiarán.

  2. Una referencia a una matriz inmutable que se puede compartir que identifica cosas que pertenecen a otras entidades.

  3. Una referencia a una matriz de propiedad exclusiva que encapsula referencias a cosas que nunca cambiarán.

  4. Una referencia a una matriz de propiedad exclusiva que encapsula referencias a elementos de propiedad exclusiva.

  5. Una referencia a una matriz de propiedad exclusiva que identifica cosas pertenecientes a otras entidades.

  6. Una referencia que identifica una matriz propiedad de otra entidad.

En los casos 1, 3 y 4, dos referencias de matriz deben considerarse iguales si los elementos correspondientes son "iguales en valor". En los casos 2 y 5, dos referencias de matriz deben considerarse iguales si identifican la misma secuencia de objetos. En el caso 6, dos referencias de matriz deben considerarse iguales solo si identifican la misma matriz.

Para equalscomportarse con sensatez con los tipos agregados, necesitan tener alguna forma de saber cómo se utilizarán las referencias. Desafortunadamente, el sistema de tipos de Java no tiene forma de indicar eso.

Super gato
fuente
-2

Anular la matriz equals()y hashCode()depender del contenido los haría similares a las colecciones, tipos mutables con no constante hashCode(). Los tipos con cambios se hashCode()comportan mal cuando se almacenan en tablas hash y otras aplicaciones que dependen de hashCode()un valor fijo.

Set<List<Integer>> data = new HashSet<List<Integer>>();
List<Integer> datum = new ArrayList<Integer>();
datum.add(1);
data.add(datum);
assert data.contains(datum); // true
datum.add(2);
assert data.contains(datum); // false, WAT???

Las matrices, por otro lado, tienen trivial hashCode (), se pueden usar como claves de tabla hash y aún son mutables.

Set<int[]> data = new HashSet<int[]>(67);
int[] datum = new int[]{1, 2};
data.add(datum);
System.out.println(data.contains(datum)); //true
datum[0] = 78;
System.out.println(data.contains(datum)); //true
//PROFIT!!!
Basilevs
fuente
3
No hay una matriz allí. El int[]tipo de matriz.
@MichaelT, ese es el punto.
Basilevs
¿Por qué los votos negativos? El respondedor hizo mucho trabajo. ¿Hay algo mal?
Tom Au
3
@TomAu la pregunta es sobre el diseño de la matriz en java, y su naturaleza de objeto psuedo y las decisiones de diseño detrás de esa elección (para hacer que la matriz solo use igualdad referencial en lugar de una igualdad profunda). Esta respuesta, por otro lado, intenta presentar una solución de código sobre cómo se podría reescribir el código, que no es lo que plantea la pregunta.
@MichaelT, esto ilustra el dominio de la lógica de igualdad basada en la identidad sobre el estado dependiente.
Basilevs