JavaRush /Java Blog /Random-IT /Analisi di domande e risposte da interviste per sviluppat...

Analisi di domande e risposte da interviste per sviluppatori Java. Parte 12

Pubblicato nel gruppo Random-IT
Ciao! Sapere è potere. Più conoscenze hai prima del tuo primo colloquio, più ti sentirai sicuro. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 1Con una buona conoscenza, sarà difficile confonderti e allo stesso tempo sarai in grado di sorprendere piacevolmente il tuo intervistatore. Pertanto, oggi, senza ulteriori indugi, continueremo a rafforzare la tua base teorica esaminando oltre 250 domande per uno sviluppatore Java . Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 2

103. Quali sono le regole per verificare le eccezioni in eredità?

Se ho capito bene la domanda, stanno chiedendo informazioni sulle regole per lavorare con le eccezioni durante l'ereditarietà e 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 nel metodo superclasse/interfaccia.
Cioè, se abbiamo una certa interfaccia Animal con un metodo che lancia IOException :
public  interface Animal {
   void voice() throws IOException;
}
Nell'implementazione di questa interfaccia, non possiamo lanciare un'eccezione più generale (ad esempio, Exception , Throwable ), ma possiamo sostituirla con un'eccezione discendente, come FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • Il costruttore della sottoclasse deve includere nel suo blocco dei lanci tutte le classi di eccezioni lanciate dal costruttore della superclasse che viene chiamato quando viene creato l'oggetto.
Supponiamo che il costruttore della classe Animal generi molte eccezioni:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Poi l’erede della classe dovrà indicarli anche nel costruttore:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Oppure, come nel caso dei metodi, è possibile specificare non le stesse eccezioni, ma quelle più generali. Nel nostro caso basterà specificare un'eccezione più generale - Exception , poiché questa è l'antenato comune di tutte e tre le eccezioni considerate:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Potresti scrivere il codice per quando il blocco finale non verrà eseguito?

Innanzitutto, ricordiamo cos'è finalmente . In precedenza, abbiamo esaminato il meccanismo per catturare le eccezioni: il blocco try delinea l'area di cattura, mentre i blocchi catch sono il codice che funzionerà quando viene lanciata un'eccezione specifica. Infine è il terzo blocco di codice dopo finalmente che è intercambiabile con catch ma non si esclude a vicenda. L'essenza di questo blocco è che il codice in esso contenuto funziona sempre, indipendentemente dal risultato del try o catch (indipendentemente dal fatto che sia stata lanciata o meno un'eccezione). I casi di suo fallimento sono molto rari e sono anormali. Il caso più semplice di fallimento è quando nel codice sopra viene chiamato il metodo System.exit(0) , che termina il programma (lo spegne):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
Ci sono anche alcune altre situazioni in cui alla fine non funzionerà:
  • Chiusura anomala del programma causata da problemi critici del sistema, oppure dalla caduta di qualche errore che manderà in crash l'applicazione (un esempio di errore può essere lo stesso StackOwerflowError che si verifica quando la memoria dello stack va in overflow).
  • Quando il filo del demone passa attraverso la ry...finalmente si blocca e parallelamente a questo termina il programma. Dopotutto, il thread demone è un thread per azioni in background, ovvero non è prioritario e obbligatorio e l'applicazione non aspetterà che il suo lavoro finisca.
  • Il ciclo infinito più banale, in try or catch , una volta in cui il flusso rimarrà lì per sempre:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

Questa domanda è molto popolare nelle interviste per i principianti, quindi vale la pena ricordare un paio di queste situazioni eccezionali. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 3

105. Scrivi un esempio di gestione di più eccezioni in un blocco catch

1) Forse la domanda è stata posta in modo errato. Per quanto ho capito, questa domanda si riferisce a più catture per un blocco try :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
Se si verifica un'eccezione in un blocco try , i blocchi catch tentano alternativamente di catturarla dall'alto verso il basso. Se un determinato blocco catch riesce, ottiene il diritto di gestire l'eccezione, mentre il resto dei blocchi sottostanti non lo sarà più in grado di provare a coglierlo ed elaborarlo a modo loro. Pertanto, le eccezioni più ristrette vengono posizionate più in alto nella catena di blocchi catch , mentre le eccezioni più ampie vengono posizionate più in basso. Ad esempio, se nel nostro primo blocco catch viene catturata un'eccezione della classe Exception , le eccezioni verificate non potranno entrare nei blocchi rimanenti (i blocchi rimanenti con discendenti Exception saranno assolutamente inutili). 2) La domanda è stata posta correttamente, in questo caso la nostra elaborazione sarà simile alla seguente:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
Dopo aver catturato un'eccezione tramite catch , proviamo a scoprirne il tipo specifico tramite il metodo exampleof , che viene utilizzato per verificare se un oggetto appartiene a un determinato tipo, in modo da poterlo successivamente restringere a questo tipo senza conseguenze negative. Entrambi gli approcci considerati possono essere utilizzati nella stessa situazione, ma ho detto che la domanda non è corretta perché non definirei buona la seconda opzione e non l'ho mai vista nella mia pratica, mentre allo stesso tempo il primo metodo con multicatch ha ricevuto ampia diffusione attenzione, diffusione. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 4

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

L'ho già usato più volte sopra, ma ripeterò comunque questa parola chiave: lanciare . Esempio di utilizzo (forzatura di un'eccezione):
throw new NullPointerException();

107. Il metodo main può lanciare un'eccezione Throws? Se sì, dove verrà trasferito?

Innanzitutto voglio sottolineare che main non è altro che un normale metodo e sì, viene chiamato dalla macchina virtuale per avviare l'esecuzione del programma, ma oltre a questo può essere chiamato da qualsiasi altro codice. Cioè, è anche soggetto alle consuete regole per specificare le eccezioni controllate dopo i lanci :
public static void main(String[] args) throws IOException {
Di conseguenza, possono verificarsi anche eccezioni. Se main non è stato chiamato in qualche metodo, ma è stato avviato come punto di avvio del programma, l'eccezione generata verrà gestita dall'interceptor .UncaughtExceptionHandler . Questo gestore è uno per thread (ovvero un gestore in ogni thread). Se necessario, puoi creare il tuo gestore e impostarlo utilizzando il metodo setDefaultUncaughtExceptionHandler chiamato sull'oggetto Thread .

Multithreading

Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 5

108. Quali strumenti conosci per lavorare con il multithreading?

Strumenti di base/di base per l'utilizzo del multithreading in Java:
  • Sincronizzato è un meccanismo per chiudere (bloccare) un metodo/blocco quando un thread vi accede, da altri thread.
  • Volatile è un meccanismo per garantire un accesso coerente a una variabile da parte di thread diversi, ovvero, con la presenza di questo modificatore su una variabile, tutte le operazioni di assegnazione e lettura devono essere atomiche. In altre parole, i thread non copieranno questa variabile nella memoria locale e non la modificheranno, ma cambieranno il suo valore originale.
Maggiori informazioni su volatili qui .
  • Runnable è un'interfaccia che può essere implementata (in particolare, il suo metodo run) in una determinata classe:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
E dopo aver creato un oggetto di questa classe, puoi iniziare un nuovo thread impostando questo oggetto nel costruttore del nuovo oggetto Thread e chiamando il suo metodo start() :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
Il metodo start esegue il metodo run() implementato in un thread separato.
  • Thread è una classe, che eredita dalla quale (pur sovrascrivendo il metodo run ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
E creando un oggetto di questa classe e avviandolo utilizzando il metodo start() , lanceremo così un nuovo thread:
new CustomThread().start();
  • La concorrenza è un pacchetto con strumenti per lavorare in un ambiente multi-thread.
Consiste in:
  • Raccolte simultanee : un insieme di raccolte specializzate per lavorare in un ambiente multi-thread.
  • Code : code specializzate per un ambiente multi-thread (bloccante e non bloccante).
  • I sincronizzatori sono utilità specializzate per lavorare in un ambiente multi-thread.
  • Gli esecutori sono meccanismi per la creazione di pool di thread.
  • Blocchi : meccanismi di sincronizzazione dei thread (più flessibili di quelli standard: sincronizzato, attendi, notifica, notificaTutto).
  • Gli atomici sono classi ottimizzate per l'esecuzione multi-thread; ogni operazione è atomica.
Maggiori informazioni sul pacchetto simultaneo qui .

109. Parliamo di sincronizzazione tra thread. A cosa servono i metodi wait(), notify() - notifyAll() join() utilizzati?

Per quanto ho capito la domanda, la sincronizzazione tra thread riguarda il modificatore chiave - sincronizzato . Questo modificatore può essere posizionato direttamente accanto al blocco:
synchronized (Main.class) {
   // некоторая логика
}
Oppure direttamente nella firma del metodo:
public synchronized void move() {
   // некоторая логика}
Come ho detto prima, sincronizzato è un meccanismo che consente di chiudere un blocco/metodo da altri thread quando un thread è già entrato in esso. Pensa a un blocco/metodo come a una stanza. Qualche ruscello, arrivato ad essa, entrerà e la bloccherà, altri ruscelli, venuti nella stanza e vedendo che è chiusa, aspetteranno vicino ad essa finché non sarà libera. Dopo aver svolto i propri compiti, il primo thread lascia la stanza e rilascia la chiave. E non per niente ho parlato costantemente della chiave, perché esiste davvero. Questo è un oggetto speciale che ha uno stato occupato/libero. Questo oggetto è allegato ad ogni oggetto Java, quindi quando si utilizza un blocco sincronizzato dobbiamo indicare tra parentesi l'oggetto su cui vogliamo chiudere la porta:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
Puoi anche utilizzare un mutex di classe, come ho fatto nel primo esempio ( Main.class ). Quando utilizziamo sincronizzato su un metodo, non specifichiamo l'oggetto su cui vogliamo chiudere, giusto? In questo caso, per un metodo non statico, si chiuderà sul mutex di this object , ovvero l'oggetto corrente di questa classe. Quello statico si chiuderà sul mutex della classe corrente ( this.getClass(); ). Puoi leggere di più sul mutex qui . Bene, leggi informazioni sulla sincronizzazione qui . Wait() è un metodo che rilascia il mutex e mette il thread corrente in modalità standby, 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 dovrebbe essere libero e cosa dovrebbe aspettarsi). Si noti inoltre che questo è un metodo della classe Object . Più precisamente, non uno, ma addirittura tre:
  • Wait() - mette il thread corrente in modalità di attesa finché un altro thread non chiama il metodo notify() o notifyAll() per questo oggetto (parleremo di questi metodi più tardi).

  • Wait (timeout lungo) : mette il thread corrente in modalità di attesa finché un altro thread non chiama il metodo notify() o notifyAll() su questo oggetto o finché non scade il timeout specificato .

  • Wait (timeout lungo, int nanos) - simile al precedente, solo nanos consente di specificare i nanosecondi (impostazione del tempo più precisa).

  • Notify() è un metodo che ti consente di riattivare un thread casuale del blocco di sincronizzazione corrente. Ancora una volta, può essere chiamato solo in un blocco o metodo sincronizzato (dopotutto, in altri posti non avrà nessuno da sbloccare).

  • NotifyAll() è un metodo che riattiva tutti i thread in attesa sul monitor corrente (utilizzato anche solo in un blocco o metodo sincronizzato ).

110. Come fermare il flusso?

La prima cosa da dire è che quando il metodo run() viene eseguito completamente , il thread viene automaticamente distrutto. Ma a volte è necessario ucciderlo prima del previsto, prima che questo metodo venga completato. Quindi cosa dovremmo fare allora? Forse l' oggetto Thread dovrebbe avere un metodo stop() ? Non importa come sia! Questo metodo è considerato obsoleto e può causare arresti anomali del sistema. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 6E allora? Ci sono due modi per farlo: il primo è usare 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é non si ferma completamente:
public class CustomThread extends Thread {
private boolean isActive;

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

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
Quando si utilizza il metodo stopRunning() , il flag interno diventa false e il metodo run interrompe l'esecuzione. Eseguiamolo in main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
Di conseguenza, vedremo qualcosa di simile nella console:
Inizio dell'esecuzione 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 il thread sta eseguendo una logica... Fine dell'esecuzione del programma Il thread è interrotto!
Ciò significa che il nostro thread ha funzionato, ha inviato un certo numero di messaggi alla console ed è stato interrotto con successo. Noto che il numero di messaggi in uscita varia da esecuzione a esecuzione; a volte il thread aggiuntivo non ha nemmeno prodotto nulla. Come ho notato, questo dipende dal tempo di sospensione del thread principale, più lungo è, minori sono le possibilità che il thread aggiuntivo non generi nulla. Con un tempo di sospensione di 1 ms, i messaggi non vengono quasi mai emessi, ma se lo imposti su 20 ms, funziona quasi sempre. Forse, quando il tempo stringe, il thread semplicemente non ha il tempo di avviarsi e iniziare il suo lavoro, e viene immediatamente interrotto. Il secondo modo è utilizzare il metodo interrotto() sull'oggetto Thread , che restituisce il valore del flag di interruzione interno (questo flag è falso per impostazione predefinita ) e l'altro metodo interrupt() , che imposta questo flag su true (quando questo flag è vero , il thread dovrebbe interrompere il suo lavoro). Diamo un'occhiata ad un esempio:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Esegui in main :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
Il risultato dell'esecuzione sarà lo stesso del primo caso, ma mi piace di più questo approccio: scriviamo meno codice e utilizziamo più funzionalità standard già pronte. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 7È lì che ci fermeremo oggi.Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 12 - 8
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION