CodeGym /Java Blog /Random-IT /Esplorazione di domande e risposte da un colloquio di lav...
John Squirrels
Livello 41
San Francisco

Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java. Parte 11

Pubblicato nel gruppo Random-IT
CIAO! Anche la nave più veloce semplicemente andrà alla deriva sulle onde se non ha una rotta. Se stai leggendo questo articolo proprio adesso, hai sicuramente un obiettivo. La cosa principale è non andare fuori rotta e puntare invece su tutto per diventare uno sviluppatore Java. Oggi voglio continuare la mia revisione delle domande per gli sviluppatori Java per aiutarti a colmare alcune delle tue lacune teoriche. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 11 - 1

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 .

codice hash() :
  • 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.

In altre parole, getClass() restituisce l'identità specifica della classe, ma exampleof restituisce true anche se l'oggetto è solo un sottotipo, il che può darci maggiore flessibilità quando si utilizza il polimorfismo. Entrambi gli approcci sono promettenti se si capisce esattamente come funzionano e li si applica nei posti giusti.

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. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 11 - 2Per utilizzare questo metodo, è necessario implementare l' interfaccia del marcatore clonabile :
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. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 11 - 3Le eccezioni sono problemi che possono verificarsi in fase di esecuzione e in fase di compilazione. Questi problemi di solito sorgono nel codice che scriviamo come sviluppatori. Ciò significa che queste eccezioni sono più prevedibili e dipendono maggiormente da noi. Al contrario, gli errori sono più casuali e più indipendenti da noi. Dipendono invece da problemi nel sistema su cui è in esecuzione la nostra applicazione.

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: Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 11 - 4 qui, in cima alla gerarchia, vediamo la classe Throwable , che è l'antenato generale della gerarchia delle eccezioni e a sua volta si divide in:
  • Errori : problemi critici e non controllati.
  • Eccezioni : eccezioni che possono essere controllate.
Le eccezioni sono suddivise in varie eccezioni di runtime non controllate e varie eccezioni 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:
Ops! Qualcosa è andato storto =(
Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 11 - 5Bene, per oggi è tutto! Ci vediamo nella prossima parte!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION