Ciao di nuovo a tutti! Continuiamo a cercare risposte alle domande degli sviluppatori Java junior, di livello medio e senior. Le domande sono super interessanti. Personalmente mi piace analizzarli, perché mi aiuta a trovare le lacune nella mia conoscenza teorica, e a volte nei posti più inaspettati. Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 2 - 1La parte precedente può essere trovata in questo articolo . Ma prima di iniziare, voglio ricordarti che:
  1. Tralascerò le domande che si sovrappongono a questa serie di articoli per non duplicare inutilmente le informazioni. Consiglio di leggere questi articoli poiché coprono le domande più comuni (popolari) dell'intervista Java Core.
  2. Potrei descrivere le risposte in modo più dettagliato, ma non lo farò, perché altrimenti ogni risposta potrebbe durare per l'intero articolo. E nessuno ti chiederà quel livello di dettaglio in nessun colloquio di lavoro.
Se vuoi lascio i link per l'approfondimento. Voliamo!

11. Assegnare un nome a tutti i metodi della classe Object

La classe Object ha 11 metodi:
  1. Class<?> getClass() — ottiene la classe dell'oggetto corrente;

  2. int hashCode() : ottiene il codice hash dell'oggetto corrente;

  3. boolean equals(Object obj) — confronta l'oggetto corrente con un altro oggetto;

  4. Object clone() : crea e restituisce una copia dell'oggetto corrente;

  5. String toString() : ottiene la rappresentazione in stringa dell'oggetto;

  6. void notify() — sveglia un thread in attesa sul monitor di questo oggetto (la scelta del thread è casuale);

  7. void notifyAll() — riattiva tutti i thread in attesa sul monitor di questo oggetto;

  8. void wait() — fa attendere il thread corrente sul monitor corrente (congela il thread corrente) finché una chiamata notify o notifyAll non riattiva il thread (funziona solo in un blocco sincronizzato);

  9. void wait(long timeout) — fa sì che il thread corrente attenda sul monitor corrente (sul blocco attualmente sincronizzato), ma con un timeout per uscire dallo stato di attesa (o ancora, finché una chiamata notify o notifyAll non riattiva il thread);

  10. void wait(long timeout, int nanos) — questo metodo è come il metodo precedente, ma con un timeout più preciso;

  11. void finalize() — questo metodo viene chiamato (finalmente) prima che l'oggetto venga rimosso dal garbage collector. Viene utilizzato per ripulire le risorse acquisite.

Per utilizzare correttamente i metodi hashCode , equals , clone , toString e finalize , è necessario sovrascriverli in base alle specifiche dell'attività corrente.

12. Qual è la differenza tra try-with-resources e try-catch-finally quando si lavora con le risorse?

In genere, quando si utilizza try-catch-finally , il blocco finale viene utilizzato per chiudere le risorse. Java 7 introduce la nuova istruzione try-with-resources . È analogo a try-catch-finalmente per liberare risorse, ma più compatto e leggibile. Ricordiamo come appare try-catch-finalmente :
String text = "some text......";
BufferedWriter bufferedWriter = null;
try {
   bufferedWriter = new BufferedWriter(new FileWriter("someFileName"));
   bufferedWriter.write(text);
} catch (IOException e) {
   e.printStackTrace();
} finally {
   try {
       bufferedWriter.close();
   } catch (IOException e) {
       e.printStackTrace();
   }
}
Ora riscriviamo questo codice, ma utilizzando try-with-resources :
String text = "some text......";
try(BufferedWriter bufferedWriter =new BufferedWriter(new FileWriter("someFileName"))) {
   bufferedWriter.write(text);
} catch (IOException e) {
   e.printStackTrace();
}
Ora è in qualche modo più semplice, non credi? Oltre al codice più semplice, ci sono un paio di altri punti da notare:
  1. In try-with-resources , le risorse dichiarate tra parentesi (risorse che verranno chiuse) devono implementare l' interfaccia AutoCloseable e il suo unico metodo close() .

    Il metodo close viene eseguito in un implicito last block , altrimenti, come farebbe il programma a capire esattamente come chiudere la risorsa?

    Ma probabilmente raramente scriverai le tue implementazioni delle risorse e il loro metodo di chiusura.

  2. I blocchi vengono eseguiti in questo ordine:

    1. Il blocco prova .
    2. L'implicito infine blocca.
    3. Il blocco catch , che rileva le eccezioni che si verificano nei passaggi precedenti.
    4. Il blocco finale esplicito .

    Di norma, le eccezioni lanciate più in basso nell'elenco interrompono quelle lanciate più in alto.

