{
  "assessmentTests": {
    "java_test": {
      "name": "Test Java",
      "desc": "30 domande scenario su sintassi core, meccanica OOP, collezioni, generics, eccezioni e concorrenza — scopri se il tuo Java corrisponde a ciò che un annuncio di lavoro intende per competenza Java.",
      "recommendation": "Il tuo profilo di competenze Java",
      "results": {
        "beginner": {
          "name": "Principiante",
          "desc": "Puoi scrivere classi e metodi funzionanti, ma le domande che hai sbagliato si concentrano su identità e default piuttosto che su sintassi — String == vs .equals, perché new String(\"x\") non riusa mai lo string pool, cosa un campo numerico fa di default rispetto a un campo wrapper. Niente di questo riguarda l'essere scarsi a Java; sono le regole specifiche che trippano chi ha imparato il linguaggio per prova e errore piuttosto che da come riferimenti e primitivi funzionano realmente sotto il cofano. Contano al lavoro perché ognuna di queste è dove il codice compila, gira, e silenziosamente fa la cosa sbagliata.",
          "recommendation": "Inizia con tre cose, in questo ordine: perché == su due oggetti String confronta i riferimenti, non i caratteri (e .equals è quello che quasi sempre vuoi), come int overflow si avvolge silenziosamente invece di lanciare, e perché un campo Integer non inizializzato fa di default null mentre un campo int fa di default 0. I tutorial ufficiali Java di Oracle e Baeldung coprono tutti e tre con esempi eseguibili."
        },
        "intermediate": {
          "name": "Intermedio",
          "desc": "Gestisci comodamente il codice applicativo quotidiano — classi, collezioni, control flow diretto — e non saresti rallentato da lavoro di feature di routine. Il divario tra qui e Avanzato è per lo più in ciò che accade quando le regole di Java interagiscono con concorrenza e generics: un metodo static risolto per tipo dichiarato invece dell'oggetto che ti aspettavi, un ordine di iterazione HashMap su cui silenziosamente facevi affidamento, una ConcurrentModificationException dal rimuovere un elemento durante l'iterazione. Questi sono il tipo di bug che passano una lettura veloce e appaiono solo sotto una condizione di runtime specifica.",
          "recommendation": "Focalizzati su come la vista statica del compilatore del tuo codice differisce da ciò che gira: risoluzione di overload e occultamento di metodo static per tipo dichiarato piuttosto che tipo runtime, perché HashMap non dà garanzia di ordine di iterazione, e perché modificare una lista direttamente mentre iteri lancia ConcurrentModificationException. Poi generics erasure, dato che questo confonde chi già conosce le collezioni singolarmente."
        },
        "advanced": {
          "name": "Avanzato",
          "desc": "Questo è il livello che la maggior parte degli annunci di lavoro intende con \"Java forte.\" Leggi una classe con blocchi static, blocchi instance e multipli costruttori e puoi prevedere l'ordine esatto in cui girano, sai perché un blocco finally può silenziosamente scartare il valore di ritorno del blocco try, e raggiungi per try-with-resources invece di un finally-close manuale perché conosci la garanzia di ordinamento che ti dà. Ciò che separa questa banda dal top è il lato concurrent e interface-level del lavoro: cosa synchronized effettivamente blocca, cosa volatile fa e non fa garantire, e come i conflitti di default-method vengono risolti.",
          "recommendation": "Approfondisci le parti che proteggono il codice che altri thread e altre interface toccano anche: la differenza tra cosa un metodo instance synchronized blocca rispetto a un metodo static synchronized, perché volatile dà visibilità ma non atomicità per counter++, e come Java ti forza a risolvere un default-method diamond a mano. Il materiale su Java Memory Model di Baeldung e Java Concurrency in Practice è il prossimo step naturale per entrambi."
        },
        "expert": {
          "name": "Esperto",
          "desc": "Hai segnato il massimo in ogni sezione — sintassi core e tipi, meccanica OOP e scope, collezioni e generics, e eccezioni, concorrenza e idiomi. Praticamente, questo significa che puoi ricevere la classe di uno sconosciuto e spiegare perché un errore di compilazione, un'eccezione runtime, o un valore silenziosamente sbagliato accade, non solo cosa la sintassi dice che dovrebbe fare, che è l'abilità più difficile e più preziosa. A questo livello il linguaggio stesso è raramente il fattore limitante; il limite è di solito il design della concorrenza o la forma dei dati sottostanti.",
          "recommendation": "I rendimenti sono ora in design e diagnosi: leggere un thread dump prima di assumere che un hang sia un deadlock, scegliere tra synchronized, java.util.concurrent locks e atomics come un tradeoff piuttosto che un default, e stream-pipeline design che rimane lazy di proposito piuttosto che per caso. Se stai siendo proiettato per un ruolo, descrivi un bug di concorrenza come un missed happens-before edge o una ConcurrentModificationException che hai trovato in produzione piuttosto che nominare feature Java — dimostra il ragionamento, non solo il vocabolario."
        }
      },
      "questions": [
        {
          "question": "Crei due oggetti String: String a = new String(\"cat\"); String b = new String(\"cat\"); A cosa si valuta a == b?",
          "options": [
            {
              "icon": "",
              "label": "true — i letterali String con gli stessi caratteri sono sempre lo stesso oggetto"
            },
            {
              "icon": "",
              "label": "true — new String(...) riusa lo string pool, così il testo identico sempre condivide un oggetto"
            },
            {
              "icon": "",
              "label": "Un errore di compilazione — == non può essere usato per confrontare oggetti String"
            },
            {
              "icon": "",
              "label": "false — new String(...) alloca sempre un nuovo oggetto sulla heap, così == confronta due riferimenti diversi anche se .equals(b) ritornerebbe true"
            }
          ]
        },
        {
          "question": "Integer x = 100; Integer y = 100; System.out.println(x == y); poi Integer x2 = 200; Integer y2 = 200; System.out.println(x2 == y2); Cosa stampano le due righe?",
          "options": [
            {
              "icon": "",
              "label": "true poi true — gli oggetti Integer autoboxed sono sempre cachati indipendentemente dal valore"
            },
            {
              "icon": "",
              "label": "false poi false — == su oggetti Integer è sempre un confronto di riferimento, così i valori uguali mai corrispondono"
            },
            {
              "icon": "",
              "label": "true poi false, ma solo perché 200 fa overflow di un byte — il confine della cache è scollegato da -128..127"
            },
            {
              "icon": "",
              "label": "true poi false — i valori Integer autoboxed da -128 a 127 sono cachati e condivisi, così 100 riusa un oggetto, ma 200 cade fuori dalla cache e autobox a due oggetti separati"
            }
          ]
        },
        {
          "question": "int max = Integer.MAX_VALUE; System.out.println(max + 1); Cosa accade?",
          "options": [
            {
              "icon": "",
              "label": "Lancia un ArithmeticException per integer overflow"
            },
            {
              "icon": "",
              "label": "Stampa Integer.MAX_VALUE di nuovo, perché Java blocca al massimo del tipo"
            },
            {
              "icon": "",
              "label": "Stampa Integer.MIN_VALUE — l'aritmetica int silenziosamente si avvolge in caso di overflow invece di lanciare"
            },
            {
              "icon": "",
              "label": "È un errore di compilazione — il compilatore rileva l'overflow in anticipo"
            }
          ]
        },
        {
          "question": "System.out.println(0.1 + 0.2 == 0.3); Cosa stampa, e perché?",
          "options": [
            {
              "icon": "",
              "label": "false — 0.1, 0.2 e 0.3 non possono essere rappresentati esattamente in floating point binario, così la somma porta errore di arrotondamento che la rende non bit-per-bit uguale a 0.3"
            },
            {
              "icon": "",
              "label": "true — Java arrotonda l'aritmetica double al valore decimale rappresentabile più prossimo prima di confrontare"
            },
            {
              "icon": "",
              "label": "true — l'addizione di due double è sempre esatta per valori con due cifre decimali"
            },
            {
              "icon": "",
              "label": "Lancia un'eccezione, perché == non è definito per double in Java"
            }
          ]
        },
        {
          "question": "Una classe dichiara due campi instance senza inizializzatore: int count; e Integer total; Prima che il costruttore giri, quali sono i loro valori di default?",
          "options": [
            {
              "icon": "",
              "label": "Entrambi fanno di default 0, perché Integer autobox a int all'inizializzazione di campo"
            },
            {
              "icon": "",
              "label": "count è 0 e total è 0, boxed automaticamente la prima volta che è letto"
            },
            {
              "icon": "",
              "label": "count è 0 e total è null — i campi numerici primitivi fanno di default zero, ma un campo di riferimento wrapper non inizializzato fa di default null come qualsiasi altro riferimento di oggetto"
            },
            {
              "icon": "",
              "label": "Entrambi fanno di default null fino a che non sono esplicitamente assegnati, poiché Java non ha default numerici impliciti"
            }
          ]
        },
        {
          "question": "Un loop gira 1000 volte, ogni volta facendo result += \"x\"; su un risultato String. Cosa effettivamente accade sotto il cofano ogni iterazione?",
          "options": [
            {
              "icon": "",
              "label": "L'array di caratteri interno dell'oggetto String esistente è esteso in place"
            },
            {
              "icon": "",
              "label": "Java automaticamente raggruppa le concatenazioni e alloca solo un oggetto String finale"
            },
            {
              "icon": "",
              "label": "Compila a una singola chiamata StringBuilder.append() condivisa tra tutte le 1000 iterazioni, senza nessun oggetto extra allocato per iterazione"
            },
            {
              "icon": "",
              "label": "Un brand new oggetto String è creato e result è riassegnato a puntare a esso — l'oggetto String precedente è scartato, perché String è immutabile e += non può modificarlo in place"
            }
          ]
        },
        {
          "question": "final List<String> names = new ArrayList<>(); Quale di questi è vero?",
          "options": [
            {
              "icon": "",
              "label": "names.add(\"Ana\") compila e funziona bene — final solo previene di riassegnare il riferimento names stesso, non di mutare l'oggetto a cui punta"
            },
            {
              "icon": "",
              "label": "names.add(\"Ana\") è un errore di compilazione, perché final rende la lista stessa non modificabile"
            },
            {
              "icon": "",
              "label": "La lista è thread-safe per scritture concorrenti perché è stata dichiarata final"
            },
            {
              "icon": "",
              "label": "final su una variabile locale non ha effetto a meno che il tipo non sia anche dichiarato immutabile"
            }
          ]
        },
        {
          "question": "int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; System.out.println(a == b); System.out.println(Arrays.equals(a, b)); Cosa stampano le due righe?",
          "options": [
            {
              "icon": "",
              "label": "false poi true — == confronta i riferimenti di array (due oggetti array diversi), mentre Arrays.equals confronta gli elementi"
            },
            {
              "icon": "",
              "label": "true poi true — gli array con contenuti identici sono lo stesso oggetto in Java"
            },
            {
              "icon": "",
              "label": "false poi false — Arrays.equals funziona solo per array di oggetti, non per array di primitivi int"
            },
            {
              "icon": "",
              "label": "true poi false — == su array confronta il contenuto, e Arrays.equals è ridondante"
            }
          ]
        },
        {
          "question": "void show(Object o) { print(\"Object\"); } void show(String s) { print(\"String\"); } Object ref = \"hello\"; show(ref); Quale overload gira, e perché?",
          "options": [
            {
              "icon": "",
              "label": "show(String) gira, perché Java ispeziona la classe runtime effettiva dell'oggetto prima di scegliere l'overload"
            },
            {
              "icon": "",
              "label": "show(Object) gira — la risoluzione di overload è decisa al compile time usando il tipo dichiarato della variabile, non il tipo runtime effettivo dell'oggetto a cui si riferisce"
            },
            {
              "icon": "",
              "label": "È un errore di compilazione — Object non può essere passato dove esiste un overload più specifico"
            },
            {
              "icon": "",
              "label": "Entrambi i metodi girano, uno per uno, perché Java risolve gli overload provando ogni match"
            }
          ]
        },
        {
          "question": "La classe Base ha static void greet() { print(\"Base\"); }. La classe Derived extends Base e dichiara anche static void greet() { print(\"Derived\"); }. Scrivi: Base ref = new Derived(); ref.greet(); Cosa stampa?",
          "options": [
            {
              "icon": "",
              "label": "Derived — i metodi static fanno override proprio come i metodi instance, seguendo il tipo runtime effettivo dell'oggetto"
            },
            {
              "icon": "",
              "label": "È un errore di compilazione — i metodi static non possono essere chiamati attraverso un riferimento di istanza"
            },
            {
              "icon": "",
              "label": "Base — i metodi static non sono polimorfici; chiamarne uno attraverso un riferimento è risolto dal tipo dichiarato del riferimento al compile time, non dal tipo effettivo dell'oggetto"
            },
            {
              "icon": "",
              "label": "Sia Base che Derived stampano, perché la chiamata si risolve sia alla versione nascosta che a quella che nasconde"
            }
          ]
        },
        {
          "question": "Dentro un metodo, scrivi int count = 0; poi prova a referenziare count da dentro un lambda passato a un altro metodo. Cosa deve essere vero su count per compilare?",
          "options": [
            {
              "icon": "",
              "label": "count deve essere effectively final — mai riassegnato da nessuna parte dopo il suo valore iniziale — perché un lambda cattura uno snapshot, non un riferimento vivo a una variabile locale mutabile"
            },
            {
              "icon": "",
              "label": "Niente — qualsiasi variabile locale può essere liberamente letta e riassegnata da dentro un lambda"
            },
            {
              "icon": "",
              "label": "count deve essere dichiarato volatile così il lambda sempre vede il suo valore più recente"
            },
            {
              "icon": "",
              "label": "count deve essere un campo, non una variabile locale — i lambda non possono catturare i locali affatto"
            }
          ]
        },
        {
          "question": "void rename(StringBuilder sb) { sb = new StringBuilder(\"new\"); } è chiamato come StringBuilder original = new StringBuilder(\"old\"); rename(original); Cosa è original dopo la chiamata?",
          "options": [
            {
              "icon": "",
              "label": "Diventa \"new\" — gli oggetti sono passati per riferimento in Java, così riassegnare il parametro cambia anche la variabile del chiamante"
            },
            {
              "icon": "",
              "label": "Diventa \"new\" solo per tipi mutabili come StringBuilder, ma non per tipi immutabili"
            },
            {
              "icon": "",
              "label": "Rimane \"old\" — Java passa il riferimento stesso per valore, così riassegnare il parametro dentro il metodo solo ripunta la copia locale del riferimento, lasciando l'originale del chiamante intatto"
            },
            {
              "icon": "",
              "label": "Lancia un'eccezione runtime, perché sb è stato riassegnato dentro il metodo"
            }
          ]
        },
        {
          "question": "Una classe ha un blocco static initializer, un blocco instance initializer, e un costruttore, in quell'ordine di sorgente. Crei due oggetti di questa classe uno dopo l'altro. In quale ordine questi girano?",
          "options": [
            {
              "icon": "",
              "label": "Il blocco static gira esattamente una volta, la prima volta che la classe è caricata; poi per ogni oggetto, il blocco instance gira e poi il corpo del costruttore gira"
            },
            {
              "icon": "",
              "label": "Tutti e tre girano freschi, in ordine di sorgente, per ogni singolo oggetto creato"
            },
            {
              "icon": "",
              "label": "Il costruttore gira prima per ogni oggetto, poi il blocco instance, poi il blocco static una volta alla fine"
            },
            {
              "icon": "",
              "label": "Il blocco static gira una volta per oggetto, giusto prima del blocco instance"
            }
          ]
        },
        {
          "question": "Una classe ha void log(String s) e void log(String... args). Chiami log(\"hi\"). Quale gira?",
          "options": [
            {
              "icon": "",
              "label": "log(String... args) — gli overload varargs sono sempre preferiti quando entrambi sono applicabili"
            },
            {
              "icon": "",
              "label": "È un errore di compilazione — la chiamata è ambigua tra i due overload"
            },
            {
              "icon": "",
              "label": "log(String s) — quando un overload fixed-arity corrisponde esattamente, Java sempre lo preferisce sopra un overload varargs, che è usato solo come ultimo ricorso"
            },
            {
              "icon": "",
              "label": "Quale è dichiarato prima nel file sorgente gira"
            }
          ]
        },
        {
          "question": "La prima riga di un costruttore chiama this(0); per delegare a un altro costruttore nella stessa classe. Dove è permesso che this() appaia?",
          "options": [
            {
              "icon": "",
              "label": "Dovunque nel corpo del costruttore, finché gira prima che l'oggetto sia ritornato"
            },
            {
              "icon": "",
              "label": "Solo come la primissima dichiarazione del costruttore — this() (o super()) deve essere la prima riga, e un costruttore non può chiamare sia this() che super()"
            },
            {
              "icon": "",
              "label": "Solo come l'ultima dichiarazione, dopo che tutta l'inizializzazione di campo è completa"
            },
            {
              "icon": "",
              "label": "Solo in costruttori marcati esplicitamente come delegating, usando una keyword delegate"
            }
          ]
        },
        {
          "question": "Hai bisogno di una lista che avrà elementi inseriti al fronte estremamente spesso, e letture di random-access sono rare. Quale è il miglior adattamento strutturale, e perché?",
          "options": [
            {
              "icon": "",
              "label": "ArrayList — il suo array backing contiguo rende ogni operazione, incluso l'inserimento al fronte, più veloce di una struttura collegata"
            },
            {
              "icon": "",
              "label": "Performano identicamente, perché entrambi implementano l'interfaccia List con le stesse garanzie di tempo"
            },
            {
              "icon": "",
              "label": "LinkedList — inserire al fronte è O(1) perché giusto ricollega un nodo, mentre ArrayList deve spostare ogni elemento esistente su di uno, rendendo l'inserimento al fronte O(n)"
            },
            {
              "icon": "",
              "label": "LinkedList, perché supporta l'accesso random in O(1) mentre ArrayList no"
            }
          ]
        },
        {
          "question": "Inserisci cinque voci in un plain HashMap in uno ordine specifico, poi itera su di esso con un for-each loop. In quale ordine le voci escono?",
          "options": [
            {
              "icon": "",
              "label": "Nello stesso ordine che le voci sono state inserite, poiché le mappe Java sempre preservano l'ordine di inserimento"
            },
            {
              "icon": "",
              "label": "Nessun ordine garantito affatto — l'ordine di iterazione di HashMap dipende dal posizionamento di bucket hash, non dall'ordine di inserimento, e può anche cambiare tra le esecuzioni; LinkedHashMap è quello che preserva l'ordine di inserimento"
            },
            {
              "icon": "",
              "label": "Ordinato per chiave automaticamente, nello stesso modo che una TreeMap si comporta"
            },
            {
              "icon": "",
              "label": "Inverso dell'ordine di inserimento, perché HashMap internamente usa uno stack"
            }
          ]
        },
        {
          "question": "Quanti null keys può contenere un plain java.util.HashMap alla volta, e quanti null values?",
          "options": [
            {
              "icon": "",
              "label": "Nessun null key e nessun null value sono mai permessi in nessuna implementazione Map"
            },
            {
              "icon": "",
              "label": "Un null key al massimo, e qualsiasi numero di null values — HashMap permette una singola null key, a differenza di Hashtable che non permette nessuno dei due"
            },
            {
              "icon": "",
              "label": "Unlimited null keys e unlimited null values, poiché null è trattato come qualsiasi altra chiave"
            },
            {
              "icon": "",
              "label": "Una null key e un null value massimi, entrambi cappati a uno"
            }
          ]
        },
        {
          "question": "Iteri una List con un for-each loop e chiami list.remove(item) direttamente sulla lista dal corpo del loop, su alcuni ma non tutti gli elementi. Cosa accade?",
          "options": [
            {
              "icon": "",
              "label": "Funziona correttamente e rimuove esattamente gli elementi intesi"
            },
            {
              "icon": "",
              "label": "Silenziosamente salta l'elemento dopo quello rimosso, ma altrimenti completa senza errore"
            },
            {
              "icon": "",
              "label": "Lancia IndexOutOfBoundsException una volta che il loop raggiunge la vecchia dimensione della lista"
            },
            {
              "icon": "",
              "label": "Lancia ConcurrentModificationException — modificare la struttura della lista direttamente mentre un iteratore implicito la cammina è rilevato e rifiutato; Iterator.remove() deve essere usato invece"
            }
          ]
        },
        {
          "question": "Al runtime, dato un List<String> list, cosa puoi effettivamente determinare sul suo parametro di tipo generico via reflection o instanceof?",
          "options": [
            {
              "icon": "",
              "label": "Puoi chiamare list.getElementType() per recuperare String.class al runtime"
            },
            {
              "icon": "",
              "label": "instanceof List<String> compila e correttamente controlla il tipo di elemento"
            },
            {
              "icon": "",
              "label": "La JVM immagazzina il parametro di tipo come metadati nascosti accessibili via list.getGenericType()"
            },
            {
              "icon": "",
              "label": "Niente — le informazioni di tipo generico sono cancellate al compile time, così al runtime l'oggetto è solo una List, e non c'è modo di recuperare se è stata dichiarata List<String> o List<Integer>"
            }
          ]
        },
        {
          "question": "switch (day) { case 1: case 2: print(\"Weekday\"); break; case 6: print(\"Saturday\"); case 7: print(\"Sunday\"); break; default: print(\"?\"); } Se day è 6, cosa stampa?",
          "options": [
            {
              "icon": "",
              "label": "Solo Saturday — ogni blocco case in uno switch sempre si ferma dopo la sua propria dichiarazione print"
            },
            {
              "icon": "",
              "label": "Solo Sunday — il matching inizia in case 6 ma solo l'ultima label che corrisponde prima di break esegue"
            },
            {
              "icon": "",
              "label": "Niente stampa, perché day 6 non ha nessun caso che corrisponde con il suo proprio break direttamente sotto di esso"
            },
            {
              "icon": "",
              "label": "Saturday poi Sunday — case 6 non ha break, così l'esecuzione cade nel codice del prossimo case prima di colpire il break seguente"
            }
          ]
        },
        {
          "question": "Una classe Product ha bisogno di un singolo, ovvio ordine \"naturale\" di sorting per prezzo, più l'abilità di anche sortare per nome o per livello di stock in posti diversi nel codebase. Quale combinazione è il design giusto?",
          "options": [
            {
              "icon": "",
              "label": "Implementare Comparable<Product> tre volte separate, una per ordinamento, e lasciare che il chiamante scelga quale compareTo gira"
            },
            {
              "icon": "",
              "label": "Implementare Comparable<Product> per l'ordinamento naturale di prezzo, e scrivere separate istanze Comparator<Product> per gli ordinamenti di nome e livello di stock usati altrove"
            },
            {
              "icon": "",
              "label": "Usare solo Comparator per ogni ordinamento, incluso prezzo, dato che Comparable non ha vantaggio qui"
            },
            {
              "icon": "",
              "label": "Usare solo Comparable aggiungendo tre metodi compareTo sovraccaricati, uno per ordinamento"
            }
          ]
        },
        {
          "question": "Un metodo chiama new FileReader(path), che dichiara throws IOException. IOException extends Exception, non RuntimeException. Cosa devi fare per compilare?",
          "options": [
            {
              "icon": "",
              "label": "Niente — il compilatore impone questo solo per eccezioni che extends RuntimeException"
            },
            {
              "icon": "",
              "label": "O catturare IOException in un try/catch o dichiarare throws IOException sul tuo metodo — le eccezioni checked devono essere gestite o propagate esplicitamente, a differenza dei sottoclassi unchecked RuntimeException"
            },
            {
              "icon": "",
              "label": "Avvolgi la chiamata in un try/catch per RuntimeException, poiché IOException è automaticamente unchecked nel Java moderno"
            },
            {
              "icon": "",
              "label": "Dichiara il metodo come static — i metodi static sono esenti dalla gestione delle eccezioni checked"
            }
          ]
        },
        {
          "question": "try (Resource a = new Resource(\"A\"); Resource b = new Resource(\"B\")) { ... } usa due risorse che implementano AutoCloseable. Se il blocco try finisce normalmente, in quale ordine sono chiuse?",
          "options": [
            {
              "icon": "",
              "label": "A è chiuso prima, poi B, corrispondendo all'ordine di dichiarazione"
            },
            {
              "icon": "",
              "label": "B è chiuso prima, poi A — try-with-resources chiude le risorse nell'inverso dell'ordine in cui sono state dichiarate"
            },
            {
              "icon": "",
              "label": "Entrambi sono chiusi simultaneamente, poiché try-with-resources parallelizza la pulizia"
            },
            {
              "icon": "",
              "label": "Solo B è chiuso automaticamente — A deve ancora essere chiuso manualmente in un blocco finally"
            }
          ]
        },
        {
          "question": "int getValue() { try { return 1; } finally { return 2; } } Cosa ritorna chiamare getValue()?",
          "options": [
            {
              "icon": "",
              "label": "1 — il valore di ritorno del blocco try è già committed prima che finally gira, così finally non può cambiarlo"
            },
            {
              "icon": "",
              "label": "Lancia un'eccezione al runtime, perché un metodo non può ritornare da due posti"
            },
            {
              "icon": "",
              "label": "2 — una dichiarazione return dentro finally sovrascrive e sostituisce qualsiasi return già in progress dal blocco try, scartando il valore 1 completamente"
            },
            {
              "icon": "",
              "label": "È un errore di compilazione — finally non è permesso contenere una dichiarazione return"
            }
          ]
        },
        {
          "question": "try { ... } catch (IOException e) { ... } catch (FileNotFoundException e) { ... }, e FileNotFoundException extends IOException. Cosa accade quando provi a compilare?",
          "options": [
            {
              "icon": "",
              "label": "Un errore di compilazione — il blocco catch FileNotFoundException è irraggiungibile perché il blocco catch IOException precedente e più generale già corrisponde a ogni FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Compila bene, e il blocco più specifico FileNotFoundException gira ogni volta che quel tipo esatto è lanciato"
            },
            {
              "icon": "",
              "label": "Compila bene, e entrambi i blocchi catch girano in ordine per una FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Va bene al compile time ma lancia un errore runtime la prima volta che effettivamente si verifica una FileNotFoundException"
            }
          ]
        },
        {
          "question": "Una classe ha un metodo instance synchronized process() e un metodo static synchronized configure(). Cosa, specificamente, blocca ognuno?",
          "options": [
            {
              "icon": "",
              "label": "process() blocca il monitor dell'istanza di oggetto specifico su cui è chiamato, mentre configure() blocca il monitor dell'oggetto Class stesso — condiviso da ogni istanza"
            },
            {
              "icon": "",
              "label": "Entrambi bloccano lo stesso singolo lock globale per l'intera JVM, indipendentemente da istanza o classe"
            },
            {
              "icon": "",
              "label": "process() blocca l'oggetto Class, e configure() blocca qualsiasi istanza che lo chiama"
            },
            {
              "icon": "",
              "label": "Nessuno effettivamente blocca niente a meno che un blocco synchronized sia anche usato dentro il corpo del metodo"
            }
          ]
        },
        {
          "question": "Un campo è dichiarato volatile int counter = 0;, e più thread girano counter++ su di esso concorrentemente. Volatile previene perdite di aggiornamento qui?",
          "options": [
            {
              "icon": "",
              "label": "Sì — volatile rende ogni operazione sul campo atomica, inclusi gli incrementi"
            },
            {
              "icon": "",
              "label": "No — volatile solo garantisce che le letture vedono lo scritto più recente attraverso i thread (visibilità); counter++ è un read-modify-write con multipli step, e volatile non fa niente per rendere questi step atomici"
            },
            {
              "icon": "",
              "label": "Sì, ma solo per campi int e long specificamente, a causa di come la JVM gestisce valori di 64-bit"
            },
            {
              "icon": "",
              "label": "No, e volatile anche non garantisce visibilità per tipi primitivi come int"
            }
          ]
        },
        {
          "question": "L'interfaccia A e l'interfaccia B ognuna dichiara un metodo default describe(). Una classe implementa sia A che B e non fa override di describe() stessa. Cosa accade?",
          "options": [
            {
              "icon": "",
              "label": "Il compilatore automaticamente sceglie la versione di A, poiché è elencata prima nella clausola implements"
            },
            {
              "icon": "",
              "label": "Entrambe le versioni girano, una dopo l'altra, ogni volta che describe() è chiamato"
            },
            {
              "icon": "",
              "label": "Va bene al compile time, ma lancia una AmbiguousMethodException la prima volta che describe() è effettivamente chiamato"
            },
            {
              "icon": "",
              "label": "Un errore di compilazione — quando due interfacce contribuiscono lo stesso metodo default, la classe che implementa deve farvi override stessa per risolvere l'ambiguità, poiché Java non indovinerà quale intendevi"
            }
          ]
        },
        {
          "question": "list.stream().filter(x -> x > 0).map(x -> x * 2); è scritto ma il risultato mai è assegnato a un'operazione terminale come .collect() o .forEach(). Cosa effettivamente accade quando questa riga esegue?",
          "options": [
            {
              "icon": "",
              "label": "Niente accade agli elementi della lista affatto — filter e map sono operazioni intermediate lazy che solo costruiscono una descrizione di pipeline; senza un'operazione terminale, nessuna di questa pipeline mai effettivamente gira"
            },
            {
              "icon": "",
              "label": "Ogni elemento è filtrato e mappato immediatamente, esattamente come se un'operazione terminale fosse chiamata"
            },
            {
              "icon": "",
              "label": "Solo il filter gira immediatamente; map è differito finché un'operazione terminale appare"
            },
            {
              "icon": "",
              "label": "Lancia una IllegalStateException, perché una pipeline stream richiede un'operazione terminale per compilare"
            }
          ]
        }
      ],
      "optionOrderVersion": "0154f9576054ad6f"
    }
  }
}
