Perché gli array in Java non sovrascrivono equals ()?

7

Stavo lavorando con un HashSet l'altro giorno, che è scritto nella specifica:

[add()] adds the specified element e to this set if this set contains no element e2 such that (e==null ? e2==null : e.equals(e2))

Stavo usando char[] in HashSet fino a quando ho realizzato che, in base a questo contratto, non era migliore di ArrayList ! Dal momento che sta usando il% non sostituito da% co_de, i miei array verranno controllati solo per l'uguaglianza di riferimento, che non è particolarmente utile. So che .equals() esiste, ma questo non aiuta quando si utilizzano raccolte come Arrays.equals() .

Quindi la mia domanda è, perché gli array Java not non sovrascriveranno gli uguali?

    
posta Azar 15.05.2014 - 13:15
fonte

4 risposte

7

C'era una decisione progettuale da prendere in anticipo in Java:

Are arrays primitives? or are they Objects?

La risposta è, né davvero ... o entrambi, se la guardi in un altro modo. Funzionano abbastanza strettamente con il sistema stesso e il back-end del jvm.

Un esempio di questo è java.lang.System.arraycopy () metodo che deve prendere un array di qualsiasi tipo. Pertanto, l'array deve essere in grado di ereditare qualcosa e questo è un Oggetto. E arraycopy è un metodo nativo.

Gli array sono anche divertenti in quanto possono contenere primitive ( int , char , double , ecc ... mentre le altre collezioni possono contenere solo oggetti. Guarda, ad esempio, a java.util.Arrays e il brutto dei metodi equivalenti. dopo aver pensato. deepEquals (Object [], Object []) non è stato aggiunto fino a 1,5 mentre il resto della classe Arrays è stato aggiunto in 1.2.

Poiché questi oggetti sono matrici , ti permettono di fare alcune cose che sono in memoria o vicino al livello di memoria - qualcosa che Java spesso nasconde dal codificatore. Ciò consente che alcune cose vengano fatte più velocemente a scapito della rottura del modello di oggetto.

C'è stato un compromesso all'inizio del sistema tra la flessibilità e alcune prestazioni. Le prestazioni vinsero e la mancanza di flessibilità fu avvolta nelle varie collezioni. Le matrici in Java sono un oggetto implementato in modo sottile su un tipo primitivo (originariamente) progettato per funzionare con il sistema quando ne hai bisogno.

Per la maggior parte, gli array grezzi erano cose che sembra che i progettisti originali cercassero di ignorare e riporre solo nel sistema. E volevano che fosse veloce (presto Java aveva alcuni problemi con la velocità). Era una verruca sul design che gli array non fossero matrici carine, ma era necessario quando si voleva esporre qualcosa il più vicino possibile al sistema. Del resto, i linguaggi contemporanei dei primi Java hanno questa verruca - non si può fare un .equals() sull'array del C ++.

Sia Java che C ++ hanno preso lo stesso percorso per gli array: una libreria esterna che esegue le operazioni secondo necessità sugli array piuttosto che sugli array ... e suggerisce ai programmatori di utilizzare tipi nativi migliori, a meno che non realmente sapere cosa stanno facendo e perché lo stanno facendo in quel modo.

Quindi, l'approccio che impianta. equals in un array è sbagliato, ma è lo stesso che i codificatori provenienti da C ++ sapevano. Quindi scegli la cosa meno sbagliata in termini di prestazioni - lasciala come implementazione di Object: due oggetti sono uguali se e solo se si riferiscono allo stesso oggetto.

È necessario che l'array sia una struttura primitiva come la possibilità di comunicare con i binding nativi, il più vicino possibile al classico array C. Ma a differenza degli altri primitivi, è necessario che la matrice sia passata come riferimento e quindi come oggetto. Quindi è più di un primitivo con alcuni hack di oggetti sul lato e alcuni limiti di controllo.

    
risposta data 15.05.2014 - 19:12
fonte
3

In Java, gli array sono pseudo-oggetti. I riferimenti agli oggetti possono contenere array e hanno i metodi Object standard, ma sono molto leggeri rispetto a una raccolta vera. Gli array fanno abbastanza per soddisfare il contratto di un oggetto e utilizzano le implementazioni predefinite di equals , hashCode e toString in modo deliberato.

Considera un Object[] . Un elemento di questo array può essere qualsiasi cosa che si adatta a un oggetto, che include un altro array. Potrebbe essere una primitiva inscatolata, una presa di corrente, qualsiasi cosa. Cosa significa uguaglianza in questo caso? Bene, dipende da cosa è effettivamente nella matrice. Questo non è qualcosa di noto nel caso generale quando il linguaggio è stato progettato. L'uguaglianza è definita sia dalla matrice stessa che dal suo contenuto .

Questo è il motivo per cui esiste una classe helper Arrays che ha metodi per calcolare l'uguaglianza (compresi i deep equals), i codici hash, ecc. Tuttavia, questi metodi sono ben definiti per quanto riguarda ciò che fanno. Se hai bisogno di diverse funzionalità, scrivi il tuo metodo per confrontare due array per l'uguaglianza in base alle esigenze del tuo programma.

Pur non essendo una risposta rigorosa alla tua domanda, penso che sia importante dire che dovresti davvero usare le raccolte invece degli array. Converti in array solo quando si interfaccia con un'API che richiede array. In caso contrario, le raccolte offrono una migliore sicurezza del tipo, contratti più ben definiti e sono generalmente più facili da utilizzare rispetto agli array.

    
risposta data 15.05.2014 - 17:10
fonte
0

La difficoltà fondamentale con gli array che sovrascrivono equals è che una variabile di un tipo come int[] può essere utilizzata in almeno tre modi fondamentalmente diversi, e il significato di equals dovrebbe variare a seconda dell'utilizzo. In particolare, un campo di tipo int[] ...

  1. ... può incapsulare una sequenza di valori in una matrice che non sarà mai modificata, ma può essere condivisa liberamente con un codice che non la modificherà.

  2. ... può incapsulare la proprietà esclusiva di un contenitore contenente interi che può essere mutato a piacimento dal suo proprietario.

  3. ... potrebbe identificare un contenitore contenente interi che altre entità stanno usando per incapsulare il suo stato, e quindi servire come una connessione allo stato di quell'altra entità.

Se una classe ha un int[] campo foo che viene utilizzato per uno dei primi due scopi, allora le istanze x e y dovrebbero considerare x.foo e y.foo come incapsulare lo stesso stato se tengono la stessa sequenza di numeri; se il campo viene utilizzato per il terzo scopo, tuttavia, x.foo e y.foo incapsulerebbero lo stesso stato solo se identificano lo stesso array [vale a dire sono uguali a riferimento]. Se Java avesse incluso diversi tipi per i tre usi sopra, e se equals avesse preso un parametro che identificasse come veniva usato il riferimento, sarebbe stato appropriato per int[] usare l'uguaglianza di sequenza per i primi due usi e l'uguaglianza di riferimento per il terzo. Tuttavia, questo meccanismo non esiste.

Nota anche che il caso int[] era il tipo più semplice di array. Per gli array che contengono riferimenti a classi diverse da Object o tipi di array, ci sarebbero ulteriori possibilità.

  1. Un riferimento a un array immutabile condivisibile che incapsula cose che non cambieranno mai.

  2. Un riferimento a un array immutabile condivisibile che identifica le cose di proprietà di altre entità.

  3. Un riferimento a un array di proprietà esclusiva che incapsula i riferimenti a cose che non cambieranno mai.

  4. Un riferimento a un array di proprietà esclusiva che incapsula i riferimenti a elementi di proprietà esclusiva.

  5. Un riferimento a un array di proprietà esclusiva che identifica le cose di proprietà di altre entità.

  6. Un riferimento che identifica una matrice di proprietà di un'altra entità.

Nei casi 1, 3 e 4, due riferimenti di matrice dovrebbero essere considerati uguali se gli articoli corrispondenti sono "valore-uguale". Nei casi 2 e 5, due riferimenti di matrice devono essere considerati uguali se identificano la stessa sequenza di oggetti. Nel caso 6, due riferimenti di matrice dovrebbero essere considerati uguali solo se identificano la stessa matrice.

Affinché equals si comporti in modo ragionevole con i tipi aggregati, è necessario avere un qualche modo per sapere come verranno utilizzati i riferimenti. Sfortunatamente, il sistema di tipi di Java non ha modo di indicarlo.

    
risposta data 14.06.2014 - 18:44
fonte
-2

Sostituire l'array equals() e hashCode() per dipendere dal contenuto li renderebbe simili alle raccolte - tipi mutabili con% non costanti% co_de. I tipi che cambiano hashCode() si comportano male quando sono memorizzati in tabelle hash e altre applicazioni che si basano sul valore fisso 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???

Le matrici in altra mano hanno hashCode () banale, possono essere usate come chiavi della tabella hash e sono comunque modificabili.

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!!!
    
risposta data 15.05.2014 - 16:17
fonte

Leggi altre domande sui tag