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 12

Pubblicato nel gruppo Random-IT
CIAO! Sapere è potere. Più conoscenze avrai durante il tuo primo colloquio, più ti sentirai sicuro. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 12 - 1Se porti un grande cervello pieno di conoscenza, il tuo intervistatore avrà difficoltà a confonderti e probabilmente rimarrà piacevolmente sorpreso. Quindi, senza ulteriori indugi, oggi continueremo a rafforzare la tua base teorica esaminando le domande per uno sviluppatore Java.

103. Quali regole si applicano al controllo delle eccezioni durante l'ereditarietà?

Se ho capito bene la domanda, stanno chiedendo regole per lavorare con le eccezioni durante l'ereditarietà. Le norme rilevanti sono le seguenti:
  • Un metodo sovrascritto o implementato in un discendente/implementazione non può generare eccezioni verificate che sono più in alto nella gerarchia rispetto alle eccezioni in un metodo superclasse/interfaccia.
Ad esempio, supponiamo di avere un'interfaccia Animal con un metodo che lancia una IOException :
public interface Animal {
   void speak() throws IOException;
}
Quando implementiamo questa interfaccia, non possiamo esporre un'eccezione lanciabile più generale (ad esempio Exception , Throwable ), ma possiamo sostituire l'eccezione esistente con una sottoclasse, come FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • La clausola Throws del costruttore della sottoclasse deve includere tutte le classi di eccezione lanciate dal costruttore della superclasse chiamato per creare l'oggetto.
Supponiamo che il costruttore della classe Animal lanci molte eccezioni:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Quindi anche un costruttore di sottoclassi deve lanciarli:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Oppure, come con i metodi, è possibile specificare eccezioni diverse e più generali. Nel nostro caso possiamo indicare Exception , perché è più generale ed è un antenato comune a tutte e tre le eccezioni indicate nella superclasse:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Puoi scrivere del codice in cui il blocco finale non viene eseguito?

Per prima cosa, ricordiamo cos'è finalmente . In precedenza, abbiamo esaminato il meccanismo di cattura delle eccezioni: un blocco try designa dove verranno catturate le eccezioni, e i blocchi catch sono il codice che verrà invocato quando viene catturata un'eccezione corrispondente. Un terzo blocco di codice contrassegnato dalla parola chiave final può sostituire o venire dopo i blocchi catch. L'idea alla base di questo blocco è che il suo codice viene sempre eseguito indipendentemente da ciò che accade in un blocco try o catch (indipendentemente dal fatto che vi sia o meno un'eccezione). I casi in cui questo blocco non viene eseguito sono rari e sono anomali. L'esempio più semplice è quando System.exit(0) viene chiamato prima del blocco final, terminando così il programma:
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Ci sono anche altre situazioni in cui il blocco finalmente non verrà eseguito:
  • Ad esempio, una chiusura anomala del programma causata da errori critici del sistema o qualche errore che causa l'arresto anomalo dell'applicazione (ad esempio, StackOverflowError , che si verifica quando lo stack dell'applicazione è in overflow).

  • Un'altra situazione è quando un thread daemon entra in un blocco try-finally , ma poi il thread principale del programma termina. Dopo tutto, i thread del demone servono per il lavoro in background che non è ad alta priorità o obbligatorio, quindi l'applicazione non aspetterà che finiscano.

  • L'esempio più insensato è un ciclo infinito all'interno di un blocco try o catch : una volta all'interno, un thread rimarrà bloccato lì per sempre:

    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
Questa domanda è molto popolare nelle interviste agli sviluppatori junior, quindi è una buona idea ricordare un paio di queste situazioni eccezionali. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 12 - 2

105. Scrivi un esempio in cui gestisci più eccezioni in un singolo blocco catch.

1) Non sono sicuro che la domanda sia stata posta correttamente. Per quanto ho capito, questa domanda si riferisce a diversi blocchi di cattura e un singolo tentativo :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
Se viene lanciata un'eccezione in un blocco try , i blocchi catch associati tentano di rilevarla, in sequenza dall'alto verso il basso. Una volta che l'eccezione corrisponde a uno dei blocchi catch , tutti i blocchi rimanenti non saranno più in grado di rilevarla e gestirla. Tutto ciò significa che le eccezioni più ristrette sono disposte sopra quelle più generali nell'insieme dei blocchi catch . Ad esempio, se il nostro primo blocco catch rileva la classe Exception , tutti i blocchi successivi non rileveranno le eccezioni selezionate (ovvero, i blocchi rimanenti con sottoclassi di Exception saranno completamente inutili). 2) O forse la domanda è stata posta correttamente. In tal caso, potremmo gestire le eccezioni come segue:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
Dopo aver utilizzato catch per intercettare un'eccezione, proviamo a scoprirne il tipo specifico utilizzando l' operatore istanzaof , che controlla se un oggetto appartiene a un determinato tipo. Ciò ci consente di eseguire con sicurezza una conversione di tipo restringente senza timore di conseguenze negative. Potremmo applicare entrambi gli approcci nella stessa situazione. Ho espresso dubbi sulla questione solo perché non definirei la seconda opzione un buon approccio. Nella mia esperienza non l'ho mai riscontrato e il primo approccio che coinvolge più blocchi di cattura è molto diffuso.

106. Quale operatore ti consente di forzare la generazione di un'eccezione? Scrivi un esempio

L'ho già utilizzata più volte negli esempi sopra, ma la ripeterò ancora una volta: la parola chiave Throw . Esempio di lancio manuale di un'eccezione:
throw new NullPointerException();

107. Il metodo main può generare un'eccezione? Se è così, allora dove va?

Prima di tutto, voglio sottolineare che il metodo principale non è altro che un metodo ordinario. Sì, viene chiamato dalla macchina virtuale per avviare l'esecuzione di un programma, ma oltre a ciò può essere chiamato da qualsiasi altro codice. Ciò significa che è anche soggetto alle consuete regole sull'indicazione delle eccezioni checkate dopo la parola chiave Throws :
public static void main(String[] args) throws IOException {
Di conseguenza, può generare eccezioni. Quando main viene chiamato come punto di partenza del programma (anziché con qualche altro metodo), qualsiasi eccezione generata verrà gestita da UncaughtExceptionHandler . Ogni thread ha uno di questi gestori (ovvero, esiste uno di questi gestori in ogni thread). Se necessario, puoi creare il tuo gestore e impostarlo chiamando il metodo public static void main(String[] args) Throws IOException {setDefaultUncaughtExceptionHandler metodo su un public static void main(String[] args) Throws IOException {Thread object.

Multithreading

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

108. Quali meccanismi per lavorare in un ambiente multithread conosci?

I meccanismi di base per il multithreading in Java sono:
  • La parola chiave sincronizzata , che è un modo con cui un thread blocca un metodo/blocco quando entra, impedendo ad altri thread di entrare.

  • La parola chiave volatile garantisce un accesso coerente a una variabile a cui accedono thread diversi. Cioè, quando questo modificatore viene applicato a una variabile, tutte le operazioni per assegnare e leggere quella variabile diventano atomiche. In altre parole, i thread non copieranno la variabile nella memoria locale e non la modificheranno. Cambieranno il suo valore originale.

  • Eseguibile : possiamo implementare questa interfaccia (che consiste in un singolo metodo run() ) in alcune classi:

    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    E una volta creato un oggetto di quella classe, possiamo iniziare un nuovo thread passando il nostro oggetto al costruttore Thread e quindi chiamando il metodo start() :

    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    Il metodo start esegue il metodo run() implementato su un thread separato.

  • Thread : possiamo ereditare questa classe e sovrascrivere il suo metodo di esecuzione :

    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    Possiamo iniziare un nuovo thread creando un oggetto di questa classe e quindi chiamando il metodo start() :

    new CustomThread().start();

  • Concorrenza : si tratta di un pacchetto di strumenti per lavorare in un ambiente multithread.

    Consiste in:

    • Raccolte simultanee : si tratta di una raccolta di raccolte create esplicitamente per lavorare in un ambiente multithread.

    • Code : code specializzate per un ambiente multithread (bloccante e non bloccante).

    • Sincronizzatori : si tratta di utilità specializzate per lavorare in un ambiente multithread.

    • Esecutori : meccanismi per la creazione di pool di thread.

    • Blocchi : meccanismi di sincronizzazione dei thread più flessibili rispetto a quelli standard (sincronizzato, attendi, notifica, notifica tutto).

    • Atomics — Classi ottimizzate per il multithreading. Ciascuna delle loro operazioni è atomica.

109. Parlaci della sincronizzazione tra i thread. A cosa servono i metodi wait(), notify(), notifyAll() e join()?

La sincronizzazione tra i thread riguarda la parola chiave sincronizzata . Questo modificatore può essere posizionato direttamente sul blocco:
synchronized (Main.class) {
   // Some logic
}
Oppure direttamente nella firma del metodo:
public synchronized void move() {
   // Some logic }
Come ho detto prima, sincronizzato è un meccanismo per bloccare un blocco/metodo su altri thread una volta che un thread entra. Pensiamo a un blocco/metodo di codice come a una stanza. Un filo si avvicina alla stanza, vi entra e chiude la porta con la sua chiave. Quando altri thread si avvicinano alla stanza, vedono che la porta è chiusa a chiave e aspettano nelle vicinanze finché la stanza non diventa disponibile. Una volta che il primo thread ha terminato i suoi affari nella stanza, apre la porta, lascia la stanza e rilascia la chiave. Ho menzionato una chiave un paio di volte per un motivo: perché esiste davvero qualcosa di analogo. Questo è un oggetto speciale che ha uno stato occupato/libero. Ogni oggetto in Java ha un oggetto di questo tipo, quindi quando utilizziamo il blocco sincronizzato , dobbiamo utilizzare le parentesi per indicare l'oggetto il cui mutex verrà bloccato:
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
Possiamo anche utilizzare un mutex associato ad una classe, come ho fatto nel primo esempio ( Main.class ). Dopotutto, quando utilizziamo sincronizzato su un metodo, non specifichiamo l'oggetto che vogliamo bloccare, giusto? In questo caso, per i metodi non statici, il mutex che verrà bloccato è l' oggetto this , cioè l'oggetto corrente della classe. Per i metodi statici, il mutex associato alla classe corrente ( this.getClass(); ) è bloccato. wait() è un metodo che libera il mutex e mette il thread corrente nello stato di attesa, come se fosse collegato al monitor corrente (qualcosa come un'ancora). Per questo motivo questo metodo può essere chiamato solo da un blocco o metodo sincronizzato . Altrimenti cosa aspetterebbe e cosa verrebbe rilasciato?). Si noti inoltre che questo è un metodo della classe Object . Beh, non uno, ma tre:
  • wait() mette il thread corrente nello stato di attesa finché un altro thread non chiama il metodo notify() o notifyAll() su questo oggetto (parleremo di questi metodi più avanti).

  • wait(long timeout) mette il thread corrente nello stato di attesa finché un altro thread non chiama il metodo notify() o notifyAll() su questo oggetto o finché non scade l'intervallo di tempo specificato da timeout.

  • wait(long timeout, int nanos) è come il metodo precedente, ma qui nanos ti consente di specificare i nanosecondi (un timeout più preciso).

  • notify() ti consente di riattivare un thread casuale in attesa sul blocco di sincronizzazione corrente. Ancora una volta, questo metodo può essere chiamato solo in un blocco o metodo sincronizzato (dopotutto, in altri posti non ci sarebbe nessuno a svegliarsi).

  • notifyAll() risveglia tutti i thread in attesa sul monitor corrente (utilizzato anche solo in un blocco o metodo sincronizzato ).

110. Come fermiamo un thread?

La prima cosa da dire qui è che quando run() viene eseguito fino al completamento, il thread termina automaticamente. Ma a volte vogliamo interrompere un thread prima del previsto, prima che il metodo sia completato. Quindi cosa facciamo? Forse possiamo usare il metodo stop() sull'oggetto Thread ? No! Questo metodo è deprecato e potrebbe causare arresti anomali del sistema. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 12 - 4E allora? Ci sono due modi per farlo: in primo luogo , utilizzare il flag booleano interno. Diamo un'occhiata a un esempio. Abbiamo la nostra implementazione di un thread che dovrebbe visualizzare una determinata frase sullo schermo finché il thread non si interrompe completamente:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
La chiamata al metodo stopRunningThread() imposta il flag interno su false, provocando la terminazione del metodo run() . Chiamiamolo in main :
System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
Di conseguenza, vedremo qualcosa di simile nella console:
Avvio del programma... Il thread sta eseguendo una logica... Il thread sta eseguendo una logica... Il thread sta eseguendo una logica... Il thread sta eseguendo una logica... Il thread sta eseguendo una logica... Il thread sta eseguendo una logica... Arresto del programma... Il thread si è fermato!
Ciò significa che il nostro thread è iniziato, ha stampato diversi messaggi sulla console e poi si è interrotto con successo. Tieni presente che il numero di messaggi visualizzati varia da lancio a lancio. E a volte il thread ausiliario potrebbe non visualizzare nulla. Il comportamento specifico dipende da quanto tempo dorme il thread principale. Più a lungo dorme, meno è probabile che il thread ausiliario non riesca a visualizzare nulla. Con un tempo di sospensione di 1 ms non vedrai quasi mai i messaggi. Ma se lo imposti su 20 ms, i messaggi verranno quasi sempre visualizzati. Quando il tempo di sospensione è breve, il thread semplicemente non ha il tempo di avviarsi e svolgere il proprio lavoro. Invece, viene immediatamente interrotto. Un secondo modo è utilizzare il metodo interrotto() sull'oggetto Thread . Restituisce il valore del flag interno interrotto, che è falso per impostazione predefinita. Oppure il suo metodo interrupt() , che imposta questo flag su true (quando il flag è true , il thread dovrebbe interrompere l'esecuzione). Diamo un'occhiata ad un esempio:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
In esecuzione in main :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
Il risultato dell'esecuzione è lo stesso del primo caso, ma preferisco questo approccio: abbiamo scritto meno codice e utilizzato più funzionalità standard già pronte. Bene, per oggi è tutto!
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION