
97. Si applicano regole quando si sovrascrive equals()?
Quando si sovrascrive il metodo equals(), è necessario rispettare le seguenti regole:-
riflessività : per qualsiasi valore x , x.equals(x) deve sempre restituire true (dove x != null ).
-
simmetria : per qualsiasi valore x e y , x.equals(y) deve restituire true solo se y.equals(x) restituisce true .
-
transitività — per qualsiasi valore x , yez , se x.equals(y) restituisce true e anche y.equals (z) restituisce true , allora x.equals(z) deve restituire true .
-
coerenza : per qualsiasi valore x e y , chiamare ripetutamente x.equals(y) restituirà sempre lo stesso valore purché i campi utilizzati per confrontare i due oggetti non siano cambiati tra ogni chiamata.
-
confronto nullo : per qualsiasi valore x , la chiamata a x.equals(null) deve restituire false .
98. Cosa succede se non sovrascrivi equals() e hashCode()?
In questo caso, hashCode() restituirà un numero generato in base all'indirizzo della cella di memoria in cui è archiviato l'oggetto. In altre parole, quando il metodo hashCode() originale viene chiamato su due oggetti con esattamente gli stessi campi, il risultato sarà diverso (perché sono archiviati in posizioni di memoria diverse). Il metodo originale equals() confronta i riferimenti, cioè indica se i riferimenti puntano allo stesso oggetto. In altre parole, il confronto utilizza l' operatore == e restituirà sempre false per oggetti diversi, anche quando i loro campi sono identici. true viene restituito solo quando si confrontano i riferimenti allo stesso oggetto. A volte ha senso non ignorare questi metodi. Ad esempio, se desideri che tutti gli oggetti di una determinata classe siano unici: sovrascrivere questi metodi potrebbe solo rovinare la garanzia esistente di codici hash univoci. L'importante è comprendere le sfumature di questi metodi, che siano ignorati o meno, e utilizzare l'approccio richiesto dalla situazione.99. Perché il requisito di simmetria è soddisfatto solo se x.equals(y) restituisce vero?
Questa domanda è un po' strana. Se l'oggetto A è uguale all'oggetto B, allora l'oggetto B è uguale all'oggetto A. Se B non è uguale all'oggetto A, allora come potrebbe essere possibile il contrario? Questo è buon senso.100. Cos'è una collisione HashCode? Come lo gestisci?
Una collisione HashCode si verifica quando due oggetti diversi hanno lo stesso HashCode . Come è possibile? Bene, il codice hash viene mappato su un numero intero, che ha un intervallo compreso tra -2147483648 e 2147483647. Cioè, può essere uno dei circa 4 miliardi di numeri interi diversi. Questa gamma è enorme ma non infinita. Ciò significa che ci sono situazioni in cui due oggetti completamente diversi possono avere lo stesso codice hash. È altamente improbabile, ma è possibile. Una funzione hash mal implementata può rendere più frequenti i codici hash identici restituendo numeri in un intervallo ristretto, aumentando così la possibilità di collisioni. Per ridurre le collisioni, è necessario disporre di una buona implementazione del metodo HashCode che distribuisca uniformemente i valori e riduca al minimo la possibilità di valori ripetuti.101. Cosa succede se cambia il valore di un elemento partecipante al contratto hashCode?
Se un elemento coinvolto nel calcolo di un codice hash cambia, allora dovrebbe cambiare il codice hash dell'oggetto (se la funzione hash è buona). Ecco perché dovresti utilizzare oggetti immutabili come chiavi in una HashMap , poiché il loro stato interno (campi) non può essere modificato dopo la creazione. E ne consegue che il loro codice hash cambia dopo la creazione. Se utilizzi un oggetto mutabile come chiave, quando i campi dell'oggetto cambiano, il suo codice hash cambierà e potresti perdere la coppia chiave-valore corrispondente in HashMap . Dopotutto, verrà archiviato nel bucket associato al codice hash originale, ma dopo che l'oggetto verrà modificato, lo cercherai in un bucket diverso.102. Scrivi i metodi equals() e hashCode() per una classe Student che ha campi String name e int age.
public class Student {
int age;
String name;
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Student student = (Student) o;
if (this.age != student.age) {
return false;
}
return this.name != null ? this.name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = this.age;
result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
return result;
}
}
equivale():
-
Per prima cosa confrontiamo direttamente i riferimenti, perché se i riferimenti puntano allo stesso oggetto, che senso ha continuare a verificare l'uguaglianza? Sappiamo già che il risultato sarà vero .
-
Controlliamo la presenza di null e se i tipi di classe sono gli stessi perché se il parametro è null o di un altro tipo, gli oggetti non possono essere uguali e il risultato deve essere false .
-
Trasformiamo il parametro nello stesso tipo (dopo tutto, cosa succede se è un oggetto del tipo genitore).
-
Confrontiamo i campi primitivi ( sarà sufficiente un confronto utilizzando =! ). Se non sono uguali, restituiamo false .
-
Controlliamo il campo non primitivo per vedere se è null e utilizzando il metodo equals (la classe String sovrascrive il metodo, quindi eseguirà il confronto correttamente). Se entrambi i campi sono null o uguale a restituisce true , interrompiamo il controllo e il metodo restituisce true .
-
Impostiamo il valore iniziale del codice hash uguale al valore del campo età dell'oggetto .
-
Moltiplichiamo il codice hash corrente per 31 (per una maggiore diffusione dei valori) e quindi aggiungiamo il codice hash del campo String non primitivo (se non è nullo).
-
Restituiamo il risultato.
-
Sovrascrivere il metodo in questo modo significa che gli oggetti con lo stesso nome e valori int restituiranno sempre lo stesso codice hash.
103. Qual è la differenza tra l'utilizzo di "if (obj istanza di Student)" e "if (getClass() == obj.getClass())"?
Diamo un'occhiata a cosa fa ciascuna espressione:-
exampleof controlla se il riferimento all'oggetto sul lato sinistro è un'istanza del tipo sul lato destro o uno dei suoi sottotipi.
-
"getClass() == ..." controlla se i tipi sono gli stessi.
104. Fornisci una breve descrizione del metodo clone().
Il metodo clone() appartiene alla classe Object . Il suo scopo è creare e restituire un clone (copia) dell'oggetto corrente.
Student implements Cloneable
E sovrascrivi il metodo clone() stesso:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
Dopotutto, è protetto nella classe Object , cioè sarà visibile solo all'interno della classe Student e non visibile alle classi esterne.
105. Quali considerazioni speciali devi tenere a mente riguardo al metodo clone() e alle variabili di riferimento in un oggetto?
Quando gli oggetti vengono clonati, vengono copiati solo i valori primitivi e il valore dei riferimenti agli oggetti. Ciò significa che se un oggetto ha un campo che fa riferimento a un altro oggetto, verrà clonato solo il riferimento: quest'altro oggetto a cui si fa riferimento non verrà clonato. Questa è quella che viene chiamata una copia superficiale. E se avessi bisogno di una copia completa, in cui ogni oggetto nidificato viene clonato? Come ci si assicura che queste non siano semplici copie di riferimenti, ma invece copie a tutti gli effetti di oggetti distinti che occupano indirizzi di memoria distinti nell'heap? In realtà, è tutto abbastanza semplice: per ogni classe a cui si fa riferimento internamente, è necessario sovrascrivere il metodo clone() e aggiungere l' interfaccia del marcatore Cloneable . Una volta fatto questo, l'operazione di clonazione non copierà i riferimenti agli oggetti esistenti, ma copierà invece gli oggetti referenziati, poiché ora hanno anche la possibilità di copiare se stessi.Eccezioni
106. Qual è la differenza tra un errore e un'eccezione?
Le eccezioni, così come gli errori, sono sottoclassi di Throwable . Tuttavia, hanno le loro differenze. L'errore indica un problema che si verifica principalmente a causa della mancanza di risorse di sistema. E la nostra applicazione non dovrebbe riscontrare questo tipo di problemi. Esempi di questi errori includono un arresto anomalo del sistema e un errore di memoria insufficiente. Gli errori si verificano principalmente in fase di esecuzione, poiché non sono controllati.
107. Qual è la differenza tra controllato, non controllato, eccezione, lanciata e lanciata?
Come ho detto prima, un'eccezione è un errore di runtime o di compilazione che si verifica nel codice scritto dallo sviluppatore (a causa di una situazione anomala). Checked è ciò che chiamiamo eccezioni che un metodo deve sempre gestire utilizzando il meccanismo try-catch o rethrow al metodo chiamante. La parola chiave Throws viene utilizzata nell'intestazione di un metodo per indicare le eccezioni che il metodo potrebbe generare. In altre parole, ci fornisce un meccanismo per lanciare eccezioni al metodo chiamante. Non è necessario gestire le eccezioni non controllate . Tendono ad essere meno prevedibili e meno probabili. Detto questo, puoi gestirli se vuoi. Usiamo Throw quando lanciamo manualmente un'eccezione, ad esempio:throw new Exception();
108. Qual è la gerarchia delle eccezioni?
La gerarchia delle eccezioni è molto estesa. C'è troppo da descrivere adeguatamente qui. Quindi, considereremo invece solo i suoi rami chiave:
- Errori : problemi critici e non controllati.
- Eccezioni : eccezioni che possono essere controllate.
109. Cosa sono le eccezioni verificate e non verificate?
Come ho detto prima:-
Le eccezioni selezionate sono eccezioni che devi in qualche modo gestire. Cioè, devi gestirli in un blocco try-catch o lanciarli nel metodo sopra. Per fare ciò, dopo aver elencato gli argomenti del metodo nella firma del metodo, utilizzare Throws <exception type> per indicare che il metodo può lanciare quell'eccezione. Questo è un po' come un avvertimento, che avvisa il metodo chiamante che deve assumersi la responsabilità di gestire quell'eccezione.
-
Non è necessario gestire le eccezioni non controllate , poiché non vengono controllate in fase di compilazione e di solito sono più imprevedibili. La loro principale differenza con le eccezioni controllate è che gestirle utilizzando un blocco try-catch o rilanciando è facoltativo anziché obbligatorio.
101. Scrivi un esempio in cui utilizzi un blocco try-catch per catturare e gestire un'eccezione.
try{ // Start of the try-catch block
throw new Exception(); // Manually throw an exception
} catch (Exception e) { // Exceptions of this type and its subtypes will be caught
System.out.println("Oops! Something went wrong =("); // Display the exception
}
102. Scrivi un esempio in cui catturi e gestisci le tue eccezioni personalizzate.
Innanzitutto, scriviamo la nostra classe di eccezione che eredita Exception e sovrascrive il suo costruttore che accetta un messaggio di errore come argomento:public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
}
Successivamente ne lanceremo manualmente uno e lo prenderemo proprio come abbiamo fatto nell'esempio della domanda precedente:
try{
throw new CustomException("Oops! Something went wrong =(");
} catch (CustomException e) {
System.out.println(e.getMessage());
}
Ancora una volta, quando eseguiamo il nostro codice, otteniamo il seguente output:

GO TO FULL VERSION