Codice hash di ArrayList che si contiene come elemento


38

Possiamo trovare il hashcodedi a listche si contiene come element?

So che questa è una cattiva pratica, ma è quello che ha chiesto l'intervistatore.

Quando ho eseguito il seguente codice genera un StackOverflowError:

public class Main {
    public static void main(String args[]) {
        ArrayList<ArrayList> a = new ArrayList();
        a.add(a);
        a.hashCode();
    }
}

Ora qui ho due domande:

  1. Perché c'è un StackOverflowError?
  2. È possibile trovare il codice hash in questo modo?

7
Perché aggiungi l'Elenco a se stesso. prova a.hashCode () senza l'istruzione add
Jens

Quando metti un oggetto in un arraylist, stai memorizzando il riferimento all'oggetto. Nel tuo caso stai mettendo una strega ArrayList è esso stesso riferimento.
Vishwa Ratna


Ok, ho capito perché c'è StackOverflow, qualcuno può aiutarmi a spiegare il problema numero 2- Come trovarlo
Joker

9
Come altri hanno risposto, questo non è possibile, per la stessa definizione Listdell'interfaccia, il hashCodedi un elenco dipende dai suoi membri. Dato che la lista è il suo membro, il suo codice hash dipende dal suo hashCode, che dipende dal suo hashCode... e così via, causando una ricorsione infinita e il punto in StackOverflowErrorcui ti imbatti. Ora la domanda è: perché hai bisogno di un elenco per contenere se stesso? Ti posso garantire che puoi ottenere qualsiasi cosa tu stia cercando di fare, in un modo migliore, senza richiedere un abbonamento ricorsivo come questo.
Alexander - Ripristina Monica

Risposte:


36

Il codice hash per Listimplementazioni conformi è stato specificato nell'interfaccia :

Restituisce il valore del codice hash per questo elenco. Il codice hash di un elenco è definito come il risultato del seguente calcolo:

 int hashCode = 1;
 for (E e : list)
     hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Ciò garantisce che ciò list1.equals(list2)implica che list1.hashCode()==list2.hashCode()per due elenchi list1e list2, come richiesto dal contratto generale di Object.hashCode().

Ciò non richiede che l'implementazione sia esattamente così (vedi Come calcolare il codice hash per un flusso allo stesso modo di List.hashCode () per un'alternativa), ma il codice hash corretto per un elenco contenente solo se stesso sarebbe essere un numero per il quale x == 31 + xdeve essere true, in altre parole, è impossibile calcolare un numero conforme.


1
@Holger, Eirc vuole sostituire il codice dell'intera funzione hashCode()da restituire 0. Questo risolve tecnicamente x == 31 + xma ignora il requisito che x deve essere almeno 1.
bxk21

4
@EricDuminil il punto della mia risposta è, il contratto descrive una logica che ArrayListimplementa letteralmente, portando a una ricorsione, ma non esiste nemmeno un'implementazione alternativa conforme. Si noti che ho pubblicato la mia risposta in un momento in cui l'OP ha già capito perché questa particolare implementazione porta a un StackOverflowError, che è stato affrontato in altre risposte. Quindi mi sono concentrato sull'impossibilità generale di un'implementazione conforme che si completa a tempo finito con un valore.
Holger

2
@pdem non importa se la specifica utilizza una descrizione prolissa di un algoritmo, una formula, uno pseudo codice o un codice effettivo. Come detto nella risposta, il codice fornito nelle specifiche non preclude implementazioni alternative in generale. La forma della specifica non dice nulla sull'esecuzione o meno dell'analisi. La frase della documentazione dell'interfaccia " Sebbene sia consentito che le liste contengano se stesse come elementi, si consiglia estrema cautela: i metodi uguali e hashCode non sono più ben definiti in tale lista " suggerisce che tale analisi sia avvenuta.
Holger

2
@pdem lo stai invertendo. Non ho mai detto che l'elenco deve essere uguale a causa del codice hash. Le liste sono uguali, per definizione. Arrays.asList("foo")è uguale a Collections.singletonList("foo"), è uguale a List.of("foo")è uguale a new ArrayList<>(List.of("foo")). Tutti questi elenchi sono uguali, punto. Quindi, poiché questi elenchi sono uguali, devono avere lo stesso codice hash. Non viceversa. Poiché devono avere lo stesso codice hash, deve essere ben definito. Indipendentemente da come lo hanno definito (di nuovo in Java 2), deve essere ben definito, per essere concordato da tutte le implementazioni.
Holger

2
@pdem esattamente, un'implementazione personalizzata che non implementa Listo ha un grande segnale di avvertimento "non mescolare con gli elenchi ordinari", confrontare con IdentityHashMape il suo " Questa classe non è un'implementazione della mappa per tutti gli usi! " avvertimento. Nel primo caso, stai già bene con l'implementazione Collectionma aggiungendo anche i metodi di accesso basati sull'indice di stile elenco. Quindi, non esiste alcun vincolo di uguaglianza, ma funziona ancora senza problemi con altri tipi di raccolta.
Holger

23

Scopri l'implementazione scheletrica del hashCodemetodo in AbstractListclasse.

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}

Per ogni elemento nell'elenco, questo chiama hashCode. Nel tuo elenco di casi ha se stesso come unico elemento. Ora questa chiamata non finisce mai. Il metodo si chiama ricorsivamente e la ricorsione continua a girare fino a quando non incontra il StackOverflowError. Quindi non puoi trovare in hashCodequesto modo.


Quindi la risposta è che non c'è modo di trovare il codice hash in questo modo?
Joker

3
Sì, a causa della condizione ricorsiva
Springe

Non solo, è definito in questo modo.
Chrylis

14

Hai definito un elenco (patologico) che contiene se stesso.

Perché c'è StackOverflowError?

Secondo il javadocs (cioè la specifica), l'hashcode di a Listè definito in funzione dell'hashcode di ciascuno dei suoi elementi. Dice:

"Il codice hash di un elenco è definito come il risultato del seguente calcolo:"

int hashCode = 1;
    for (E e : list)
         hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Quindi per calcolare l'hashcode di a, devi prima calcolare l'hashcode di a. Questo è infinitamente ricorsivo e porta rapidamente a un overflow dello stack.

È possibile trovare il codice hash in questo modo?

No. Se consideri la specifica algoritmica sopra in termini matematici, l'hashcode di un Listche contiene se stesso è una funzione non calcolabile . Non è possibile calcolarlo in questo modo (usando l'algoritmo sopra) o in altro modo .


Non so perché questa risposta sia inferiore alle altre due poiché in realtà risponde alle domande del PO con spiegazioni.
Neyt

1
@Neyt alcuni utenti leggono solo le risposte in alto.
Holger

8

No, la documentazione ha una risposta

La documentazione della struttura dell'elenco afferma esplicitamente:

Nota: anche se è consentito che gli elenchi si contengano come elementi, si consiglia estrema cautela: i metodi equals e hashCode non sono più ben definiti in tale elenco.

Non c'è molto altro da dire oltre a questo: secondo la specifica Java, non sarai in grado di calcolare hashCode per un elenco che contiene se stesso; altre risposte spiegano in dettaglio perché è così, ma il punto è che è noto e intenzionale.


1
Hai detto perché non rientra nelle specifiche, quindi spiega che non è un bug. Questa era la parte mancante dalle altre risposte.
pdem

3

La risposta di Ravindra fornisce una buona spiegazione per il punto 1. Per commentare la domanda 2:

È possibile trovare il codice hash in questo modo?

Qualcosa è circolare qui. Almeno uno di questi 2 deve essere sbagliato nel contesto di questo errore di overflow dello stack:

  • che il codice hash dell'elenco deve tenere conto di quelli dei suoi elementi
  • che è giusto che un elenco sia il suo elemento

Ora, poiché abbiamo a che fare con un ArrayList, il primo punto è stato risolto. In altre parole, forse hai bisogno di un'implementazione diversa per poter calcolare in modo significativo un codice hash di un elenco ricorsivo ... Si potrebbe estendere ArrayListe saltare l'aggiunta dei codici hash degli elementi, qualcosa di simile

for (E e : this)
  if(e == this) continue; //contrived rules
  hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

Usando una tale classe invece di ArrayList, potresti.

Con ArrayList, il secondo punto è sbagliato. Quindi se l'intervistatore intendesse "È possibile trovare il codice hash in questo modo (con un elenco di array)?" , quindi la risposta è no, perché è assurdo.


1
Il calcolo del codice hash viene mandato da Listcontratto . Nessuna implementazione valida può saltare se stessa. Dalle specifiche, puoi ricavare che se trovi un intnumero per il quale x == 31 + xè true, puoi implementare una scorciatoia valida ...
Holger

Non ho capito bene cosa stesse dicendo @Holger. Ma ci sono 2 Problemi con la soluzione: Primo: questo risolve il problema solo quando questo elenco è un elemento di se stesso e non se l'elenco in cui un elemento di un elemento di se stesso (strati più profondi di ricorsione) Secondo: Se l'elenco si è saltato da solo potrebbe essere uguale a un elenco vuoto.
Jonas Michel

1
@JonasMichel Non ho proprio proposto una soluzione. Sto solo filosoficamente dibattere intorno l'assurdità della domanda 2. Se si deve calcolare un codice hash per un tale elenco, allora non lavoro a meno che non rimuovere il vincolo di una lista di array (e rafforza Holger che dicendo che Listformalmente dettami la logica del codice hash che deve essere rispettata dalle implementazioni)
ernest_k

La mia (limitata) comprensione è che Listfornisce un calcolo del codice hash, ma che potremmo ignorarlo. L'unico vero requisito è quello degli hashcode generali: se gli oggetti sono uguali, gli hashcode devono essere uguali. Questa implementazione segue tale requisito.
Teepeemm

1
@Teepeemm Listè un'interfaccia. Definisce un contratto , non fornisce un'implementazione. Naturalmente, il contratto riguarda sia l'uguaglianza che il codice hash. Poiché il contratto generale di uguaglianza è che deve essere simmetrico, se si modifica un'implementazione di un elenco, è necessario modificare tutte le implementazioni di elenchi esistenti, altrimenti si spezzerebbe persino il contratto fondamentale di java.lang.Object.
Holger

1

Perché quando chiami la stessa funzione con dalla stessa funzione, creerà una condizione di ricorsione che non finisce mai. E per impedire questa operazione tornerà JAVAjava.lang.StackOverflowError

Di seguito è riportato un codice di esempio che spiega uno scenario simile:

public class RefTest {

    public static void main(String... strings) {
        RefTest rf = new RefTest();
        rf.message(message("test"));
    }

    public static String message2(String s){
        return message(s);
    }

    public static String message(String s){
        return message2(s);
    }

}   
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.