Fuochi d'artificio! Essere un programmatore non è facile. Devi imparare costantemente, imparare sempre qualcosa di nuovo. Ma, come in ogni altra attività, la cosa più difficile è iniziare, fare il primo passo verso il proprio obiettivo. E poiché sei seduto su questo sito e stai leggendo questo articolo, hai completato il primo passaggio. Ciò significa che ora devi muoverti intenzionalmente verso il tuo obiettivo, senza rallentare o spegnerti lungo il percorso. Se ho capito bene, il tuo obiettivo è diventare uno sviluppatore Java o migliorare le tue conoscenze se lo sei. Se è così, allora sei nel posto giusto, perché continueremo ad analizzare un ampio elenco di oltre 250 domande per interviste agli sviluppatori Java. Continuiamo!
Collezioni
84. Parlaci degli iteratori e del loro utilizzo
Le raccolte sono uno degli argomenti preferiti in qualsiasi colloquio con uno sviluppatore Java e, quando parlano della gerarchia delle raccolte, i candidati spesso affermano che inizia con l' interfaccia Raccolta . Ma questo non è vero, perché sopra questa interfaccia ce n'è un'altra: Iterable . Questa interfaccia rappresenta il metodo iterator() , che consente di chiamare un oggetto Iterator per la raccolta corrente. E cos'è esattamente questo oggetto Iterator ? Un Iterator è un oggetto che offre la possibilità di spostarsi attraverso una raccolta ed eseguire l'iterazione sugli elementi senza che l'utente debba conoscere l'implementazione di una particolare raccolta. Cioè, questo è una sorta di puntatore agli elementi della collezione, che, per così dire, guarda a un certo posto al suo interno. L'iteratore ha i seguenti metodi:- hasNext() - restituisce true se c'è un elemento situato dopo il puntatore (questo metodo permette di scoprire se è stata raggiunta la fine della raccolta);
- next() - restituisce l'elemento successivo dopo il puntatore. Se non ce n'è, viene lanciata NoSuchElementException . Cioè, prima di utilizzare questo metodo, è meglio assicurarsi che l'elemento esista - utilizzando hasNext() ;
- rimuovi() - rimuove l'ultimo elemento ricevuto dalla raccolta utilizzando il metodo next() . Se next() non è mai stato chiamato prima di rimuovere() , verrà lanciata un'eccezione - IllegalStateException ;
- forEachRemaining(<Consumer>) - esegue l'azione passata con ciascun elemento della raccolta (il metodo è apparso in Java 8).
List<String> list = new ArrayList<>();
list.add("Hello ");
list.add("World, ");
list.add("It's ");
list.add("Amigo!");
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();
iterator.remove();
}
System.out.println(list.size());
La console visualizzerà:
0
Ciò significa che la rimozione degli elementi ha avuto successo. Una volta ottenuto un iteratore, potremmo utilizzare un metodo per stampare tutti gli elementi sullo schermo:
iterator.forEachRemaining(x -> System.out.print(x));
Ma dopo questo, l'iteratore diventerebbe inadatto per un ulteriore utilizzo, poiché attraverserebbe l'intero elenco e un iteratore regolare non dispone di metodi per il backtracking. Qui ci avviciniamo gradualmente a LinkedList , vale a dire al suo metodo listIterator() , che restituisce un tipo modernizzato di iteratore - ListIterator . Oltre ai metodi iteratori regolari (standard), questo ne ha altri aggiuntivi:
- add(<Element>) - inserisce un nuovo elemento nella lista;
- hasPrevious() - restituisce true se c'è un elemento situato prima del puntatore (se c'è un elemento precedente);
- nextIndex() - restituisce l'indice nell'elenco dell'elemento successivo dopo il puntatore;
- previous() - restituisce l'elemento precedente (fino al puntatore);
- previousIndex() - restituisce l'indice dell'elemento precedente;
- set(<Element>) - Sostituisce l'ultimo elemento restituito dai metodi next() o previous() .
85. Qual è la gerarchia delle raccolte in Java Collection Framework?
Esistono due gerarchie di raccolte in Java. La prima gerarchia è la gerarchia della Collezione stessa con la seguente struttura: Essa, a sua volta, è suddivisa nelle seguenti sottoraccolte:- Set è un'interfaccia che descrive una struttura di dati come un insieme contenente elementi unici (non ripetitivi) non ordinati. L'interfaccia ha implementazioni standard: TreeSet , HashSet e LinkedHashSet .
- List è un'interfaccia che descrive una struttura dati che memorizza una sequenza ordinata di oggetti. Le istanze contenute in un Elenco possono essere inserite ed eliminate tramite il loro indice in questa raccolta (analogamente a un array, ma con ridimensionamento dinamico). L'interfaccia ha implementazioni standard: ArrayList , Vector ( considerato obsoleto e attualmente non utilizzato ) e LinkedList .
- Queue è un'interfaccia che descrive una struttura dati che memorizza gli elementi sotto forma di una coda che segue la regola FIFO: First In First Out . L'interfaccia ha le seguenti implementazioni standard: LinkedList (sì, implementa anche Queue ) e PriotityQueue .
86. Qual è la struttura interna di un ArrayList?
ArrayList è simile a un array, ma con la capacità di espandersi dinamicamente. Cosa significa? Il fatto è che ArrayList funziona sulla base di un array regolare, ovvero memorizza gli elementi in un array interno (la sua dimensione predefinita è 10 celle). Quando l'array interno è pieno, viene creato un nuovo array, la cui dimensione è determinata dalla formula:<размерТекущегоМассива> * 3 / 2 + 1
Cioè, se la dimensione del nostro array è 10, la dimensione di quello nuovo sarà: 10 * 3 / 2 + 1 = 16. Successivamente, tutti i valori del primo (vecchio) array vengono copiati al suo interno utilizzando il comando metodo nativo System.arraycopy () e il primo array viene eliminato. In realtà, questo è il modo in cui viene implementata l'estensibilità dinamica di ArrayList . Diamo un'occhiata ai metodi ArrayList più utilizzati : 1. add(<Elelement>) - aggiunge un elemento alla fine dell'array (all'ultima cella vuota) e prima controlla se c'è spazio in questo array. Se non è presente, viene creato un nuovo array in cui vengono copiati gli elementi. La complessità logaritmica di questa operazione è O(1). Esiste un metodo simile: add(<Index>,<Elelement>) . Aggiunge un elemento non alla fine dell'elenco (array), ma a una cella specifica con l'indice fornito come argomento. In questo caso, la complessità logaritmica varierà a seconda di dove viene aggiunta:
- se questo fosse all'incirca l'inizio della lista, la complessità logaritmica sarà prossima a O(N), perché tutti gli elementi situati a destra di quello nuovo dovranno essere spostati di una cella a destra;
- se l'elemento è inserito al centro - O(N/2) perché dobbiamo spostare solo la metà degli elementi dell'elenco di una cella a destra.
87. Qual è la struttura interna di LinkedList?
Se ArrayList contiene elementi in un array interno, LinkedList è sotto forma di un elenco doppiamente collegato. Ciò significa che ogni elemento contiene un collegamento all'elemento precedente ( previous ) e a quello successivo ( next ). Il primo elemento non ha un collegamento con il precedente (è il primo), ma è considerato l'inizio della lista, e la LinkedList ha un collegamento direttamente con esso. L'ultimo elemento, infatti, non ha un elemento successivo, è la coda della lista, e quindi c'è un collegamento diretto ad esso nella LinkedList stessa . Pertanto, la complessità logaritmica dell'accesso alla testa o alla coda di una lista è O(1). In ArrayList, quando l'elenco è cresciuto, l'array interno è aumentato, ma qui tutto avviene in modo più semplice: quando si aggiunge un elemento, un paio di collegamenti cambiano semplicemente. Diamo un'occhiata ad alcuni dei metodi LinkedlList più utilizzati : 1. add(<Elelement>) - aggiunta alla fine dell'elenco, ad es. dopo l'ultimo elemento (5) verrà aggiunto un collegamento al nuovo elemento come successivo . Il nuovo elemento avrà un collegamento all'ultimo (5) come elemento precedente . La complessità logaritmica di tale operazione sarà O(1), poiché è necessario solo un collegamento all'ultimo elemento e, come ricorderete, la coda ha un collegamento diretto da LinkedList e la complessità logaritmica per accedervi è minima. 2. add(<Index>,<Elelement>) - aggiunge un elemento per indice. Quando si aggiunge un elemento, ad esempio, al centro di un elenco, gli elementi dalla testa e dalla coda (su entrambi i lati) vengono prima ripetuti finché non viene trovata la posizione desiderata. Se vogliamo inserire un elemento tra il terzo e il quarto (nella figura sopra), allora durante la ricerca del posto giusto, il collegamento successivo del terzo elemento punterà già a quello nuovo. Per quello nuovo, il collegamento precedente punterà al terzo. Di conseguenza, il collegamento del quarto elemento - precedente - punterà già al nuovo elemento, e il collegamento successivo del nuovo elemento punterà al quarto elemento: La complessità logaritmica di questo metodo dipenderà dall'indice dato al nuovo elemento:- se è vicino alla testa o alla coda, si avvicinerà a O(1), poiché in realtà non sarà necessario iterare sugli elementi;
- se è vicino al centro, allora O(N/2) - gli elementi della testa e della coda verranno ordinati simultaneamente finché non viene trovato l'elemento richiesto.
88. Qual è la struttura interna di una HashMap?
Forse una delle domande più popolari quando si intervista uno sviluppatore Java. HashMap v funziona con coppie chiave-valore . Come vengono archiviati all'interno dello stesso HashMapv ? All'interno della HashMap c'è una serie di nodi:Node<K,V>[] table
Per impostazione predefinita, la dimensione dell'array è 16 e raddoppia ogni volta che viene riempita di elementi (quando viene raggiunta LOAD_FACTOR - una certa percentuale di pienezza, per impostazione predefinita è 0.75 ). Ogni nodo memorizza un hash della chiave, una chiave, un valore e un collegamento all'elemento successivo: in realtà, "collegamento all'elemento successivo" significa che abbiamo a che fare con una lista collegata singolarmente, in cui ogni elemento contiene un collegamento a il prossimo. Cioè, HashMap memorizza i dati in una serie di elenchi collegati singolarmente. Ma lo noterò subito: quando una cella dell'array della tabella ha un collegamento a un elenco simile concatenato singolarmente composto da più di un elemento, questo non va bene. Questo fenomeno è chiamato collisione . Ma prima le cose principali. Vediamo come viene salvata una nuova coppia utilizzando il metodo put . Per prima cosa viene preso l'hachCode() della chiave. Pertanto, affinché hashmap funzioni correttamente , è necessario prendere classi in cui questo metodo viene sovrascritto come chiavi. Questo codice hash viene quindi utilizzato nel metodo interno - hash() - per determinare il numero all'interno della dimensione dell'array della tabella . Successivamente, utilizzando il numero ricevuto, si accede a una cella specifica dell'array della tabella . Qui abbiamo due casi:
- La cella è vuota: al suo interno è memorizzato il nuovo valore del nodo .
- La cella non è vuota: viene confrontato il valore delle chiavi. Se sono uguali, il nuovo valore Nodo sovrascrive quello vecchio, se non sono uguali, si accede all'elemento successivo e lo si confronta con la sua chiave... E così via fino a quando il nuovo valore sovrascrive quello vecchio o raggiunge la fine del elenco collegato singolarmente e verrà memorizzato lì come ultimo elemento.
GO TO FULL VERSION