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

7

Estaba trabajando con un HashSet el otro día, lo cual está escrito en la especificación:

  

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

Estaba usando char[] en HashSet hasta que me di cuenta de que, según este contrato, ¡no era mejor que un ArrayList ! Ya que está usando .equals() no reemplazado, mis arreglos solo serán revisados para la igualdad de referencia, lo que no es particularmente útil. Sé que Arrays.equals() existe, pero eso no ayuda cuando uno usa colecciones como HashSet .

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

    
pregunta Azar 15.05.2014 - 13:15

4 respuestas

7

Hubo una decisión de diseño que tomar temprano en Java:

  

¿Son matrices primitivas? o son objetos?

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

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

Las matrices también son divertidas porque pueden contener primitivas ( int , char , double , etc ... mientras que las otras colecciones solo pueden contener Objetos. Mire, por ejemplo, a java.util.Arrays y la fea de los métodos iguales. Esto se puso como un después de pensarlo. 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 que están 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 costa de romper principalmente el modelo de objetos.

Hubo una compensación temprana en el sistema entre la flexibilidad y algunos resultados. El rendimiento ganó y la falta de flexibilidad quedó envuelta en las distintas colecciones. Los arreglos en Java son un objeto de implementación delgada sobre un tipo primitivo (originalmente) destinado a trabajar con el sistema cuando lo necesite.

En su mayor parte, los arreglos en bruto eran cosas que parece que los diseñadores originales intentaron ignorar y guardar solo en el sistema. Y querían que fuera rápido (Java temprano tenía algunos problemas con la velocidad). Era una verruga en el diseño que los arreglos no son buenos Arreglos, pero es uno que se necesitaba cuando se quería exponer algo lo más cerca posible del sistema. En este sentido, los lenguajes contemporáneos de Java temprano también tienen esta verruga, no se puede hacer un .equals() en la matriz de C ++.

Java y C ++ tomaron la misma ruta para las matrices: una biblioteca externa que realiza las operaciones necesarias en las matrices en lugar de las matrices ... y sugiere a los codificadores que usen mejores tipos nativos, a menos que realmente saber qué están haciendo y por qué lo están haciendo de esa manera.

Por lo tanto, el enfoque que implanta .equals en una matriz es incorrecto, pero es el mismo error que conocían los codificadores provenientes de C ++. Así que eligió lo menos incorrecto en términos de rendimiento: déjelo como la implementación de Objeto: dos Objetos son iguales si y solo si se refieren al mismo objeto.

Necesitas que la matriz sea una estructura similar a la primitiva para poder comunicarte 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, un Objeto. Así que es más una primitiva con algunos hacks de objetos en el lado y algunos límites de comprobación.

    
respondido por el user40980 15.05.2014 - 19:12
3

En Java, las matrices son pseudo-objetos. Las referencias de objetos pueden contener matrices, y sí tienen los métodos estándar de objetos, pero son muy ligeros en comparación con una colección verdadera. Las matrices hacen lo suficiente para cumplir con el contrato de un Objeto y usan las implementaciones predeterminadas de equals , hashCode y toString de forma deliberada.

Considera un Object[] . Un elemento de esta matriz puede ser cualquier cosa que se ajuste a un objeto, que incluya otra matriz. Podría ser una caja primitiva, un zócalo, cualquier cosa. ¿Qué significa la igualdad en ese caso? Bueno, depende de lo que realmente está en la matriz. Eso no es algo conocido en el caso general cuando el lenguaje estaba siendo diseñado. La igualdad se define tanto por la propia matriz como por su contenido .

Esta es la razón por la que existe una clase de ayuda Arrays que tiene métodos para calcular la igualdad (incluidos los iguales profundos), códigos hash, etc. Sin embargo, esos métodos están bien definidos en lo que respecta 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 mejores tipos de seguridad, contratos mejor definidos y, en general, son más fáciles de usar que los arreglos.

    
respondido por el user22815 15.05.2014 - 17:10
0

La dificultad fundamental con las matrices que anulan equals es que una variable de un tipo como int[] se puede usar en al menos tres formas fundamentalmente diferentes, y el significado de equals debe 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 puede compartirse libremente con un código que no la modifique.

  2. ... puede encapsular la propiedad exclusiva de un contenedor de número entero que puede ser mutado a voluntad por su propietario.

  3. ... puede identificar un contenedor de número entero que otra entidad está utilizando para encapsular su estado, y por lo tanto, sirve de conexión con el estado de esa otra entidad.

Si una clase tiene un campo int[] foo que se usa para cualquiera de los dos primeros propósitos, entonces las instancias x y y deberían considerar x.foo y y.foo como encapsulando el mismo estado si tienen la misma secuencia de números; sin embargo, si el campo se utiliza para el tercer propósito, x.foo y y.foo solo encapsularán el mismo estado si identifican la misma matriz [es decir, son iguales de referencia]. Si Java hubiera incluido diferentes tipos para los tres usos anteriores, y si equals tomó un parámetro que identifica cómo se estaba utilizando la referencia, entonces habría sido apropiado que int[] usara la igualdad de secuencia para los dos primeros usos y la igualdad de referencia para el tercero. Sin embargo, no existe tal mecanismo.

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

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

  2. Una referencia a una matriz que no cambia que se puede compartir y que identifica cosas que son propiedad de 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 artículos de propiedad exclusiva.

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

  6. Una referencia que identifica una matriz que pertenece a otra entidad.

En los casos 1, 3 y 4, dos referencias de matriz deben considerarse iguales si los elementos correspondientes son "igual al 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 que equals se comporte con sensatez con los tipos agregados, necesitan tener alguna forma de saber cómo se usarán las referencias. Desafortunadamente, el sistema de tipos de Java no tiene forma de indicar eso.

    
respondido por el supercat 14.06.2014 - 18:44
-2

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

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???

Por otro lado, las matrices tienen un código hash trivial (), se pueden usar como claves de tabla hash y todavía 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!!!
    
respondido por el Basilevs 15.05.2014 - 16:17

Lea otras preguntas en las etiquetas