Immagina di utilizzare un try-catch-finally e di ottenere un'eccezione nel blocco try . Quindi inizia immediatamente l'esecuzione del blocco catch specificato, in cui abbiamo scritto un'altra eccezione (ad esempio, con un messaggio che descrive l'errore in modo più dettagliato) e vuoi che il metodo lanci questa eccezione verso l'alto . Quindi viene eseguito il blocco last e viene generata anche un'eccezione. Ma questa volta diversa. Quale di queste due eccezioni genererà alla fine questo metodo? L'eccezione lanciata dal last block! Ma ora siamo arrivati ​​a un altro punto relativo al try-with-resources . Consideriamo come si comporta try-with-resources nella stessa situazione. Otteniamo un'eccezione nel blocco try quando proviamo a chiudere le risorse nel metodo close() , cioè nel blocco implicito finalmente . Quale di queste eccezioni verrà catturata dal blocco catch ? Quello lanciato dal blocco try ! L'eccezione dal blocco implicito final (dal metodo loss() ) verrà ignorata. Questo ignorare le eccezioni è anche chiamato soppressione delle eccezioni.

13. Cosa sono le operazioni bit a bit?

Le operazioni bit a bit sono operazioni su sequenze di bit. Includono operazioni logiche e spostamenti bit a bit. Operatori logici:
  • AND bit per bit : confronta i valori dei bit. Qualsiasi bit impostato su 0 (falso) imposta il bit corrispondente nel risultato su 0. Cioè, se un bit è 1 (vero) in entrambi i valori confrontati, anche il bit risultante sarà 1.

    Indicato come AND o &

    Esempio: 10111101 e 01100111 = 00100101

  • OR bit per bit : questa operazione è l'opposto della precedente. Qualsiasi bit impostato su 1 imposta il bit corrispondente nel risultato su 1. Di conseguenza, se il bit è 0 in entrambi i valori confrontati, anche il bit risultante sarà 0.

    Indicato come OR o |

    Esempio: 10100101 | 01100011 = 11100111

  • NOT bit per bit : questo operatore viene applicato a un singolo valore. Capovolge (inverte) i bit. Cioè, i bit che erano 1 diventano 0; e quelli che erano 0 diventano 1.

    Indicato come NOT o ~

    Esempio: ~10100101 = 01011010

  • OR esclusivo bit per bit : confronta i valori dei bit. Se entrambi i bit sono 1, il bit risultante è 0. Se entrambi i bit sono 0, il bit risultante è 0. In altre parole, affinché il bit risultante sia 1, solo uno dei bit deve essere 1 e l'altro bit deve essere 0.

    Indicato come XOR o ^

    Esempio: 10100101 ^ 01100011 = 11000110

Gli spostamenti bit a bit ( >> e << ) spostano i bit dell'operando nella direzione specificata, del numero di posizioni specificato. Le posizioni vacanti vengono riempite con zeri. Per esempio:
  1. 01100011 >> 4 = 00000110
  2. 01100011 << 3 = 00011000
L'eccezione è quando sposti un numero negativo a destra. Come ricorderete, il primo bit di un numero con segno indica il segno. Se questo bit è 1, il numero è negativo. Se si sposta un numero negativo, le posizioni libere non vengono riempite con zeri ma con unità, poiché il bit di segno deve essere preservato. Ad esempio: 10100010 >> 2 = 11101000 Detto questo, Java ha un ulteriore operatore di spostamento a destra senza segno (>>>). Questo operatore è analogo a >>, ma quando viene spostato, le posizioni libere vengono riempite con 0, indipendentemente dal fatto che l'operando sia un numero negativo o positivo. Ad esempio: 10100010 >>> 2 = 00101000 Maggiori informazioni sulle operazioni bit a bit qui . Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 2 - 2Puoi prendere il metodo hash() in HashMaps come esempio di spostamenti bit a bit in Java. Questo metodo viene utilizzato per determinare lo speciale hashcode interno della chiave: Esplorazione di domande e risposte da un colloquio di lavoro per una posizione di sviluppatore Java.  Parte 2 - 3questo metodo consente di distribuire uniformemente i dati in una HashMap, in modo da ridurre al minimo il numero di collisioni.

14. Quali oggetti immutabili standard ci sono in Java?

Un oggetto è immutabile se non consente il cambiamento dei suoi valori originali. Potrebbe avere metodi che restituiscono nuovi oggetti dello stesso tipo con valori diversi. Alcuni oggetti immutabili standard includono:
  • senza dubbio, il tipo immutabile più famoso di Java è String;
  • istanze delle classi wrapper che racchiudono tipi standard: Boolean, Character, Byte, Short, Integer, Long, Double, Float;
  • Oggetti BigInteger e BigDecimal, che vengono solitamente utilizzati per numeri particolarmente GRANDI;
  • Oggetti StackTraceElement che costituiscono una traccia dello stack (ad esempio, la traccia dello stack di un'eccezione);
  • un oggetto della classe File: può modificare i file, ma allo stesso tempo l'oggetto stesso rimane invariato;
  • UUID, che vengono spesso utilizzati per identificare in modo univoco gli elementi;
  • tutti gli oggetti delle classi nel pacchetto java.time;
  • Oggetti locali, utilizzati per identificare una regione geografica, politica o culturale.

15. Quali sono i vantaggi dell'oggetto immutabile rispetto agli oggetti ordinari?

  1. Gli oggetti immutabili sono sicuri da usare in un ambiente multithread . Fanno in modo che tu non debba preoccuparti della perdita di dati a causa delle condizioni di gara. Questo è diverso rispetto a quando lavori con oggetti ordinari. In tal caso, devi pensare e inventare buoni meccanismi quando usi l'oggetto in un ambiente parallelo.

  2. Gli oggetti immutabili sono utili come chiavi in ​​una mappa. Se usi un oggetto mutabile come chiave HashMap e poi lo stato dell'oggetto cambia, la struttura dei dati potrebbe confondersi: l'oggetto sarà ancora presente, ma se usi contieneKey(), potresti non trovarlo.

  3. Gli oggetti immutabili sono ottimi per archiviare dati immutabili (costanti) che non dovrebbero mai essere modificati mentre il programma è in esecuzione.

  4. Un altro vantaggio è l’atomicità del fallimento. Se un oggetto immutabile genera un'eccezione, non verrà lasciato in uno stato indesiderato (rotto).

  5. Queste classi sono facili da testare.

  6. Non sono necessari meccanismi aggiuntivi come un costruttore di copie o l'implementazione della clonazione di oggetti.

Domande sull'OOP

16. Quali sono i vantaggi dell'OOP in generale e rispetto alla programmazione procedurale?

Ok, vantaggi dell'OOP:
  1. È più facile scrivere applicazioni complesse utilizzando la programmazione orientata agli oggetti rispetto alla programmazione procedurale poiché tutto è suddiviso in piccoli moduli (oggetti che interagiscono tra loro) e, di conseguenza, la programmazione è ridotta alle relazioni tra oggetti.

  2. Le applicazioni scritte con OOP sono molto più facili da modificare (se i principi di progettazione vengono rispettati correttamente).

  3. Poiché sia ​​i dati che le operazioni sui dati formano una singola entità, non sono sparsi in tutta l'applicazione (come spesso accade nella programmazione procedurale).

  4. Il principio dell'incapsulamento protegge i dati più critici dall'utente.

  5. Lo stesso codice può essere riutilizzato con dati diversi perché le classi consentono di creare molti oggetti, ciascuno con i propri valori.

  6. L'ereditarietà e il polimorfismo consentono inoltre di riutilizzare ed estendere il codice esistente (invece di duplicare funzionalità simili).

  7. L'estensione di una domanda è più semplice che con un approccio procedurale.

  8. L'approccio OOP consente di astrarre i dettagli di implementazione.

17. Raccontaci quali svantaggi presenta l'OOP

Purtroppo esistono anche:
  1. L'OOP richiede molte conoscenze teoriche che devono essere padroneggiate prima di poter scrivere qualsiasi cosa.

  2. Le idee OOP non sono così facili da comprendere e applicare nella pratica (devi essere un piccolo filosofo in fondo).

  3. L'OOP riduce leggermente le prestazioni di un programma a causa della maggiore complessità del sistema.

  4. L'approccio OOP richiede più memoria poiché tutto è costituito da classi, interfacce, metodi, che occupano molta più memoria rispetto alle normali variabili.

  5. Il tempo necessario per l'analisi iniziale è maggiore rispetto a quello necessario per un approccio procedurale.

18. Cos'è il polimorfismo statico rispetto al polimorfismo dinamico?

Il polimorfismo consente agli oggetti della stessa classe o interfaccia di comportarsi in modo diverso. Esistono due tipi di polimorfismo, noti anche come legame precoce e tardivo. Polimorfismo statico o legame precoce:
  • si verifica in fase di compilazione (all'inizio del ciclo di vita del programma);
  • decide quale metodo eseguire in fase di compilazione;
  • l'overload del metodo è un esempio di polimorfismo statico;
  • l'associazione anticipata include metodi privati, statici e finali;
  • l'ereditarietà non è coinvolta nell'associazione anticipata;
  • il polimorfismo statico non coinvolge oggetti specifici, ma piuttosto informazioni sul tipo di classe che appare a sinistra del nome di una variabile.
Polimorfismo dinamico o legame tardivo:
  • si verifica in fase di esecuzione (mentre il programma è in esecuzione);
  • il polimorfismo dinamico decide quale implementazione specifica avrà un metodo in fase di esecuzione;
  • l'override del metodo è un esempio di polimorfismo dinamico;
  • l'associazione tardiva significa assegnare un oggetto specifico, un riferimento al suo tipo o alla sua superclasse;
  • l'ereditarietà è associata al polimorfismo dinamico.

19. Fornire una definizione del principio di astrazione nell'OOP

In OOP, l'astrazione è un modo per isolare un insieme di caratteristiche significative di un oggetto, escludendo dettagli insignificanti. Cioè, quando si progetta un programma con un approccio OOP, ci si concentra su modelli generali, senza entrare nei dettagli della loro implementazione. In Java, l'astrazione viene realizzata tramite interfacce . Ad esempio, hai un'auto e quella sarà un'interfaccia. E le varie interazioni con esso, ad esempio l'avvio del motore, il cambio di marcia, sono funzioni che utilizziamo senza approfondire i dettagli di implementazione. Infatti, quando guidi, non pensi esattamente a come il cambio svolge la sua funzione, o a come la chiave avvia il motore, o a come esattamente il volante fa girare le ruote. E se sostituisci l'implementazione di alcune funzionalità (ad esempio il motore), potresti anche non notarlo. Per te non importa: non approfondisci i dettagli dell'implementazione. Ciò che conta per te è che l'azione venga eseguita. In sostanza, questo sottrae i dettagli di implementazione. A questo punto ci fermiamo oggi: continua!