{
  "assessmentTests": {
    "java_test": {
      "name": "Java-Test",
      "desc": "30 Szenariofragen zu Core-Syntax, OOP-Mechanik, Collections, Generics, Exceptions und Concurrency — erfahre, ob dein Java das bedeutet, was eine Stellenanzeige unter Java-Kenntnissen versteht.",
      "recommendation": "Dein Java-Kenntnisprofil",
      "results": {
        "beginner": {
          "name": "Anfänger",
          "desc": "Du kannst funktionierende Klassen und Methoden schreiben, aber die Fragen, die du verpasst hast, konzentrieren sich auf Identität und Defaults statt auf Syntax — String == vs .equals, warum new String(\"x\") den String-Pool nie wiederverwend, was bei einem numerischen Feld standardmäßig gesetzt wird gegenüber bei einem Wrapper-Feld. Das hat nichts damit zu tun, schlecht in Java zu sein; das sind spezifische Regeln, die Menschen, die die Sprache durch Ausprobieren gelernt haben, genauso oft verwirren wie Anfänger. Sie sind wichtig bei der Arbeit, weil jede davon ein Ort ist, wo Code kompiliert, läuft und still das Falsche tut.",
          "recommendation": "Beginne mit drei Dingen in dieser Reihenfolge: warum == auf zwei String-Objekten Referenzen vergleicht, nicht Zeichen (und .equals ist das, was du fast immer willst), wie int-Überlauf still umgerechnet wird statt zu werfen, und warum ein nicht initialisiertes Integer-Feld null standardmäßig gesetzt wird, während ein int-Feld 0 standardmäßig gesetzt wird. Oracle's offizielle Java-Tutorien und Baeldung behandeln alle drei mit ausführbaren Beispielen."
        },
        "intermediate": {
          "name": "Mittelstufe",
          "desc": "Du bewältigst alltäglichen Anwendungscode mühelos — Klassen, Collections, gerader Kontrollfluss — und würdest durch Routine-Feature-Arbeit nicht verlangsamt. Der Unterschied zwischen hier und Advanced ist hauptsächlich, was passiert, wenn Javas Regeln mit Concurrency und Generics interagieren: eine statische Methode, die nach deklariertem Typ aufgelöst wird, statt nach dem Objekt, das du erwartet hast, eine HashMap-Iterations-Ordnung, auf die du still verlässt, eine ConcurrentModificationException aus dem Entfernen eines Elements mid-Loop. Das sind die Art von Bugs, die eine schnelle Sichtprüfung bestehen und nur unter einer spezifischen Runtime-Bedingung sichtbar werden.",
          "recommendation": "Konzentriere dich darauf, wie Compiler-Sicht auf deinen Code von dem unterscheidet, was läuft: Overload-Auflösung und statisches-Methoden-Verstecken nach deklariertem Typ statt Runtime-Typ, warum HashMap keine Iterations-Ordnung-Garantie gibt, und warum direktes Modifizieren einer Liste beim Iterieren ConcurrentModificationException wirft. Dann Generics-Erasure, weil das Menschen verwirrt, die Collections einzeln bereits kennen."
        },
        "advanced": {
          "name": "Fortgeschritten",
          "desc": "Das ist die Stufe, die die meisten Stellenanzeigen unter \"starkem Java\" verstehen. Du liest eine Klasse mit statischen Blöcken, Instanzblöcken und mehreren Konstruktoren und kannst die exakte Ordnung vorhersagen, in der sie laufen, du weißt, warum ein finally-Block still einen try-Block's Rückgabewert verwerfen kann, und du greifen zu try-with-resources statt zu manuellem finally-Schließen, weil du die Ordnungsgarantie kennst, die es dir gibt. Was diese Gruppe von der Spitze trennt, ist die concurrent und interface-level Seite der Arbeit: was synchronized tatsächlich sperrt, was volatile tut und nicht tut garantiert, und wie default-method-Konflikte aufgelöst werden.",
          "recommendation": "Drücke in die Teile, die Code schützen, das andere Threads und andere Interfaces auch anfassen: der Unterschied zwischen dem, was eine synchronized-Instanzmethode sperrt gegenüber einer synchronized statischen Methode, warum volatile Sichtbarkeit gibt, aber nicht Atomarität für counter++, und wie Java dich zwingt, einen default-method-Diamond von Hand aufzulösen. Baeldung's und Java Concurrency in Practice's Material über das Java Memory Model ist die natürliche nächste Station für beides."
        },
        "expert": {
          "name": "Experte",
          "desc": "Du hast in jedem Abschnitt die höchste Punktzahl erreicht — Core-Syntax und Typen, OOP-Mechanik und Bereich, Collections und Generics, und Exceptions, Concurrency und Idiome. Praktisch bedeutet das, du kannst eine fremde Klasse bekommen und erklären, warum ein Kompilierungsfehler, eine Runtime-Exception oder ein still falscher Wert passiert, nicht nur was die Syntax sagen sollte, was die schwierigere und wertvollere Fähigkeit ist. Auf dieser Stufe ist die Sprache selbst rarely der einschränkende Faktor; die Grenze ist normalerweise Concurrency-Design oder die Datenshape darunter.",
          "recommendation": "Die Erträge sind jetzt in Design und Diagnose: ein Thread-Dump lesen, bevor du annimmst, ein Hang ist ein Deadlock, zwischen synchronized, java.util.concurrent locks und atomics als Kompromiss statt als Default wählen, und Stream-Pipeline-Design, das absichtlich lazy bleibt statt durch Zufall. Wenn du für eine Rolle überprüft wirst, beschreibe einen Concurrency-Bug wie eine verpasste happens-before Kante oder eine ConcurrentModificationException, die du in der Produktion gefunden hast, statt Java-Features zu nennen — das demonstriert das Denken, nicht nur das Vokabular."
        }
      },
      "questions": [
        {
          "question": "Du erstellst zwei String-Objekte: String a = new String(\"cat\"); String b = new String(\"cat\"); Was ergibt a == b?",
          "options": [
            {
              "icon": "",
              "label": "true — String-Literale mit den gleichen Zeichen sind immer das gleiche Objekt"
            },
            {
              "icon": "",
              "label": "true — new String(...) verwendet den String-Pool wieder, daher teilt sich identischer Text immer ein Objekt"
            },
            {
              "icon": "",
              "label": "Ein Compilierungsfehler — == kann nicht zum Vergleichen von String-Objekten verwendet werden"
            },
            {
              "icon": "",
              "label": "false — new String(...) weist immer ein neues Objekt auf dem Heap zu, daher vergleicht == zwei verschiedene Referenzen, obwohl .equals(b) true zurückgeben würde"
            }
          ]
        },
        {
          "question": "Integer x = 100; Integer y = 100; System.out.println(x == y); dann Integer x2 = 200; Integer y2 = 200; System.out.println(x2 == y2); Was drucken die zwei Zeilen?",
          "options": [
            {
              "icon": "",
              "label": "true dann true — autoboxed Integer-Objekte werden unabhängig vom Wert immer gecacht"
            },
            {
              "icon": "",
              "label": "false dann false — == auf Integer-Objekten ist immer ein Referenzvergleich, daher stimmen gleiche Werte nie überein"
            },
            {
              "icon": "",
              "label": "true dann false, aber nur weil 200 eine Byte überläuft — die Cache-Grenze hat nichts mit -128..127 zu tun"
            },
            {
              "icon": "",
              "label": "true dann false — autoboxed Integer-Werte von -128 bis 127 werden gecacht und geteilt, daher verwendet 100 ein Objekt wieder, aber 200 fällt außerhalb des Caches und autoboxed zu zwei separaten Objekten"
            }
          ]
        },
        {
          "question": "int max = Integer.MAX_VALUE; System.out.println(max + 1); Was passiert?",
          "options": [
            {
              "icon": "",
              "label": "Es wirft eine ArithmeticException für Integer-Überlauf"
            },
            {
              "icon": "",
              "label": "Es druckt Integer.MAX_VALUE wieder, weil Java am Maximum des Typs klemmt"
            },
            {
              "icon": "",
              "label": "Es druckt Integer.MIN_VALUE — int-Arithmetik wickelt still auf Überlauf um statt zu werfen"
            },
            {
              "icon": "",
              "label": "Es ist ein Compilierungsfehler — der Compiler erkennt den Überlauf im Voraus"
            }
          ]
        },
        {
          "question": "System.out.println(0.1 + 0.2 == 0.3); Was druckt das, und warum?",
          "options": [
            {
              "icon": "",
              "label": "false — 0.1, 0.2 und 0.3 können nicht genau in binären Gleitkommazahlen dargestellt werden, daher trägt die Summe Rundungsfehler, der sie nicht Bit-für-Bit gleich zu 0.3 macht"
            },
            {
              "icon": "",
              "label": "true — Java rundet Double-Arithmetik zur nächsten darstellbaren Dezimalzahl vor dem Vergleichen"
            },
            {
              "icon": "",
              "label": "true — Addition von zwei Doubles ist immer exakt für Werte mit zwei Dezimalstellen"
            },
            {
              "icon": "",
              "label": "Es wirft eine Exception, weil == nicht für double in Java definiert ist"
            }
          ]
        },
        {
          "question": "Eine Klasse deklariert zwei Instanzfelder ohne Initialisierer: int count; und Integer total; Bevor der Konstruktor läuft, was sind ihre Standardwerte?",
          "options": [
            {
              "icon": "",
              "label": "Beide standardisieren zu 0, weil Integer zu int bei der Feld-Initialisierung autoboxiert"
            },
            {
              "icon": "",
              "label": "count ist 0 und total ist 0, boxed automatisch das erste Mal, wenn es gelesen wird"
            },
            {
              "icon": "",
              "label": "count ist 0 und total ist null — primitive numerische Felder standardisieren zu null, aber ein nicht initialisiertes Wrapper-Referenzfeld standardisiert zu null wie jede andere Objektreferenz"
            },
            {
              "icon": "",
              "label": "Beide standardisieren zu null bis explizit zugewiesen, da Java keine impliziten numerischen Defaults hat"
            }
          ]
        },
        {
          "question": "Eine Schleife läuft 1000 Mal, jedes Mal result += \"x\"; auf einem String-Ergebnis. Was passiert unter der Oberfläche jedes Iteration?",
          "options": [
            {
              "icon": "",
              "label": "Das existierende String-Objekt's internes Zeichenarray wird an Ort erweitert"
            },
            {
              "icon": "",
              "label": "Java bündelt automatisch die Verkettungen und weist nur ein finales String-Objekt zu"
            },
            {
              "icon": "",
              "label": "Es kompiliert zu einem einzelnen StringBuilder.append()-Aufruf, der über alle 1000 Iterationen geteilt wird, mit keinem extra Objekt pro Iteration zugewiesen"
            },
            {
              "icon": "",
              "label": "Ein völlig neues String-Objekt wird erstellt und das Ergebnis zeigt darauf — das vorherige String-Objekt wird verworfen, weil String unveränderlich ist und += es nicht an Ort ändern kann"
            }
          ]
        },
        {
          "question": "final List<String> names = new ArrayList<>(); Welche davon ist wahr?",
          "options": [
            {
              "icon": "",
              "label": "names.add(\"Ana\") kompiliert und funktioniert gut — final verhindert nur die Neuzuweisung der names-Referenz selbst, nicht das Mutieren des Objekts, auf das es zeigt"
            },
            {
              "icon": "",
              "label": "names.add(\"Ana\") ist ein Compilierungsfehler, weil final die Liste selbst unveränderlich macht"
            },
            {
              "icon": "",
              "label": "Die Liste ist Thread-sicher für gleichzeitige Schreibvorgänge, weil sie final deklariert wurde"
            },
            {
              "icon": "",
              "label": "final auf einer lokalen Variable hat keine Auswirkung, es sei denn, der Typ ist auch als unveränderlich deklariert"
            }
          ]
        },
        {
          "question": "int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; System.out.println(a == b); System.out.println(Arrays.equals(a, b)); Was drucken die zwei Zeilen?",
          "options": [
            {
              "icon": "",
              "label": "false dann true — == vergleicht Array-Referenzen (zwei verschiedene Array-Objekte), während Arrays.equals die Elemente vergleicht"
            },
            {
              "icon": "",
              "label": "true dann true — Arrays mit identischem Inhalt sind das gleiche Objekt in Java"
            },
            {
              "icon": "",
              "label": "false dann false — Arrays.equals funktioniert nur für Objekt-Arrays, nicht für primitive int-Arrays"
            },
            {
              "icon": "",
              "label": "true dann false — == auf Arrays vergleicht Inhalt, und Arrays.equals ist redundant"
            }
          ]
        },
        {
          "question": "void show(Object o) { print(\"Object\"); } void show(String s) { print(\"String\"); } Object ref = \"hello\"; show(ref); Welche Überladung läuft, und warum?",
          "options": [
            {
              "icon": "",
              "label": "show(String) läuft, weil Java die tatsächliche Runtime-Klasse des Objekts überprüft, bevor die Überladung ausgewählt wird"
            },
            {
              "icon": "",
              "label": "show(Object) läuft — Überladungs-Auflösung wird zur Compile-Zeit mit dem deklarierten Typ der Variable entschieden, nicht mit der tatsächlichen Runtime-Typ des Objekts, auf das es zeigt"
            },
            {
              "icon": "",
              "label": "Es ist ein Compilierungsfehler — Object kann nicht weitergegeben werden, wo eine spezifischere Überladung existiert"
            },
            {
              "icon": "",
              "label": "Beide Methoden laufen, je einmal, weil Java Überladungen durch Versuchen jedes Match auflöst"
            }
          ]
        },
        {
          "question": "Klasse Base hat static void greet() { print(\"Base\"); }. Klasse Derived erweitert Base und deklariert auch static void greet() { print(\"Derived\"); }. Du schreibst: Base ref = new Derived(); ref.greet(); Was druckt?",
          "options": [
            {
              "icon": "",
              "label": "Derived — statische Methoden überschreiben genauso wie Instanzmethoden und folgen der tatsächlichen Objekt's Runtime-Typ"
            },
            {
              "icon": "",
              "label": "Es ist ein Compilierungsfehler — statische Methoden können nicht durch eine Instanzreferenz aufgerufen werden"
            },
            {
              "icon": "",
              "label": "Base — statische Methoden sind nicht polymorphisch; das Aufrufen einer durch eine Referenz wird nach der Referenz's deklarierten Typ zur Compile-Zeit aufgelöst, nicht nach der Objekt's tatsächlichen Typ"
            },
            {
              "icon": "",
              "label": "Sowohl Base als auch Derived drucken, weil der Aufruf zur beiden verborgenen und versteckenden Version auflöst"
            }
          ]
        },
        {
          "question": "Innerhalb einer Methode schreibst du int count = 0; dann versuchst, count aus innerhalb eines Lambda aufzurufen, weitergegeben zu einer anderen Methode. Was muss über count wahr sein, damit das kompiliert?",
          "options": [
            {
              "icon": "",
              "label": "count muss effectively final sein — nie nach seinem Initialwert neu zugewiesen — weil ein Lambda einen Schnappschuss erfasst, keine live Referenz zu einer veränderbaren lokalen Variable"
            },
            {
              "icon": "",
              "label": "Nichts — jede lokale Variable kann frei von innerhalb eines Lambda gelesen und neu zugewiesen werden"
            },
            {
              "icon": "",
              "label": "count muss als volatile deklariert sein, damit das Lambda immer seinen neuesten Wert sieht"
            },
            {
              "icon": "",
              "label": "count muss ein Feld sein, nicht eine lokale Variable — Lambdas können Locals überhaupt nicht erfassen"
            }
          ]
        },
        {
          "question": "void rename(StringBuilder sb) { sb = new StringBuilder(\"new\"); } wird als StringBuilder original = new StringBuilder(\"old\"); rename(original); aufgerufen. Was ist original nach dem Aufruf?",
          "options": [
            {
              "icon": "",
              "label": "Wird zu \"new\" — Objekte werden als Referenz in Java übergeben, daher ändert die Neuneuzuweisung des Parameters auch die Variablen des Aufrufers"
            },
            {
              "icon": "",
              "label": "Wird zu \"new\" nur für veränderbare Typen wie StringBuilder, aber nicht für unveränderliche Typen"
            },
            {
              "icon": "",
              "label": "Bleibt \"old\" — Java übergibt die Referenz selbst als Wert, daher zeigt nur die lokale Kopie der Referenz auf ein neues Objekt, das Original des Aufrufers bleibt unberührt"
            },
            {
              "icon": "",
              "label": "Wirft eine Runtime-Exception, weil sb innerhalb der Methode neu zugewiesen wurde"
            }
          ]
        },
        {
          "question": "Eine Klasse hat einen statischen Initialisierer-Block, einen Instanz-Initialisierer-Block und einen Konstruktor in dieser Quellordn. Du erstellst zwei Objekte dieser Klasse nacheinander. In welcher Ordnung laufen diese?",
          "options": [
            {
              "icon": "",
              "label": "Der statische Block läuft genau einmal, das erste Mal die Klasse geladen wird; dann für jedes Objekt läuft der Instanzblock und dann läuft der Konstruktor-Körper"
            },
            {
              "icon": "",
              "label": "Alle drei laufen frisch, in Quellordn, für jedes einzelne Objekt erstellt"
            },
            {
              "icon": "",
              "label": "Der Konstruktor läuft zuerst für jedes Objekt, dann der Instanzblock, dann der statische Block einmal am Ende"
            },
            {
              "icon": "",
              "label": "Der statische Block läuft einmal pro Objekt, rechts vor dem Instanzblock"
            }
          ]
        },
        {
          "question": "Eine Klasse hat void log(String s) und void log(String... args). Du rufst log(\"hi\") auf. Welche läuft?",
          "options": [
            {
              "icon": "",
              "label": "log(String... args) — varargs Überladungen werden immer bevorzugt, wenn beide anwendbar sind"
            },
            {
              "icon": "",
              "label": "Es ist ein Compilierungsfehler — der Aufruf ist mehrdeutig zwischen den zwei Überladungen"
            },
            {
              "icon": "",
              "label": "log(String s) — wenn eine fest-Ariät Überladung genau stimmt, bevorzugt Java es immer über eine varargs Überladung, die nur als letzter Ausweg verwendet wird"
            },
            {
              "icon": "",
              "label": "Welchmal wird zuerst in der Quelldatei deklariert läuft"
            }
          ]
        },
        {
          "question": "Eine erste Zeile eines Konstruktors ruft this(0); auf, um einen anderen Konstruktor in der gleichen Klasse zu delegieren. Wo darf das erscheinen?",
          "options": [
            {
              "icon": "",
              "label": "Irgendwo im Konstruktor-Körper, solange es läuft, bevor das Objekt zurückgegeben wird"
            },
            {
              "icon": "",
              "label": "Nur als die allererste Anweisung des Konstruktors — this() (oder super()) muss die erste Zeile sein, und ein Konstruktor kann nicht both this() und super() aufrufen"
            },
            {
              "icon": "",
              "label": "Nur als die letzte Anweisung, nach all Feld-Initialisierung abgeschlossen ist"
            },
            {
              "icon": "",
              "label": "Nur in Konstruktoren markiert explizit als Delegating, mit einem Delegate-Wort"
            }
          ]
        },
        {
          "question": "Du brauchst eine Liste, die Elemente in der Front äußerst oft haben wird einzufügen, und Random-Access-Lesen sind selten. Welche ist das bessere strukturale Fit, und warum?",
          "options": [
            {
              "icon": "",
              "label": "ArrayList — sein zusammenhängendes Backing-Array macht jeden Betrieb, inkl. Front-Einfügung, schneller als eine verlinkte Struktur"
            },
            {
              "icon": "",
              "label": "Sie verfahren identisch, weil beide die List-Schnittstelle mit den gleichen Zeitgarantien implementieren"
            },
            {
              "icon": "",
              "label": "LinkedList — Einfügen in der Front ist O(1), weil es nur ein Knotenzweig ist, während ArrayList jedes existierende Element um ein nach oben verschieben muss, wodurch Front-Einfügung O(n) wird"
            },
            {
              "icon": "",
              "label": "LinkedList, weil es Random-Access in O(1) unterstützt, während ArrayList nicht tut"
            }
          ]
        },
        {
          "question": "Du fügst fünf Einträge in eine einfache HashMap in einer spezifischen Ordnung ein, dann iterierst über sie mit einer for-each Schleife. In welcher Ordnung kommen die Einträge aus?",
          "options": [
            {
              "icon": "",
              "label": "Die gleiche Ordnung, in welche die Einträge eingefügt wurden, da Java Maps immer Einfügungs-Ordnung bewahren"
            },
            {
              "icon": "",
              "label": "Keine garantierte Ordnung überhaupt — HashMap's Iterations-Ordnung hängt von Hash-Bucket-Platzierung ab, nicht Einfügungs-Ordnung, und kann auch zwischen Läufen ändern; LinkedHashMap ist was Einfügungs-Ordnung bewahrt"
            },
            {
              "icon": "",
              "label": "Sortiert nach Schlüssel automatisch, die gleiche Weise ein TreeMap sich verhält"
            },
            {
              "icon": "",
              "label": "Umgekehrte Einfügungs-Ordnung, weil HashMap intern einen Stack verwendet"
            }
          ]
        },
        {
          "question": "Wie viele Null-Schlüssel kann eine einfache java.util.HashMap auf einmal halten, und wie viele Null-Werte?",
          "options": [
            {
              "icon": "",
              "label": "Keine Null-Schlüssel und keine Null-Werte sind je in irgendeiner Map-Implementierung erlaubt"
            },
            {
              "icon": "",
              "label": "Ein Null-Schlüssel meistens, und jede Menge Null-Werte — HashMap erlaubt einen einzelnen Null-Schlüssel, anders als Hashtable die weder erlaubt"
            },
            {
              "icon": "",
              "label": "Unlimitiert Null-Schlüssel und unlimitiert Null-Werte, da null wie jeder andere Schlüssel behandelt wird"
            },
            {
              "icon": "",
              "label": "Ein Null-Schlüssel und ein Null-Wert-Maximum, beide begrenzt auf ein"
            }
          ]
        },
        {
          "question": "Du iterierst eine Liste mit einer for-each Schleife und rufst list.remove(item) direkt auf die Liste aus innerhalb des Schleife-Körpers auf, auf einige aber nicht alle Elemente. Was passiert?",
          "options": [
            {
              "icon": "",
              "label": "Es funktioniert korrekt und entfernt genau die beabsichtigten Elemente"
            },
            {
              "icon": "",
              "label": "Es überspringt still das Element nach dem entfernten, aber endet ansonsten ohne Fehler"
            },
            {
              "icon": "",
              "label": "Es wirft IndexOutOfBoundsException once die Schleife die alte Größe der Liste erreicht"
            },
            {
              "icon": "",
              "label": "Es wirft ConcurrentModificationException — das Modifizieren der Liste's Struktur direkt, während ein implizierter Iterator sie durchgeht, wird erkannt und abgelehnt; Iterator.remove() muss stattdessen verwendet werden"
            }
          ]
        },
        {
          "question": "Bei Runtime, gegeben eine List<String> list, was kann du tatsächlich über seinen Generic-Typ-Parameter via Reflection oder instanceof bestimmen?",
          "options": [
            {
              "icon": "",
              "label": "Du kannst list.getElementType() aufrufen, um String.class bei Runtime zu empfangen"
            },
            {
              "icon": "",
              "label": "instanceof List<String> kompiliert und prüft korrekt den Element-Typ"
            },
            {
              "icon": "",
              "label": "Die JVM speichert den Typ-Parameter als versteckte Metadaten zugänglich via list.getGenericType()"
            },
            {
              "icon": "",
              "label": "Nichts — Generische Typ-Info werden bei Compile-Zeit gelöscht, daher ist das Objekt zur Runtime einfach eine Liste, und es gibt keinen Weg, ob es List<String> oder List<Integer> war zu empfangen"
            }
          ]
        },
        {
          "question": "switch (day) { case 1: case 2: print(\"Weekday\"); break; case 6: print(\"Saturday\"); case 7: print(\"Sunday\"); break; default: print(\"?\"); } Wenn day ist 6, was druckt?",
          "options": [
            {
              "icon": "",
              "label": "Nur Saturday — jeden case-Block in einem switch stoppt immer nach seiner eigenen print-Anweisung"
            },
            {
              "icon": "",
              "label": "Nur Sunday — Matching startet bei case 6, aber nur die letzte übereinstimmende Marke vor break läuft"
            },
            {
              "icon": "",
              "label": "Nichts druckt, weil day 6 keinen case mit seinem eigenen break direkt darunter hat"
            },
            {
              "icon": "",
              "label": "Saturday dann Sunday — case 6 hat keinen break, daher fällt Ausführung durch in den nächsten case's Code bevor hit der folgende break"
            }
          ]
        },
        {
          "question": "Eine Product-Klasse braucht einen einzelnen, offensichtlichen \"natürlichen\" Sortier-Ordnung nach Preis, plus die Fähigkeit auch nach Name oder nach Lagerbestand an verschiedenen Orten im Codebase zu sortieren. Welche Kombination ist das richtige Design?",
          "options": [
            {
              "icon": "",
              "label": "Implementiere Comparable<Product> drei separate Male, je pro Ordnung, und lasse den Anrufer wählen welche compareTo läuft"
            },
            {
              "icon": "",
              "label": "Implementiere Comparable<Product> für die natürliche Preis-Ordnung, und schreibe separate Comparator<Product> Instanzen für die Name- und Lagerbestand-Ordnungen anderswo verwendet"
            },
            {
              "icon": "",
              "label": "Verwende nur Comparator für jede Ordnung, inkl. Preis, da Comparable keinen Vorteil hier hat"
            },
            {
              "icon": "",
              "label": "Verwende nur Comparable, indem überladene compareTo-Methoden hinzufügst, je pro Ordnung"
            }
          ]
        },
        {
          "question": "Eine Methode ruft new FileReader(path) auf, was throws IOException deklariert. IOException erweitert Exception, nicht RuntimeException. Was musst du tun, damit das kompiliert?",
          "options": [
            {
              "icon": "",
              "label": "Nichts — der Compiler erzwingt das nur für Exceptions, die RuntimeException erweitern"
            },
            {
              "icon": "",
              "label": "Entweder fange IOException in einem try/catch oder deklariere throws IOException auf deiner eigenen Methode — checked Exceptions müssen gehandhabt oder explizit verbreitet werden, anders als unchecked RuntimeException Subklassen"
            },
            {
              "icon": "",
              "label": "Wickle den Aufruf in einen try/catch für RuntimeException, da IOException automatisch unchecked in modernem Java ist"
            },
            {
              "icon": "",
              "label": "Deklariere die Methode als static — statische Methoden sind befreit von checked Exception-Handhabung"
            }
          ]
        },
        {
          "question": "try (Resource a = new Resource(\"A\"); Resource b = new Resource(\"B\")) { ... } verwendet zwei Ressourcen, die AutoCloseable implementieren. Wenn der try-Block normalerweise fertig ist, in welcher Ordnung werden sie geschlossen?",
          "options": [
            {
              "icon": "",
              "label": "A wird zuerst geschlossen, dann B, übereinstimmend mit Deklarations-Ordnung"
            },
            {
              "icon": "",
              "label": "B wird zuerst geschlossen, dann A — try-with-resources schließt Ressourcen in umgekehrter Ordnung in welcher sie deklariert wurden"
            },
            {
              "icon": "",
              "label": "Beide werden gleichzeitig geschlossen, da try-with-resources Cleanup parallelisiert"
            },
            {
              "icon": "",
              "label": "Nur B wird automatisch geschlossen — A muss noch in einem finally-Block manuell geschlossen werden"
            }
          ]
        },
        {
          "question": "int getValue() { try { return 1; } finally { return 2; } } Was gibt getValue() zurück?",
          "options": [
            {
              "icon": "",
              "label": "1 — die try-Block's Rückgabewert ist bereits bereit bevor finally läuft, daher kann finally es nicht ändern"
            },
            {
              "icon": "",
              "label": "Es wirft eine Exception bei Runtime, weil eine Methode nicht aus zwei Plätzen zurückgeben kann"
            },
            {
              "icon": "",
              "label": "2 — eine return-Anweisung innerhalb finally übersteigt und ersetzt jede return bereits in Fortschritt aus dem try-Block, wodurch Wert 1 völlig verworfen wird"
            },
            {
              "icon": "",
              "label": "Es ist ein Compilierungsfehler — finally darf nicht eine return-Anweisung enthalten"
            }
          ]
        },
        {
          "question": "try { ... } catch (IOException e) { ... } catch (FileNotFoundException e) { ... }, und FileNotFoundException erweitert IOException. Was passiert, wenn du versuchst das zu kompilieren?",
          "options": [
            {
              "icon": "",
              "label": "Ein Compilierungsfehler — der FileNotFoundException catch-Block ist unerreichbar, weil der frühere, allgemeinere IOException catch-Block bereits jeden FileNotFoundException passt"
            },
            {
              "icon": "",
              "label": "Es kompiliert gut, und der spezifischere FileNotFoundException-Block läuft, wann dieser genaue Typ geworfen wird"
            },
            {
              "icon": "",
              "label": "Es kompiliert gut, und beide catch-Blöcke laufen in Ordnung für einen FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Es's well bei Kompiler-Zeit, aber wirft einen Runtime-Fehler das erste Mal ein FileNotFoundException tatsächlich vorkommt"
            }
          ]
        },
        {
          "question": "Eine Klasse hat eine synchronized Instanzmethode process() und eine synchronized statische Methode configure(). Was, spezifisch, sperrt jede?",
          "options": [
            {
              "icon": "",
              "label": "process() sperrt den Monitor des spezifischen Objekt-Instanz wird aufgerufen, während configure() den Monitor der Class-Objekt sperrt — geteilt von jeder Instanz"
            },
            {
              "icon": "",
              "label": "Beide sperren die gleiche einzelne globale Lock für die ganze JVM, unabhängig von Instanz oder Klasse"
            },
            {
              "icon": "",
              "label": "process() sperrt die Class-Objekt, und configure() sperrt welchmal Instanz tritt es auf"
            },
            {
              "icon": "",
              "label": "Weder sperrt eigentlich etwas, es sei denn ein synchronized-Block wird auch innerhalb des Methoden-Körpers verwendet"
            }
          ]
        },
        {
          "question": "Ein Feld wird deklariert als volatile int counter = 0;, und mehrere Threads laufen counter++ darauf nebenläufig. Verhindert volatile verlorene Aktualisierungen hier?",
          "options": [
            {
              "icon": "",
              "label": "Ja — volatile macht jeden Betrieb auf dem Feld atomar, inkl. Increments"
            },
            {
              "icon": "",
              "label": "Nein — volatile garantiert nur Lesen die neueste Schreib über Threads sehen (Sicht); counter++ ist Lesen-ändern-schreiben mit mehreren Schritt, und volatile macht die Schritte nicht atomar"
            },
            {
              "icon": "",
              "label": "Ja, aber nur für int und long Felder spezifisch, wegen wie die JVM 64-Bit-Werte handhabt"
            },
            {
              "icon": "",
              "label": "Nein, und volatile auch fehlschlagen zu garantieren Sicht für primitive Typ wie int"
            }
          ]
        },
        {
          "question": "Interface A und Interface B deklarieren jeder eine default-Methode describe(). Eine Klasse implementiert both A und B und übersteigt beschreiben() nicht selbst. Was passiert?",
          "options": [
            {
              "icon": "",
              "label": "Der Compiler wählt Interface A's Version automatisch, da es zuerst in der implements-Klausel aufgelistet wird"
            },
            {
              "icon": "",
              "label": "Beide Versionen laufen, eins nach dem anderen, wann describe() aufgerufen wird"
            },
            {
              "icon": "",
              "label": "Es's well bei Kompiler-Zeit, aber wirft einen AmbiguousMethodException das erste Mal describe() tatsächlich aufgerufen wird"
            },
            {
              "icon": "",
              "label": "Ein Compilierungsfehler — wann zwei Interfaces die gleiche default-Methode beitragen, muss die implementierende Klasse es selbst übersteigen, um die Mehrdeutigkeit zu lösen, da Java nicht raten wird welche du meintest"
            }
          ]
        },
        {
          "question": "list.stream().filter(x -> x > 0).map(x -> x * 2); wird geschrieben, aber das Resultat wird nie einem Terminal-Operation wie .collect() oder .forEach() zugewiesen. Was geschieht tatsächlich, wenn diese Zeile läuft?",
          "options": [
            {
              "icon": "",
              "label": "Nichts geschieht zur Liste's Elemente überhaupt — filter und map sind lazy Intermediate-Operationen, die nur eine Pipeline-Beschreibung bauen; ohne eine Terminal-Operation läuft keine davon tatsächlich"
            },
            {
              "icon": "",
              "label": "Jedes Element wird gefiltert und mapped sofort, genau als ob eine Terminal-Operation aufgerufen würde"
            },
            {
              "icon": "",
              "label": "Nur der filter läuft sofort; map wird aufgeschoben bis eine Terminal-Operation erscheint"
            },
            {
              "icon": "",
              "label": "Es wirft eine IllegalStateException, weil eine Stream-Pipeline eine Terminal-Operation kompilieren erfordert"
            }
          ]
        }
      ],
      "optionOrderVersion": "0154f9576054ad6f"
    }
  }
}
