Colloquio Java: esercizi su collezioni, oggetti mutabili ed eccezioni

Colloquio Java con esercizi verificati: chiavi mutabili, viste e copie di liste, aggiornamenti parziali ed eccezioni. Prevedi lo stato e difendi la correzione.

Author: PracHub

Published: 10/11/2026

Colloquio Java: esercizi su collezioni, oggetti mutabili ed eccezioni

October 11, 2026

Quick Overview

Esperimenti originali Java17 su identità degli ordini, alias nelle collezioni e batch falliti; risultati riproducibili e contratti ufficiali distinti.

Software EngineerFree

Una mappa contiene ancora un ordine, ma una ricerca con la chiave modificata non lo trova più. Una lista non modificabile cambia davanti al chiamante. Un metodo lancia un'eccezione, però il primo aggiornamento è già avvenuto. Puoi spiegare i tre problemi seguendo identità, riferimenti e stato, prima di scegliere una collezione.

Questo percorso prepara un colloquio Java attraverso un piccolo sistema di ordini inventato. Prova anche Solve restaurant path and order event tasks, motivando cosa identifica un evento e quale stato deve sopravvivere a un errore.

Confine delle prove: i contratti citati provengono dalla documentazione ufficiale Oracle; il dominio e gli esperimenti sono originali. Il programma è stato eseguito con OpenJDK 17.0.20.1. Le pagine di riferimento sono Java SE 25, ma gli esempi usano caratteristiche disponibili in Java 17. Non utilizziamo resoconti di candidati per attribuire frequenze o procedure a un'azienda.

Identità dell'ordine separata dalla quantità modificabile

Il contratto prima della struttura dati

Nel caso didattico, un ordine è identificato da negozio e numero: IT/1 e FR/1 sono diversi. La quantità di una riga può cambiare senza creare un nuovo ordine. Decidi questa distinzione prima di scrivere equals e hashCode: includere la quantità nell'identità cambia il significato della deduplicazione.

Chiedi se un secondo evento con la stessa identità sostituisce il valore, viene ignorato oppure produce un conflitto. Nel nostro primo esperimento sostituisce il valore. È una politica dell'esercizio, non una proprietà commerciale di HashMap. Se il requisito fosse conservare tutte le versioni, servirebbe un modello differente, per esempio una sequenza di eventi separata dall'indice dello stato corrente.

Dichiara anche i vincoli: negozio non nullo e non vuoto, numero positivo, elaborazione locale in un solo thread. Nessuna parte dell'esempio salva dati o invia pagamenti. Questi limiti rendono verificabile la soluzione senza fingere che una collezione in memoria risolva transazioni, concorrenza e persistenza.

Esercizio 1: prevedi il problema della chiave mutabile

Leggi il codice senza eseguirlo. equals confronta il numero e hashCode restituisce lo stesso numero. In un dato momento i due metodi sono coerenti; il problema nasce quando cambia il valore usato come chiave.

static final class MutableKey {
    int id;
    MutableKey(int id) { this.id = id; }
    public int hashCode() { return id; }
    public boolean equals(Object other) {
        return other instanceof MutableKey k && id == k.id;
    }
}
var key = new MutableKey(1);
var orders = new HashMap<MutableKey, String>();
orders.put(key, "ordine");
key.id = 2;
System.out.println(orders.size());
System.out.println(orders.get(key));

Nell'esecuzione registrata, le ultime righe stampano 1 e null; l'iterazione trova ancora lo stesso oggetto chiave. Non significa che la mappa abbia cancellato l'ordine. L'identità usata per la ricerca è cambiata dopo l'inserimento, mentre la struttura era stata organizzata usando il valore precedente.

Il punto normativo è più importante dell'output: la documentazione ufficiale di Map non specifica il comportamento quando una modifica alla chiave influisce sui confronti di uguaglianza. Non promettere quindi null per qualunque implementazione o qualunque modifica. Presentalo come osservazione del programma e della versione dichiarati, oltre il contratto che dovevi rispettare.

Ripara l'identità, non il sintomo

Nel caso scelto, una chiave separata contiene soltanto valori stabili:

record OrderKey(String shop, int id) {
    OrderKey {
        if (shop == null || shop.isBlank() || id <= 0)
            throw new IllegalArgumentException("identity");
    }
}

Qui String e int non introducono un riferimento a uno stato modificabile che cambi l'identità. Il record non è una formula universale per l'immutabilità: un componente List contenente elementi mutabili richiederebbe un'analisi ulteriore. Spiega quali componenti rendono stabile questa particolare chiave.

Il test inserisce IT/1 con quantità uno, modifica la quantità a nove e ritrova la riga attraverso un nuovo OrderKey("IT", 1). Poi inserisce la stessa identità con quantità tre: la dimensione resta uno e il valore è sostituito. FR/1 rimane un'identità distinta. La quantità può cambiare senza rendere irraggiungibile l’ordine attraverso la sua identità.

Non correggere il problema ricostruendo la mappa dopo ogni modifica: nasconderesti un contratto fragile e imporresti un costo crescente. Se il numero dell'ordine deve davvero cambiare, descrivi un'operazione esplicita di rimozione e reinserimento con una nuova chiave stabile, chiarendo cosa accade ai riferimenti esterni. Questa variante è un progetto da discutere; non fa parte dei test eseguiti.

Esercizio 2: vista, copia e istantanea

Prepariamo una lista con una sola riga A, quantità uno. Dalla stessa lista otteniamo tre risultati: una vista non modificabile, una copia strutturale e una lista di valori copiati.

var view = Collections.unmodifiableList(base);
var shallow = List.copyOf(base);
var values = base.stream()
    .map(x -> new LineValue(x.sku, x.qty))
    .toList();

Poi aggiungiamo una riga B alla lista originale e cambiamo la quantità dell'oggetto A da uno a nove. Prima di leggere il risultato, separa due domande: quali liste condividono la struttura e quali condividono gli oggetti contenuti? Confondere questi livelli porta a risposte come «non modificabile significa congelata», che il caso smentisce.

Risultato dopo entrambe le modificheDimensioneQuantità di A
Vista unmodifiableList(base)29
Copia List.copyOf(base)19
Valori LineValue(String, int) copiati11

Secondo la documentazione di Collection, una vista legge la collezione sottostante: impedirne la modifica attraverso quel riferimento non impedisce al proprietario di aggiornare l'originale. La documentazione di List precisa inoltre che elementi mutabili possono far apparire cambiato il contenuto di una lista non modificabile.

Nel test, l’istantanea conserva quantità uno perché crea nuovi valori LineValue(String, int). Non basta chiamare il risultato «copia profonda»: devi mostrare quali parti hai copiato e quali sono già stabili. Se la riga contenesse un indirizzo mutabile o una lista di sconti, questa trasformazione dovrebbe essere estesa e verificata.

Quale risultato restituire al chiamante

La scelta dipende dal contratto dell'API. Una vista è utile quando vuoi esporre aggiornamenti successivi senza concedere operazioni strutturali tramite quel riferimento. Una copia strutturale serve quando vuoi fissare l'insieme degli elementi, ma puoi accettare che gli stessi oggetti cambino. Un'istantanea di valori serve quando il chiamante deve discutere lo stato osservato in un momento preciso.

Nel programma, view.add(...) e shallow.add(...) lanciano UnsupportedOperationException. Questo verifica soltanto il divieto di quella operazione. Non dimostra che nessun altro riferimento possa modificare gli oggetti. Per difendere il risultato, affianca al test dell'eccezione i test sulla dimensione e sulla quantità: mostrano confini diversi.

Non introdurre una copia completa per abitudine. Ha un costo di allocazione e un significato: il chiamante potrebbe aver bisogno di vedere gli aggiornamenti. Nel colloquio collega la scelta al requisito e indica quali modifiche devono essere visibili. Non inventare un benchmark o una soglia di performance quando hai misurato soltanto il comportamento funzionale.

Confronto verificato: vista, copia strutturale e valori copiati

Esercizio 3: un'eccezione annulla gli aggiornamenti?

Partiamo da una mappa contenente IT/1 → 2. Il batch propone prima IT/1 → 5, poi IT/2 → -1; una quantità negativa viola il contratto. Una versione incrementale aggiorna il destinatario mentre legge le modifiche:

for (Change c : changes) {
    if (c.qty() < 0)
        throw new IllegalArgumentException("qty");
    target.put(c.key(), c.qty());
}

Quando la seconda modifica fallisce, la prima è già avvenuta: IT/1 vale cinque. Lanciare un'eccezione interrompe il controllo normale del metodo, ma non ripristina automaticamente lo stato precedente. Il chiamante deve conoscere questa possibilità, soprattutto se ritenta il batch pensando che non sia successo nulla.

La versione alternativa lavora su una nuova LinkedHashMap costruita dall'originale. Applica lo stesso ciclo alla copia e restituisce una vista non modificabile soltanto dopo il completamento. Se la seconda quantità è negativa, l'originale resta a due e non viene restituito un risultato parziale. L’originale resta invariato per questo batch locale con valori Integer e chiavi OrderKey stabili.

PercorsoEsito del batch non validoStato originale di IT/1
Aggiornamento direttoEccezione dopo il primo aggiornamento5
Elaborazione su nuova mappaEccezione senza risultato restituito2
Elaborazione su nuova mappa, batch validoRisultato di due ordini2

Che cosa non garantisce l'elaborazione su copia

Con valori mutabili, copiare soltanto la mappa potrebbe lasciare riferimenti condivisi; una modifica all'oggetto contenuto toccherebbe ancora l'originale. Questo è lo stesso problema dell'esercizio sulle liste. Perciò il test non autorizza a chiamare qualsiasi copia di mappa una transazione. Spiega perché in questo caso i valori Integer non presentano quel rischio.

Il programma non pubblica il risultato in uno stato condiviso e non esegue operazioni esterne. Se due thread aggiornassero lo stesso ordine, oppure il ciclo inviasse un messaggio, servirebbero altre garanzie. L'assenza di un risultato parziale restituito non annulla un messaggio già inviato. Questi sono limiti da dichiarare, non funzionalità implicitamente verificate.

Per il batch valido, l'ordine di iterazione è IT/1, poi IT/2, coerente con l'uso scelto di LinkedHashMap. Se l'ordine non fosse un requisito, non venderlo come vantaggio necessario. Se fosse richiesto un ordinamento per numero, l'ordine di inserimento non lo sostituirebbe: usa un caso con numeri inseriti fuori sequenza per discutere la differenza.

Come spiegare propagazione e finally

Un catch che restituisce una mappa vuota per qualunque eccezione trasforma un fallimento in un successo apparente. Il chiamante non distingue più un batch vuoto da un batch rifiutato. Nel caso didattico lasciamo propagare l'eccezione di validazione; un'API reale potrebbe tradurla in un errore di dominio mantenendo le informazioni necessarie alla diagnosi.

Un secondo esperimento rende visibile un errore diverso: il blocco try lancia IllegalArgumentException, ma finally esegue return 9. Il risultato osservato è nove e l'eccezione non raggiunge il chiamante. Le regole ufficiali del JLS sul completamento di try/finally spiegano come il completamento del finally possa sostituire quello precedente.

Non risolvere il caso inserendo un nuovo return nel finally. Tieni separata la gestione dell'esito dalla pulizia e scrivi una verifica che l'errore previsto raggiunga il confine corretto. La prova didattica mostra il mascheramento, non un uso consigliato. Non sono state provate risorse esterne o eccezioni di chiusura: quel tema richiederebbe un esercizio distinto.

Ventotto controlli che rendono la risposta verificabile

Il file OrderMutation.java esegue ventotto controlli: ricerca prima e dopo la mutazione, identità e hash della chiave stabile, sostituzione dello stesso ordine, isolamento del negozio, dimensioni e quantità nelle tre liste, operazioni vietate, stato lasciato dai due percorsi del batch, ordine del risultato valido, identità non valida e mascheramento nel finally.

Il log salvato riporta OpenJDK 17.0.20.1 e tutti i ventotto esiti; il risultato della chiave mutata resta un’osservazione fuori contratto. Alcuni controlli documentano un comportamento osservato fuori contratto; gli altri verificano le garanzie dichiarate per l'esercizio. Non confonderli con test di carico, sicurezza o concorrenza. Se manca un requisito essenziale, ventotto controlli possono comunque verificare la politica sbagliata.

Per allenarti, cambia un solo requisito: il batch deve accettare aggiornamenti negativi come storni, oppure due negozi condividono una numerazione globale. Prima riscrivi il contratto e i risultati attesi, poi il codice. Se modifichi soltanto l'implementazione, rischi di conservare test che dimostrano una politica ormai sbagliata. Queste varianti sono proposte di studio, non esecuzioni aggiuntive conteggiate nelle ventotto verifiche.

Cinque domande PracHub per proseguire

Le destinazioni sono state verificate e i titoli restano nella lingua originale. Il collegamento con l'esercizio è esplicito: identità, stato condiviso, aggiornamenti e propagazione. Nessuna pagina viene presentata come una raccolta italiana dedicata a questi tre esperimenti.

Domanda PracHubCollegamento con il caso
Solve restaurant path and order event tasksIdentifica eventi e politica degli aggiornamenti prima della soluzione
Explain Hash Map Collisions and Operation ComplexityDistingui collisione e cambiamento scorretto dell'identità
Design a single-node persistent in-memory cacheDiscuti proprietà dello stato ed effetti del fallimento
Explain core Java and Spring fundamentalsCollega i contratti Java a un confine applicativo concreto
Compare common data structures and usesMotiva vista, copia e ordine attraverso il requisito

Riparti da Solve restaurant path and order event tasks. Prevedi uno stato finale, specifica quale modifica lo invalida e prepara il test che difende la correzione. Così una risposta sulle collezioni diventa una spiegazione verificabile di comportamento e responsabilità.

Sources and Further Reading


Comments (0)