{
  "assessmentTests": {
    "java_test": {
      "name": "Test de Java",
      "desc": "30 preguntas de lectura de código sobre sintaxis/tipos, métodos/sobrecarga/alcance, colecciones/genéricos/flujo de control, y excepciones/concurrencia/idiomas — descubre si tu Java coincide con lo que una oferta de empleo entiende por competencia en Java.",
      "recommendation": "Tu perfil de habilidades en Java",
      "results": {
        "beginner": {
          "name": "Principiante",
          "desc": "Conoces la sintaxis básica de Java y puedes escribir un programa simple, pero preguntas que contestaste incorrectamente tienden a agruparse alrededor de cómo funcionan realmente las referencias de objetos, autoboxing en Integer, y lo que sucede cuando el código toma la rama equivocada de un condicional. Nada de esto significa que seas malo con Java; estos son los peligros específicos que la gente que aprendió Java leyendo documentos en línea en lugar del estándar suele perder. Importan en el trabajo porque cada uno es un lugar donde el código ejecuta en silencio con un resultado diferente del que parecía.",
          "recommendation": "Comienza con tres cosas, en este orden: por qué == compara referencias de objetos, no contenido (y .equals() es lo que necesitas para contenido), cómo Integer cachea el rango -128 a 127 y haz que == parezca funcionar entre -128 y 127 pero falle por encima, y traza mental un if/else para ver qué rama realmente se ejecuta. El libro Effective Java y los tutoriales de Oracle cubren los tres."
        },
        "intermediate": {
          "name": "Intermedio",
          "desc": "Escribes código Java funcional cómodamente — métodos, bucles, colecciones — y el trabajo rutinario con CRUD y transformación de datos no te ralentizaría. La brecha entre aquí y Avanzado es principalmente sobre qué sucede cuando los mecanismos interactúan: un constructor que silenciosamente cae a un constructor padre, una colección genérica que se borra cuando casts son inseguros, una excepción que silenciosamente se re-lanza en un finally. Ese es el tipo de errores que pasan un rápido vistazo y solo aparecen en producción cuando alguien añade una línea nueva.",
          "recommendation": "Enfócate en las interacciones entre mecanismos en lugar de qué hace cada uno por separado: cómo el flujo de control del constructor llamador → padre afecta el estado inicial, sobrecarga con genéricos y por qué instanceof falla en tipos genéricos, y por qué un finally que devuelve un valor anula la excepción que se estaba lanzando. Effective Java, capítulos 2 y 5 cubren esto."
        },
        "advanced": {
          "name": "Avanzado",
          "desc": "Este es el nivel que la mayoría de ofertas de empleo entienden por «Java fuerte». Lees una colección genérica de tipos anidados y predices si un cast es seguro, entiendes cuándo la sobrecarga se resuelve en tiempo de compilación versus tiempo de ejecución, y sabes cuál excepción se lanza en realidad de una cadena de intentos/atrapar. Lo que separa esta banda de la superior es el lado defensivo del trabajo: saber qué hace realmente un stream lazily, cómo el cambio concurrente mientras itera afecta un iterator, y qué hace volatility realmente en lugar de asumir que significa threadsafe.",
          "recommendation": "Profundiza en las partes que son fáciles de leer mal: predicción de resolución de sobrecarga cuando argumentos son sutilmente diferentes tipos, genéricos y erasure — qué información se pierde en tiempo de compilación y cómo eso rompe instanceof y casting, y concurrencia básica — qué hace volatile, synchronized, y qué hace ConcurrentModificationException. El libro \"Java Concurrency in Practice\" cubre el lado concurrente completamente."
        },
        "expert": {
          "name": "Experto",
          "desc": "Puntuaste en la parte superior de cada sección — sintaxis y semántica de tipos, métodos y sobrecarga, colecciones y genéricos, excepciones y concurrencia. Prácticamente, eso significa que puedes leer una pieza de código Java de alguien más y explicar por qué hace lo que hace, no solo lo que los keywords dicen que debería hacer, que es la habilidad más difícil y más valiosa. A este nivel el lenguaje rara vez es el factor limitante; el límite es generalmente diseño de arquitectura o el rendimiento bajo carga.",
          "recommendation": "Los retornos ahora están en refactoring y rendimiento: qué hace un stream terminal versus intermedio y cómo eso afecta paralelismo, predicción de overhead de synchronization y cuándo lock-free es realmente más rápido, y decisiones de colección que escalan mientras un sistema crece. Si estás siendo evaluado para un rol, describe un bug como el autoboxing que necesitó un tipo de colección diferente o el stream que tardó en compilar que encontraste en producción en lugar de nombrar características de Java — demuestra el razonamiento, no solo el vocabulario."
        }
      },
      "questions": [
        {
          "question": "Creas dos objetos String: String a = new String(\"cat\"); String b = new String(\"cat\"); ¿A qué se evalúa a == b?",
          "options": [
            {
              "icon": "",
              "label": "true — los literales String con los mismos caracteres siempre son el mismo objeto"
            },
            {
              "icon": "",
              "label": "true — new String(...) reutiliza el string pool, así que el texto idéntico siempre comparte un objeto"
            },
            {
              "icon": "",
              "label": "Un error de compilación — == no puede usarse para comparar objetos String"
            },
            {
              "icon": "",
              "label": "false — new String(...) siempre asigna un nuevo objeto en el heap, así que == compara dos referencias diferentes aunque .equals(b) devolvería true"
            }
          ]
        },
        {
          "question": "Integer x = 100; Integer y = 100; System.out.println(x == y); luego Integer x2 = 200; Integer y2 = 200; System.out.println(x2 == y2); ¿Qué imprimen las dos líneas?",
          "options": [
            {
              "icon": "",
              "label": "true luego true — los objetos Integer autobox se cachean siempre sin importar el valor"
            },
            {
              "icon": "",
              "label": "false luego false — == en objetos Integer siempre es una comparación de referencias, así que los valores iguales nunca coinciden"
            },
            {
              "icon": "",
              "label": "true luego false, pero solo porque 200 desborda un byte — el límite de caché no está relacionado con -128..127"
            },
            {
              "icon": "",
              "label": "true luego false — los valores Integer autobox de -128 a 127 se cachean y comparten, así que 100 reutiliza un objeto, pero 200 cae fuera del caché y autobox a dos objetos separados"
            }
          ]
        },
        {
          "question": "int max = Integer.MAX_VALUE; System.out.println(max + 1); ¿Qué sucede?",
          "options": [
            {
              "icon": "",
              "label": "Lanza ArithmeticException por desbordamiento entero"
            },
            {
              "icon": "",
              "label": "Imprime Integer.MAX_VALUE de nuevo, porque Java fija el máximo del tipo"
            },
            {
              "icon": "",
              "label": "Imprime Integer.MIN_VALUE — la aritmética int envuelve silenciosamente en desbordamiento en lugar de lanzar"
            },
            {
              "icon": "",
              "label": "Es un error de compilación — el compilador detecta el desbordamiento con anticipación"
            }
          ]
        },
        {
          "question": "System.out.println(0.1 + 0.2 == 0.3); ¿Qué imprime esto, y por qué?",
          "options": [
            {
              "icon": "",
              "label": "false — 0.1, 0.2 y 0.3 no pueden representarse exactamente en punto flotante binario, así que la suma lleva error de redondeo que la hace no bit-por-bit igual a 0.3"
            },
            {
              "icon": "",
              "label": "true — Java redondea la aritmética double al decimal representable más cercano antes de comparar"
            },
            {
              "icon": "",
              "label": "true — la suma de dos doubles siempre es exacta para valores con dos dígitos decimales"
            },
            {
              "icon": "",
              "label": "Lanza una excepción, porque == no está definido para double en Java"
            }
          ]
        },
        {
          "question": "Una clase declara dos campos de instancia sin inicializador: int count; e Integer total; Antes de que el constructor se ejecute, ¿cuáles son sus valores por defecto?",
          "options": [
            {
              "icon": "",
              "label": "Ambos por defecto a 0, porque Integer autobox a int en la inicialización de campo"
            },
            {
              "icon": "",
              "label": "count es 0 e total es 0, autobox cuando se lee por primera vez"
            },
            {
              "icon": "",
              "label": "count es 0 e total es null — los campos numéricos primitivos por defecto a cero, pero un campo de referencia wrapper no inicializado por defecto a null como cualquier otra referencia de objeto"
            },
            {
              "icon": "",
              "label": "Ambos por defecto a null hasta ser asignados explícitamente, ya que Java no tiene defectos numéricos implícitos"
            }
          ]
        },
        {
          "question": "Un bucle se ejecuta 1000 veces, cada vez haciendo result += \"x\"; en un String result. ¿Qué sucede realmente bajo el capó cada iteración?",
          "options": [
            {
              "icon": "",
              "label": "La matriz de caracteres interna del objeto String existente se extiende en su lugar"
            },
            {
              "icon": "",
              "label": "Java automáticamente agrupa las concatenaciones y solo asigna un objeto String final"
            },
            {
              "icon": "",
              "label": "Se compila a una llamada StringBuilder.append() única compartida en las 1000 iteraciones, sin objeto extra asignado por iteración"
            },
            {
              "icon": "",
              "label": "Un nuevo objeto String se crea y result se reasigna para apuntar a él — el objeto String anterior se descarta, porque String es inmutable y += no puede modificarlo en su lugar"
            }
          ]
        },
        {
          "question": "final List<String> names = new ArrayList<>(); ¿Cuál de estas es verdadera?",
          "options": [
            {
              "icon": "",
              "label": "names.add(\"Ana\") compila y funciona bien — final solo previene reasignar la referencia names en sí, no mutar el objeto al que apunta"
            },
            {
              "icon": "",
              "label": "names.add(\"Ana\") es un error de compilación, porque final hace que la lista sea inmodificable"
            },
            {
              "icon": "",
              "label": "La lista es threadsafe para escrituras concurrentes porque fue declarada final"
            },
            {
              "icon": "",
              "label": "final en una variable local no tiene efecto a menos que el tipo también se declare inmutable"
            }
          ]
        },
        {
          "question": "int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; System.out.println(a == b); System.out.println(Arrays.equals(a, b)); ¿Qué imprimen las dos líneas?",
          "options": [
            {
              "icon": "",
              "label": "false luego true — == compara referencias de array (dos objetos array diferentes), mientras que Arrays.equals compara los elementos"
            },
            {
              "icon": "",
              "label": "true luego true — arrays con contenido idéntico son el mismo objeto en Java"
            },
            {
              "icon": "",
              "label": "false luego false — Arrays.equals solo funciona para arrays de objetos, no arrays primitivos int"
            },
            {
              "icon": "",
              "label": "true luego false — == en arrays compara contenido, y Arrays.equals es redundante"
            }
          ]
        },
        {
          "question": "void show(Object o) { print(\"Object\"); } void show(String s) { print(\"String\"); } Object ref = \"hello\"; show(ref); ¿Cuál sobrecarga se ejecuta, y por qué?",
          "options": [
            {
              "icon": "",
              "label": "show(String) se ejecuta, porque Java inspecciona la clase actual en tiempo de ejecución del objeto antes de escoger la sobrecarga"
            },
            {
              "icon": "",
              "label": "show(Object) se ejecuta — la resolución de sobrecarga se decide en tiempo de compilación usando el tipo declarado de la variable, no el tipo actual en tiempo de ejecución del objeto al que se refiere"
            },
            {
              "icon": "",
              "label": "Es un error de compilación — Object no puede pasarse donde existe una sobrecarga más específica"
            },
            {
              "icon": "",
              "label": "Ambos métodos se ejecutan, uno cada uno, porque Java resuelve sobrecargas intentando cada coincidencia"
            }
          ]
        },
        {
          "question": "La clase Base tiene static void greet() { print(\"Base\"); }. La clase Derived extiende Base y también declara static void greet() { print(\"Derived\"); }. Escribes: Base ref = new Derived(); ref.greet(); ¿Qué imprime?",
          "options": [
            {
              "icon": "",
              "label": "Derived — los métodos static anulan igual que los métodos de instancia, siguiendo el tipo actual en tiempo de ejecución del objeto"
            },
            {
              "icon": "",
              "label": "Es un error de compilación — los métodos static no pueden llamarse a través de una referencia de instancia"
            },
            {
              "icon": "",
              "label": "Base — los métodos static no son polimórficos; llamar uno a través de una referencia se resuelve por el tipo declarado de la referencia en tiempo de compilación, no el tipo actual del objeto"
            },
            {
              "icon": "",
              "label": "Tanto Base como Derived imprimen, porque la llamada se resuelve tanto a la versión oculta como a la ocultadora"
            }
          ]
        },
        {
          "question": "Dentro de un método, escribes int count = 0; luego intentas referenciar count desde dentro de una lambda pasada a otro método. ¿Qué debe ser verdadero sobre count para que esto compile?",
          "options": [
            {
              "icon": "",
              "label": "count debe ser efectivamente final — nunca reasignada en ningún lugar después de su valor inicial — porque una lambda captura una instantánea, no una referencia viva a una variable local mutable"
            },
            {
              "icon": "",
              "label": "Nada — cualquier variable local puede ser libremente leída y reasignada desde dentro de una lambda"
            },
            {
              "icon": "",
              "label": "count debe declararse volatile para que la lambda siempre vea su valor más reciente"
            },
            {
              "icon": "",
              "label": "count debe ser un campo, no una variable local — las lambdas no pueden capturar locales en absoluto"
            }
          ]
        },
        {
          "question": "void rename(StringBuilder sb) { sb = new StringBuilder(\"new\"); } se llama como StringBuilder original = new StringBuilder(\"old\"); rename(original); ¿Qué es original después de la llamada?",
          "options": [
            {
              "icon": "",
              "label": "Se convierte en \"new\" — los objetos se pasan por referencia en Java, así que reasignar el parámetro cambia la variable del llamador también"
            },
            {
              "icon": "",
              "label": "Se convierte en \"new\" solo para tipos mutables como StringBuilder, pero no para tipos inmutables"
            },
            {
              "icon": "",
              "label": "Sigue siendo \"old\" — Java pasa la referencia en sí por valor, así que reasignar el parámetro dentro del método solo reapunta la copia local de la referencia, dejando el original del llamador intacto"
            },
            {
              "icon": "",
              "label": "Lanza una excepción en tiempo de ejecución, porque sb fue reasignada dentro del método"
            }
          ]
        },
        {
          "question": "Una clase tiene un bloque inicializador static, un bloque inicializador de instancia, y un constructor, en ese orden de fuente. Creas dos objetos de esta clase uno después del otro. ¿En qué orden se ejecutan?",
          "options": [
            {
              "icon": "",
              "label": "El bloque static se ejecuta exactamente una vez, la primera vez que se carga la clase; luego para cada objeto, el bloque de instancia se ejecuta y luego el cuerpo del constructor se ejecuta"
            },
            {
              "icon": "",
              "label": "Los tres se ejecutan nuevamente, en orden de fuente, para cada objeto creado"
            },
            {
              "icon": "",
              "label": "El constructor se ejecuta primero para cada objeto, luego el bloque de instancia, luego el bloque static una vez al final"
            },
            {
              "icon": "",
              "label": "El bloque static se ejecuta una vez por objeto, justo antes del bloque de instancia"
            }
          ]
        },
        {
          "question": "Una clase tiene void log(String s) y void log(String... args). Llamas log(\"hi\"). ¿Cuál se ejecuta?",
          "options": [
            {
              "icon": "",
              "label": "log(String... args) — las sobrecargas varargs siempre se prefieren cuando ambas son aplicables"
            },
            {
              "icon": "",
              "label": "Es un error de compilación — la llamada es ambigua entre las dos sobrecargas"
            },
            {
              "icon": "",
              "label": "log(String s) — cuando una sobrecarga arity-fija coincide exactamente, Java siempre la prefiere sobre una sobrecarga varargs, que solo se usa como último recurso"
            },
            {
              "icon": "",
              "label": "La que se declara primero en el archivo de fuente se ejecuta"
            }
          ]
        },
        {
          "question": "La primera línea de un constructor llama this(0); para delegar a otro constructor en la misma clase. ¿Dónde está permitido aparecer?",
          "options": [
            {
              "icon": "",
              "label": "En cualquier lugar en el cuerpo del constructor, siempre y cuando se ejecute antes de que el objeto se devuelva"
            },
            {
              "icon": "",
              "label": "Solo como la primera sentencia del constructor — this() (o super()) debe ser la primera línea, y un constructor no puede llamar tanto this() como super()"
            },
            {
              "icon": "",
              "label": "Solo como la última sentencia, después de que toda la inicialización de campo esté completa"
            },
            {
              "icon": "",
              "label": "Solo en constructores marcados explícitamente como delegadores, usando una palabra clave delegate"
            }
          ]
        },
        {
          "question": "Necesitas una lista que tendrá elementos insertados en el frente muy frecuentemente, y las lecturas de acceso aleatorio son raras. ¿Cuál es el mejor ajuste estructural, y por qué?",
          "options": [
            {
              "icon": "",
              "label": "ArrayList — su matriz de soporte contiguo hace que cada operación, incluyendo inserción en el frente, sea más rápida que una estructura vinculada"
            },
            {
              "icon": "",
              "label": "Funcionan idénticamente, porque ambas implementan la interfaz List con las mismas garantías de tiempo"
            },
            {
              "icon": "",
              "label": "LinkedList — insertar en el frente es O(1) porque solo revincula un nodo, mientras que ArrayList tiene que desplazar cada elemento existente hacia arriba en uno, haciendo la inserción en el frente O(n)"
            },
            {
              "icon": "",
              "label": "LinkedList, porque soporta acceso aleatorio en O(1) mientras que ArrayList no"
            }
          ]
        },
        {
          "question": "Insertas cinco entradas en un HashMap simple en un orden específico, luego iteras sobre él con un bucle for-each. ¿En qué orden salen las entradas?",
          "options": [
            {
              "icon": "",
              "label": "El mismo orden en que se insertaron las entradas, ya que los mapas Java siempre preservan el orden de inserción"
            },
            {
              "icon": "",
              "label": "Sin orden garantizado en absoluto — el orden de iteración de HashMap depende de la colocación del depósito hash, no del orden de inserción, e incluso puede cambiar entre ejecuciones; LinkedHashMap es lo que preserva el orden de inserción"
            },
            {
              "icon": "",
              "label": "Ordenado por clave automáticamente, de la misma manera que se comporta un TreeMap"
            },
            {
              "icon": "",
              "label": "Orden inverso de inserción, porque HashMap internamente usa una pila"
            }
          ]
        },
        {
          "question": "¿Cuántas claves nulas puede sostener un HashMap plano de java.util al mismo tiempo, y cuántos valores nulos?",
          "options": [
            {
              "icon": "",
              "label": "Sin claves nulas y sin valores nulos nunca permitidos en ninguna implementación de Map"
            },
            {
              "icon": "",
              "label": "Como máximo una clave nula, y cualquier número de valores nulos — HashMap permite una clave nula, a diferencia de Hashtable que no permite ninguna"
            },
            {
              "icon": "",
              "label": "Claves nulas ilimitadas y valores nulos ilimitados, ya que null se trata como cualquier otra clave"
            },
            {
              "icon": "",
              "label": "Como máximo una clave nula y un valor nulo, ambos limitados a uno"
            }
          ]
        },
        {
          "question": "Iteras una List con un bucle for-each y llamas list.remove(item) directamente en la lista desde dentro del cuerpo del bucle, en algunos pero no todos los elementos. ¿Qué sucede?",
          "options": [
            {
              "icon": "",
              "label": "Funciona correctamente y elimina exactamente los elementos deseados"
            },
            {
              "icon": "",
              "label": "Silenciosamente salta el elemento después del que fue eliminado, pero por lo demás se completa sin error"
            },
            {
              "icon": "",
              "label": "Lanza IndexOutOfBoundsException una vez que el bucle llega al tamaño anterior de la lista"
            },
            {
              "icon": "",
              "label": "Lanza ConcurrentModificationException — modificar la estructura de la lista directamente mientras un iterador implícito la camina es detectado y rechazado; Iterator.remove() debe usarse en su lugar"
            }
          ]
        },
        {
          "question": "En tiempo de ejecución, dada una List<String> list, ¿qué puedes realmente determinar sobre su parámetro de tipo genérico vía reflection o instanceof?",
          "options": [
            {
              "icon": "",
              "label": "Puedes llamar list.getElementType() para recuperar String.class en tiempo de ejecución"
            },
            {
              "icon": "",
              "label": "instanceof List<String> compila y comprueba correctamente el tipo de elemento"
            },
            {
              "icon": "",
              "label": "La JVM almacena el parámetro de tipo como metadatos ocultos accesible vía list.getGenericType()"
            },
            {
              "icon": "",
              "label": "Nada — la información de tipo genérico se borra en tiempo de compilación, así que en tiempo de ejecución el objeto es solo una List, y no hay forma de recuperar si fue declarada 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(\"?\"); } Si day es 6, ¿qué imprime?",
          "options": [
            {
              "icon": "",
              "label": "Solo Saturday — cada bloque case en un switch siempre se detiene después de su propia sentencia print"
            },
            {
              "icon": "",
              "label": "Solo Sunday — la coincidencia comienza en case 6 pero solo se ejecuta la última etiqueta coincidente antes de break"
            },
            {
              "icon": "",
              "label": "Nada se imprime, porque day 6 no tiene un matching case con su propio break directamente bajo él"
            },
            {
              "icon": "",
              "label": "Saturday luego Sunday — case 6 no tiene break, así que la ejecución cae a través del código del siguiente case antes de golpear el siguiente break"
            }
          ]
        },
        {
          "question": "Una clase Product necesita un único orden de clasificación \"natural\" obvio por precio, más la capacidad de clasificar también por nombre o por nivel de existencias en diferentes lugares del código. ¿Cuál combinación es el diseño correcto?",
          "options": [
            {
              "icon": "",
              "label": "Implementa Comparable<Product> tres veces por separado, una por ordenamiento, y deja que el llamador elija qué compareTo se ejecuta"
            },
            {
              "icon": "",
              "label": "Implementa Comparable<Product> para el ordenamiento de precio natural, y escribe instancias Comparator<Product> separadas para los ordenamientos de nombre y nivel de existencias usados en otros lugares"
            },
            {
              "icon": "",
              "label": "Usa solo Comparator para cada ordenamiento, incluyendo precio, ya que Comparable no tiene ventaja aquí"
            },
            {
              "icon": "",
              "label": "Usa solo Comparable añadiendo tres métodos compareTo sobrecargados, uno por ordenamiento"
            }
          ]
        },
        {
          "question": "Un método llama new FileReader(path), que declara throws IOException. IOException extiende Exception, no RuntimeException. ¿Qué debes hacer para que esto compile?",
          "options": [
            {
              "icon": "",
              "label": "Nada — el compilador solo refuerza esto para excepciones que extienden RuntimeException"
            },
            {
              "icon": "",
              "label": "Ya sea atrapa IOException en un try/catch o declara throws IOException en tu propio método — las excepciones checked deben ser manejadas o propagadas explícitamente, a diferencia de las subclases de RuntimeException unchecked"
            },
            {
              "icon": "",
              "label": "Envuelve la llamada en un try/catch para RuntimeException, ya que IOException se desmarca automáticamente en Java moderno"
            },
            {
              "icon": "",
              "label": "Declara el método como static — los métodos static están exentos de manejo de excepciones checked"
            }
          ]
        },
        {
          "question": "try (Resource a = new Resource(\"A\"); Resource b = new Resource(\"B\")) { ... } usa dos recursos implementando AutoCloseable. Si el bloque try termina normalmente, ¿en qué orden se cierran?",
          "options": [
            {
              "icon": "",
              "label": "A se cierra primero, luego B, coincidiendo con el orden de declaración"
            },
            {
              "icon": "",
              "label": "B se cierra primero, luego A — try-with-resources cierra recursos en el orden inverso al que fueron declarados"
            },
            {
              "icon": "",
              "label": "Ambos se cierran simultáneamente, ya que try-with-resources paraleliza la limpieza"
            },
            {
              "icon": "",
              "label": "Solo B se cierra automáticamente — A aún debe cerrarse manualmente en un bloque finally"
            }
          ]
        },
        {
          "question": "int getValue() { try { return 1; } finally { return 2; } } ¿Qué retorna al llamar getValue()?",
          "options": [
            {
              "icon": "",
              "label": "1 — el valor de retorno del bloque try ya se confirma antes de que finally se ejecute, así que finally no puede cambiarlo"
            },
            {
              "icon": "",
              "label": "Lanza una excepción en tiempo de ejecución, porque un método no puede retornar de dos lugares"
            },
            {
              "icon": "",
              "label": "2 — una sentencia return dentro de finally anula y reemplaza cualquier return ya en progreso desde el bloque try, descartando el valor 1 completamente"
            },
            {
              "icon": "",
              "label": "Un error de compilación — finally no está permitido contener una sentencia return"
            }
          ]
        },
        {
          "question": "try { ... } catch (IOException e) { ... } catch (FileNotFoundException e) { ... }, y FileNotFoundException extiende IOException. ¿Qué sucede cuando intentas compilar esto?",
          "options": [
            {
              "icon": "",
              "label": "Un error de compilación — el bloque catch de FileNotFoundException es inalcanzable porque el bloque catch anterior más general IOException ya coincide con toda FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Compila bien, y el bloque más específico de FileNotFoundException se ejecuta siempre que ese tipo exacto se lance"
            },
            {
              "icon": "",
              "label": "Compila bien, y ambos bloques catch se ejecutan en orden para una FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Está bien en tiempo de compilación pero lanza un error en tiempo de ejecución la primera vez que una FileNotFoundException realmente ocurre"
            }
          ]
        },
        {
          "question": "Una clase tiene un método de instancia sincronizado process() y un método static sincronizado configure(). ¿Qué específicamente, bloquea cada uno?",
          "options": [
            {
              "icon": "",
              "label": "process() bloquea el monitor de la instancia de objeto específica en que se llama, mientras que configure() bloquea el monitor del objeto Class mismo — compartido por cada instancia"
            },
            {
              "icon": "",
              "label": "Ambos bloquean el mismo único bloqueo global para toda la JVM, sin importar instancia o clase"
            },
            {
              "icon": "",
              "label": "process() bloquea el objeto Class, y configure() bloquea la instancia que sea que lo llame"
            },
            {
              "icon": "",
              "label": "Ninguno realmente bloquea nada a menos que un bloque synchronized también se use dentro del cuerpo del método"
            }
          ]
        },
        {
          "question": "Un campo se declara volatile int counter = 0;, y múltiples hilos ejecutan counter++ en él simultáneamente. ¿Previene volatile las actualizaciones perdidas aquí?",
          "options": [
            {
              "icon": "",
              "label": "Sí — volatile hace que cada operación en el campo sea atómica, incluyendo incrementos"
            },
            {
              "icon": "",
              "label": "No — volatile solo garantiza que las lecturas ven la escritura más reciente a través de hilos (visibilidad); counter++ es una lectura-modificación-escritura con múltiples pasos, y volatile no hace nada para que esos pasos sean atómicos"
            },
            {
              "icon": "",
              "label": "Sí, pero solo para campos int y long específicamente, debido a cómo la JVM maneja valores de 64-bit"
            },
            {
              "icon": "",
              "label": "No, y volatile también falla en garantizar visibilidad para tipos primitivos como int"
            }
          ]
        },
        {
          "question": "Interface A e interface B cada una declaran un método default describe(). Una clase implementa ambas A y B y no anula describe() por sí misma. ¿Qué sucede?",
          "options": [
            {
              "icon": "",
              "label": "El compilador elige automáticamente la versión de la interface A, ya que se lista primero en la cláusula implements"
            },
            {
              "icon": "",
              "label": "Ambas versiones se ejecutan, una después de la otra, siempre que describe() se llama"
            },
            {
              "icon": "",
              "label": "Está bien en tiempo de compilación, pero lanza una AmbiguousMethodException la primera vez que describe() se llama"
            },
            {
              "icon": "",
              "label": "Un error de compilación — cuando dos interfaces contribuyen el mismo método default, la clase implementadora debe anularlo ella misma para resolver la ambigüedad, ya que Java no adivinará cuál significabas"
            }
          ]
        },
        {
          "question": "list.stream().filter(x -> x > 0).map(x -> x * 2); se escribe pero el resultado nunca se asigna a una operación terminal como .collect() o .forEach(). ¿Qué sucede realmente cuando se ejecuta esta línea?",
          "options": [
            {
              "icon": "",
              "label": "Nada sucede a los elementos de la lista en absoluto — filter y map son operaciones intermediate lazy que solo construyen una descripción de tubería; sin una operación terminal, nada de esa tubería se ejecuta realmente"
            },
            {
              "icon": "",
              "label": "Cada elemento se filtra y mapea inmediatamente, exactamente como si una operación terminal se hubiera llamado"
            },
            {
              "icon": "",
              "label": "Solo el filter se ejecuta inmediatamente; map se difiere hasta que aparece una operación terminal"
            },
            {
              "icon": "",
              "label": "Lanza una IllegalStateException, porque una tubería de stream requiere una operación terminal para compilar"
            }
          ]
        }
      ],
      "optionOrderVersion": "0154f9576054ad6f"
    }
  }
}
