{
  "assessmentTests": {
    "java_test": {
      "name": "Java 테스트",
      "desc": "core syntax, OOP mechanics, collections, generics, exceptions, concurrency에 관한 30개의 시나리오 질문 — 직무 공고에서 Java 능력이 무엇을 의미하는지 파악하고 당신의 Java 실력을 평가하세요.",
      "recommendation": "당신의 Java 실력 프로필",
      "results": {
        "beginner": {
          "name": "초급",
          "desc": "작동하는 class와 method는 작성할 수 있지만, 놓친 문제들이 syntax보다는 identity와 기본값 문제에 집중되어 있습니다 — String == vs .equals, new String(\"x\")가 string pool을 재사용하지 않는 이유, numeric field는 기본값이 무엇인지 vs wrapper field는 무엇인지. 이것이 Java를 잘 못한다는 뜻은 아닙니다. 이것들은 시행착오를 통해 언어를 배운 사람들이 reference와 primitive가 실제로 어떻게 작동하는지 이해하지 못해서 놓치는 구체적인 규칙입니다. 일하면서 중요한 이유는 각각이 코드가 컴파일되고, 실행되지만 조용히 잘못된 일을 하는 경계선에 있기 때문입니다.",
          "recommendation": "이 세 가지부터 순서대로 시작하세요: 두 String 객체에서 ==가 왜 문자를 비교하지 않고 reference를 비교하는지 (.equals가 대부분 원하는 것), int overflow가 왜 조용히 말려 들어가는지 대신 throw하지 않는지, 그리고 초기화되지 않은 Integer field는 왜 null이 기본값이지만 int field는 0이 기본값인지. Oracle의 공식 Java 튜토리얼과 Baeldung 모두 runnable example과 함께 이 세 가지를 다룹니다."
        },
        "intermediate": {
          "name": "중급",
          "desc": "일상적인 애플리케이션 코드를 편하게 다룰 수 있으며 — class, collections, 직선적인 control flow — 일상적인 feature work에서 느려지지 않을 것입니다. 여기와 Advanced 사이의 격차는 대부분 Java의 규칙이 concurrency와 generics와 상호작용할 때 일어나는 일입니다: 예상한 객체 대신 declared type으로 resolve되는 static method, 조용히 의존하던 HashMap iteration order, list를 iterating하던 중 item을 제거해서 생기는 ConcurrentModificationException. 이런 종류의 버그는 빠른 읽기를 통과하고 특정 런타임 조건 아래에서만 나타납니다.",
          "recommendation": "compiler의 코드에 대한 정적 view가 실제로 실행되는 것과 어떻게 다른지에 집중하세요: declared type 대신 runtime type에 따른 overload resolution과 static-method hiding, HashMap이 iteration-order guarantee를 주지 않는 이유, 그리고 list를 직접 iterating하면서 수정하면 왜 ConcurrentModificationException이 throw되는지. 그 다음 generics erasure를 봐보세요, 왜냐하면 이것이 이미 collections를 개별적으로 알고 있는 사람들을 곤란하게 하기 때문입니다."
        },
        "advanced": {
          "name": "고급",
          "desc": "이것은 대부분의 직무 공고에서 \"strong Java\"를 의미하는 수준입니다. Static blocks, instance blocks, 여러 constructors가 있는 class를 읽고 정확히 어떤 순서로 실행되는지 예측할 수 있습니다. finally block이 try block의 return value를 조용히 버릴 수 있는 이유를 알고, manual finally-close 대신 try-with-resources를 reach for합니다. 왜냐하면 그것이 주는 ordering guarantee를 알기 때문입니다. 이 band와 top을 분리하는 것은 concurrent와 interface-level 작업의 측면입니다: synchronized가 실제로 무엇을 lock하는지, volatile이 무엇을 guarantee하고 무엇을 guarantee하지 않는지, 그리고 default-method conflicts가 어떻게 resolve되는지.",
          "recommendation": "다른 thread와 다른 interface도 touch하는 코드를 보호하는 부분을 밀어붙이세요: synchronized instance method가 lock하는 것 vs synchronized static method, volatile이 visibility를 주지만 counter++의 atomicity를 주지 않는 이유, 그리고 Java가 default-method diamond를 어떻게 직접 resolve하도록 강제하는지. Baeldung의 그리고 Java Concurrency in Practice의 Java Memory Model 자료가 둘 다에 대한 자연스러운 다음 단계입니다."
        },
        "expert": {
          "name": "전문가",
          "desc": "모든 section에서 상위 점수를 얻었습니다 — core syntax와 types, OOP mechanics와 scope, collections과 generics, 그리고 exceptions, concurrency와 idioms. 실제로, 그것은 당신이 낯선 사람의 class를 건네받고 compile error, runtime exception, 또는 조용히 잘못된 값이 syntax가 해야 할 일이 아니라 실제로 왜 일어나는지 설명할 수 있다는 의미입니다. 이것이 더 어렵고 더 가치 있는 기술입니다. 이 수준에서 language 자체가 제한 요인인 경우는 거의 없습니다. 제한은 보통 concurrency design 또는 그 아래 데이터의 형태입니다.",
          "recommendation": "이제 return은 design과 diagnosis에 있습니다: deadlock이라고 가정하기 전에 thread dump를 읽기, synchronized, java.util.concurrent locks, atomics 사이를 tradeoff로 선택하기, stream-pipeline design이 실수로가 아니라 의도적으로 lazy로 유지되도록 하기. 만약 당신이 역할에 대해 screening을 받고 있다면, missed happens-before edge 또는 production에서 찾은 ConcurrentModificationException 같은 concurrency bug를 설명하세요. Java features를 이름짓는 것이 아니라 — reasoning을 보여주는 것이지, 어휘만 아닌."
        }
      },
      "questions": [
        {
          "question": "두 개의 String 객체를 만듭니다: String a = new String(\"cat\"); String b = new String(\"cat\"); a == b는 무엇으로 evaluate될까요?",
          "options": [
            {
              "icon": "",
              "label": "true — 같은 문자를 가진 String literals은 항상 같은 객체입니다"
            },
            {
              "icon": "",
              "label": "true — new String(...)은 string pool을 재사용하므로, 동일한 텍스트는 항상 하나의 객체를 공유합니다"
            },
            {
              "icon": "",
              "label": "compile error — ==는 String 객체를 비교하는 데 사용될 수 없습니다"
            },
            {
              "icon": "",
              "label": "false — new String(...)은 항상 heap에 새로운 객체를 할당하므로, ==는 .equals(b)가 true를 반환하더라도 두 개의 다른 reference를 비교합니다"
            }
          ]
        },
        {
          "question": "Integer x = 100; Integer y = 100; System.out.println(x == y); 그리고 Integer x2 = 200; Integer y2 = 200; System.out.println(x2 == y2); 두 줄은 무엇을 출력할까요?",
          "options": [
            {
              "icon": "",
              "label": "true 그 다음 true — autoboxed Integer 객체는 값과 관계없이 항상 cached됩니다"
            },
            {
              "icon": "",
              "label": "false 그 다음 false — Integer 객체에서 ==은 항상 reference 비교이므로, 같은 값도 절대 match하지 않습니다"
            },
            {
              "icon": "",
              "label": "true 그 다음 false, 하지만 200이 byte를 overflow하기 때문이며 — cache boundary는 -128..127과 무관합니다"
            },
            {
              "icon": "",
              "label": "true 그 다음 false — autoboxed Integer 값 -128부터 127까지는 cached되고 공유되므로, 100은 하나의 객체를 재사용하지만, 200은 cache 밖에 있어서 두 개의 별도 객체로 autobox됩니다"
            }
          ]
        },
        {
          "question": "int max = Integer.MAX_VALUE; System.out.println(max + 1); 무엇이 일어날까요?",
          "options": [
            {
              "icon": "",
              "label": "ArithmeticException을 throw합니다 integer overflow의 경우"
            },
            {
              "icon": "",
              "label": "Integer.MAX_VALUE를 다시 출력합니다, 왜냐하면 Java가 type의 maximum에 고정되기 때문입니다"
            },
            {
              "icon": "",
              "label": "Integer.MIN_VALUE를 출력합니다 — int 산술은 throw하는 대신 overflow에서 조용히 wrap합니다"
            },
            {
              "icon": "",
              "label": "compile error — compiler가 미리 overflow를 감지합니다"
            }
          ]
        },
        {
          "question": "System.out.println(0.1 + 0.2 == 0.3); 이것은 무엇을 출력하며, 왜일까요?",
          "options": [
            {
              "icon": "",
              "label": "false — 0.1, 0.2, 0.3은 binary floating point에서 정확하게 표현될 수 없으므로, 합계는 rounding error를 가지고 있어서 bit-for-bit 같지 않습니다 0.3과"
            },
            {
              "icon": "",
              "label": "true — Java가 비교하기 전에 double 산술을 가장 가까운 표현 가능한 decimal로 반올림합니다"
            },
            {
              "icon": "",
              "label": "true — 두 자리 decimal을 가진 값들의 addition은 항상 정확합니다"
            },
            {
              "icon": "",
              "label": "exception을 throw합니다, 왜냐하면 ==이 double에 대해 정의되지 않았기 때문입니다 Java에서"
            }
          ]
        },
        {
          "question": "Class가 initializer가 없는 두 개의 instance fields를 선언합니다: int count; 그리고 Integer total; Constructor가 실행되기 전에, 그들의 기본값은 무엇일까요?",
          "options": [
            {
              "icon": "",
              "label": "둘 다 0이 기본값입니다, 왜냐하면 Integer가 field initialization에서 int로 autobox되기 때문입니다"
            },
            {
              "icon": "",
              "label": "count는 0이고 total은 0입니다, 읽힐 때마다 자동으로 box됩니다"
            },
            {
              "icon": "",
              "label": "count는 0이고 total은 null입니다 — primitive numeric fields는 0으로 기본값이지만, 초기화되지 않은 wrapper reference field는 다른 object reference처럼 null로 기본값입니다"
            },
            {
              "icon": "",
              "label": "둘 다 명시적으로 할당될 때까지 null로 기본값입니다, 왜냐하면 Java가 암묵적인 numeric defaults를 가지지 않았기 때문입니다"
            }
          ]
        },
        {
          "question": "Loop가 1000번 실행되며, 매번 result += \"x\";를 String result에서 수행합니다. 실제로 내부에서 무엇이 일어날까요 각 iteration에서?",
          "options": [
            {
              "icon": "",
              "label": "존재하는 String 객체의 내부 character array가 제자리에서 확장됩니다"
            },
            {
              "icon": "",
              "label": "Java가 자동으로 concatenations을 batch하고 최종 String 객체를 하나만 할당합니다"
            },
            {
              "icon": "",
              "label": "모든 1000 iterations에서 공유되는 single StringBuilder.append() call로 컴파일되며, iteration당 추가 객체가 할당되지 않습니다"
            },
            {
              "icon": "",
              "label": "brand new String 객체가 생성되고 result가 그것을 가리키도록 reassign됩니다 — 이전 String 객체가 버려집니다, 왜냐하면 String은 immutable이고 +=이 그것을 제자리에서 수정할 수 없기 때문입니다"
            }
          ]
        },
        {
          "question": "final List<String> names = new ArrayList<>(); 이 중 어느 것이 참일까요?",
          "options": [
            {
              "icon": "",
              "label": "names.add(\"Ana\")는 컴파일되고 잘 작동합니다 — final은 이름 reference 자체를 reassign하는 것만 방지하고, 그것이 가리키는 객체를 mutate하는 것은 방지하지 않습니다"
            },
            {
              "icon": "",
              "label": "names.add(\"Ana\")는 compile error입니다, 왜냐하면 final이 list 자체를 unmodifiable하게 만들기 때문입니다"
            },
            {
              "icon": "",
              "label": "list는 concurrent writes에 대해 thread-safe입니다 왜냐하면 final로 선언되었기 때문입니다"
            },
            {
              "icon": "",
              "label": "final on a local variable은 type도 immutable로 선언되지 않으면 효과가 없습니다"
            }
          ]
        },
        {
          "question": "int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; System.out.println(a == b); System.out.println(Arrays.equals(a, b)); 두 줄은 무엇을 출력할까요?",
          "options": [
            {
              "icon": "",
              "label": "false 그 다음 true — ==은 array references를 비교하며 (두 개의 다른 array 객체), Arrays.equals는 elements를 비교합니다"
            },
            {
              "icon": "",
              "label": "true 그 다음 true — 동일한 contents를 가진 arrays는 Java에서 같은 객체입니다"
            },
            {
              "icon": "",
              "label": "false 그 다음 false — Arrays.equals는 primitive int arrays가 아니라 object arrays에만 작동합니다"
            },
            {
              "icon": "",
              "label": "true 그 다음 false — arrays에서 ==은 contents를 비교하고, Arrays.equals는 중복됩니다"
            }
          ]
        },
        {
          "question": "void show(Object o) { print(\"Object\"); } void show(String s) { print(\"String\"); } Object ref = \"hello\"; show(ref); 어느 overload가 실행되며, 왜일까요?",
          "options": [
            {
              "icon": "",
              "label": "show(String)이 실행됩니다, 왜냐하면 Java가 overload를 선택하기 전에 객체의 실제 runtime class를 조사하기 때문입니다"
            },
            {
              "icon": "",
              "label": "show(Object)가 실행됩니다 — overload resolution은 실제 runtime type이 아니라 variable의 declared type을 사용하여 compile time에 결정됩니다"
            },
            {
              "icon": "",
              "label": "compile error — Object는 더 구체적인 overload가 존재하는 곳에 전달될 수 없습니다"
            },
            {
              "icon": "",
              "label": "두 methods 모두 실행되며, 각각 한 번씩, 왜냐하면 Java가 every match를 시도해서 overloads를 resolve하기 때문입니다"
            }
          ]
        },
        {
          "question": "Class Base가 static void greet() { print(\"Base\"); }를 가집니다. Class Derived가 Base를 extend하고 또한 static void greet() { print(\"Derived\"); }를 선언합니다. 다음을 작성합니다: Base ref = new Derived(); ref.greet(); 무엇이 출력될까요?",
          "options": [
            {
              "icon": "",
              "label": "Derived — static methods는 instance methods처럼 override되며, 실제 객체의 runtime type을 따릅니다"
            },
            {
              "icon": "",
              "label": "compile error — static methods를 instance reference를 통해 호출할 수 없습니다"
            },
            {
              "icon": "",
              "label": "Base — static methods는 polymorphic하지 않습니다; reference를 통해 하나를 호출하는 것은 객체의 실제 type이 아니라 reference의 declared type에 의해 compile time에 resolve됩니다"
            },
            {
              "icon": "",
              "label": "Base와 Derived 모두 출력됩니다, 왜냐하면 호출이 hidden과 hiding 버전 모두로 resolve되기 때문입니다"
            }
          ]
        },
        {
          "question": "Method 내에서, 다음을 작성합니다 int count = 0; 그 다음 lambda에서 count를 reference하려고 다른 method에 passed합니다. 이것이 컴파일되려면 count에 대해 무엇이 참이어야 할까요?",
          "options": [
            {
              "icon": "",
              "label": "count는 effectively final이어야 합니다 — 초기값 이후 어디서도 reassign되지 않으므로 — lambda가 live reference이 아니라 snapshot을 capture하기 때문입니다 mutable local variable의"
            },
            {
              "icon": "",
              "label": "아무것도 없습니다 — 어떤 local variable도 lambda 내에서 자유롭게 읽고 reassign될 수 있습니다"
            },
            {
              "icon": "",
              "label": "count는 volatile로 선언되어야 해서 lambda가 항상 가장 최신의 value를 봅니다"
            },
            {
              "icon": "",
              "label": "count는 field여야 합니다, local variable이 아니므로 — lambdas는 locals를 capture할 수 없습니다"
            }
          ]
        },
        {
          "question": "void rename(StringBuilder sb) { sb = new StringBuilder(\"new\"); }가 호출됩니다 StringBuilder original = new StringBuilder(\"old\"); rename(original); 호출 후 original은 무엇일까요?",
          "options": [
            {
              "icon": "",
              "label": "\"new\"가 됩니다 — objects는 Java에서 reference로 전달되므로, parameter를 reassign하는 것이 caller's variable도 변경합니다"
            },
            {
              "icon": "",
              "label": "\"new\"가 됩니다 오직 mutable types 같은 StringBuilder에 대해서만, 하지만 immutable types에 대해서는 아닙니다"
            },
            {
              "icon": "",
              "label": "여전히 \"old\"입니다 — Java가 reference 자체를 value로 전달하므로, method 내에서 parameter를 reassign하는 것은 오직 local copy of the reference를 repoint하며, caller's original을 그대로 남깁니다"
            },
            {
              "icon": "",
              "label": "runtime exception을 throw합니다, 왜냐하면 sb가 method 내에서 reassign되었기 때문입니다"
            }
          ]
        },
        {
          "question": "A class가 static initializer block, instance initializer block, constructor를 source 순서대로 가집니다. 이 class의 객체 두 개를 하나 다음 하나로 생성합니다. 이들이 어떤 순서로 실행될까요?",
          "options": [
            {
              "icon": "",
              "label": "static block은 정확히 한 번 실행되며, class가 처음 load될 때; 그 다음 각 객체에 대해, instance block이 실행되고 그 다음 constructor body가 실행됩니다"
            },
            {
              "icon": "",
              "label": "세 가지 모두 fresh하게 source 순서대로 매 객체마다 실행됩니다"
            },
            {
              "icon": "",
              "label": "constructor가 각 객체에 대해 먼저 실행되며, 그 다음 instance block, 그 다음 static block을 가장 끝에 한 번 실행합니다"
            },
            {
              "icon": "",
              "label": "static block은 객체당 한 번 실행되며, instance block 바로 전에"
            }
          ]
        },
        {
          "question": "A class가 void log(String s)와 void log(String... args)를 가집니다. log(\"hi\")를 호출합니다. 어느 것이 실행될까요?",
          "options": [
            {
              "icon": "",
              "label": "log(String... args) — varargs overloads는 둘 다 applicable할 때 항상 선호됩니다"
            },
            {
              "icon": "",
              "label": "compile error — 호출이 두 overloads 사이에서 ambiguous합니다"
            },
            {
              "icon": "",
              "label": "log(String s) — fixed-arity overload가 정확하게 match할 때, Java는 항상 varargs overload를 선호합니다, last resort로만 사용됩니다"
            },
            {
              "icon": "",
              "label": "source file에서 먼저 선언된 것이 실행됩니다"
            }
          ]
        },
        {
          "question": "Constructor's 첫 줄이 this(0);를 호출해서 같은 class의 다른 constructor에 delegate합니다. 이것이 어디에 나타날 수 있을까요?",
          "options": [
            {
              "icon": "",
              "label": "constructor body의 어디든지, 객체가 반환되기 전에 실행되는 한"
            },
            {
              "icon": "",
              "label": "오직 constructor의 very first statement로만 — this() (또는 super())는 첫 줄이어야 하며, constructor는 this()와 super() 모두를 호출할 수 없습니다"
            },
            {
              "icon": "",
              "label": "오직 last statement로만, 모든 field initialization이 완료된 후"
            },
            {
              "icon": "",
              "label": "오직 delegate keyword를 사용해서 명시적으로 delegating으로 marked된 constructors에서만"
            }
          ]
        },
        {
          "question": "당신이 front에 극도로 자주 elements가 insert되는 list가 필요하며, random-access reads는 드뭅니다. 더 나은 structural fit은 무엇이며, 왜일까요?",
          "options": [
            {
              "icon": "",
              "label": "ArrayList — 그 contiguous backing array는 front-insertion을 포함한 모든 operation을 linked structure보다 빠르게 만듭니다"
            },
            {
              "icon": "",
              "label": "그들은 동일하게 perform합니다, 왜냐하면 둘 다 같은 time guarantees를 가진 List interface를 implement하기 때문입니다"
            },
            {
              "icon": "",
              "label": "LinkedList — front에 inserting은 O(1)입니다 왜냐하면 그냥 node를 relink하기 때문이며, ArrayList는 모든 existing element를 shift up해야 하므로 front-insertion은 O(n)입니다"
            },
            {
              "icon": "",
              "label": "LinkedList, 왜냐하면 그것이 random access를 O(1)에서 support하기 때문이며 ArrayList는 하지 않습니다"
            }
          ]
        },
        {
          "question": "다섯 개의 entries를 plain HashMap에 특정 순서로 insert하고, 그 다음 for-each loop로 그것을 iterate합니다. Entries가 어떤 순서로 나올까요?",
          "options": [
            {
              "icon": "",
              "label": "같은 순서로 entries가 insert되었던, Java maps가 항상 insertion order를 preserve하기 때문입니다"
            },
            {
              "icon": "",
              "label": "guaranteed order가 없습니다 — HashMap의 iteration order는 hash bucket placement에 의존하며, insertion order가 아니고, runs 사이에서도 change할 수 있습니다; LinkedHashMap이 insertion order를 preserve하는 것입니다"
            },
            {
              "icon": "",
              "label": "key에 의해 자동으로 sorted되며, TreeMap가 behave하는 같은 방식으로"
            },
            {
              "icon": "",
              "label": "insertion order의 reverse, HashMap이 internally stack을 사용하기 때문입니다"
            }
          ]
        },
        {
          "question": "plain java.util.HashMap이 한 번에 몇 개의 null keys를 hold할 수 있으며, 몇 개의 null values를 hold할 수 있을까요?",
          "options": [
            {
              "icon": "",
              "label": "어떤 Map implementation도 null keys를 허용하지 않으며 null values도 허용하지 않습니다"
            },
            {
              "icon": "",
              "label": "최대 하나의 null key, 그리고 어떤 개수의 null values — HashMap은 single null key를 허용하며, Hashtable과 달리 어느 것도 허용하지 않습니다"
            },
            {
              "icon": "",
              "label": "unlimited null keys와 unlimited null values, null이 다른 key처럼 treated되기 때문입니다"
            },
            {
              "icon": "",
              "label": "최대 하나의 null key와 최대 하나의 null value, 모두 하나로 capped입니다"
            }
          ]
        },
        {
          "question": "List를 for-each loop로 iterate하며 loop body 내에서 list.remove(item)을 직접 list에서 호출합니다, 일부이지만 모든 element가 아닙니다. 무엇이 일어날까요?",
          "options": [
            {
              "icon": "",
              "label": "그것이 정확하게 작동하고 정확히 intended elements를 제거합니다"
            },
            {
              "icon": "",
              "label": "제거된 element 다음의 element를 조용히 skip하지만, 그 외에는 error 없이 완료됩니다"
            },
            {
              "icon": "",
              "label": "loop이 list의 old size에 도달하면 IndexOutOfBoundsException을 throw합니다"
            },
            {
              "icon": "",
              "label": "ConcurrentModificationException을 throw합니다 — implicit iterator가 그것을 walk하는 동안 list의 structure를 modifying하는 것이 detected되고 rejected되며; Iterator.remove()를 대신 사용해야 합니다"
            }
          ]
        },
        {
          "question": "Runtime에서, given List<String> list, reflection이나 instanceof를 통해 그 generic type parameter에 대해 실제로 무엇을 결정할 수 있을까요?",
          "options": [
            {
              "icon": "",
              "label": "list.getElementType()을 호출해서 runtime에 String.class를 retrieve할 수 있습니다"
            },
            {
              "icon": "",
              "label": "instanceof List<String>이 compile되고 correctly element type을 check합니다"
            },
            {
              "icon": "",
              "label": "JVM이 type parameter를 hidden metadata로 store하며 list.getGenericType()을 통해 accessible합니다"
            },
            {
              "icon": "",
              "label": "아무것도 없습니다 — generic type information은 compile time에 erased되므로, runtime에 객체는 그냥 List이며, List<String> 또는 List<Integer>로 선언되었는지를 recover할 방법이 없습니다"
            }
          ]
        },
        {
          "question": "switch (day) { case 1: case 2: print(\"Weekday\"); break; case 6: print(\"Saturday\"); case 7: print(\"Sunday\"); break; default: print(\"?\"); } day가 6이면, 무엇이 출력될까요?",
          "options": [
            {
              "icon": "",
              "label": "오직 Saturday — switch의 각 case block은 항상 자체 print statement 후에 stop합니다"
            },
            {
              "icon": "",
              "label": "오직 Sunday — matching은 case 6에서 시작하지만 break 전의 마지막 matching label만 실행됩니다"
            },
            {
              "icon": "",
              "label": "아무것도 출력되지 않습니다, day 6이 자체 break를 직접 아래에 가진 matching case를 가지지 않기 때문입니다"
            },
            {
              "icon": "",
              "label": "Saturday 그 다음 Sunday — case 6이 break를 가지지 않으므로, execution이 다음 case's code로 fall through한 뒤 following break를 hit합니다"
            }
          ]
        },
        {
          "question": "Product class가 price에 의한 single, obvious \"natural\" sort order가 필요하며, name이나 stock level에 의해서도 다른 장소에서 sort할 수 있어야 합니다. 올바른 design combination은 무엇일까요?",
          "options": [
            {
              "icon": "",
              "label": "Comparable<Product>를 세 번 implement하며, ordering당 한 번씩, caller가 어느 compareTo가 실행될지 선택하게 합니다"
            },
            {
              "icon": "",
              "label": "Comparable<Product>를 natural price ordering에 대해 implement하고, name과 stock-level orderings에 대해 따로 Comparator<Product> instances를 작성합니다"
            },
            {
              "icon": "",
              "label": "price를 포함한 모든 ordering에 대해 오직 Comparator를 사용합니다, Comparable이 여기 advantage를 가지지 않기 때문입니다"
            },
            {
              "icon": "",
              "label": "오직 Comparable을 사용하며 overloaded compareTo methods 세 개를 추가하며, ordering당 하나씩"
            }
          ]
        },
        {
          "question": "Method가 new FileReader(path)를 호출하며, IOException을 throws하며 선언됩니다. IOException은 RuntimeException이 아니라 Exception을 extend합니다. 이것을 컴파일되도록 하려면 무엇을 해야 할까요?",
          "options": [
            {
              "icon": "",
              "label": "아무것도 없습니다 — compiler는 RuntimeException을 extend하는 exceptions에 대해서만 이것을 enforce합니다"
            },
            {
              "icon": "",
              "label": "try/catch에서 IOException을 catch하거나 자신의 method에서 throws IOException을 선언합니다 — checked exceptions은 명시적으로 handled되거나 propagated되어야 하며, unchecked RuntimeException subclasses와 달리"
            },
            {
              "icon": "",
              "label": "RuntimeException에 대해 try/catch로 호출을 wrap합니다, IOException이 modern Java에서 자동으로 unchecked되기 때문입니다"
            },
            {
              "icon": "",
              "label": "method를 static으로 선언합니다 — static methods는 checked exception handling에서 exempt됩니다"
            }
          ]
        },
        {
          "question": "try (Resource a = new Resource(\"A\"); Resource b = new Resource(\"B\")) { ... } AutoCloseable을 implement하는 두 개의 resources를 사용합니다. Try block이 normally finish되면, 어떤 순서로 그들이 close될까요?",
          "options": [
            {
              "icon": "",
              "label": "A가 먼저 close되고, 그 다음 B, declaration order를 matching합니다"
            },
            {
              "icon": "",
              "label": "B가 먼저 close되고, 그 다음 A — try-with-resources가 resources를 declare된 순서의 reverse로 close합니다"
            },
            {
              "icon": "",
              "label": "둘 다 simultaneously close됩니다, try-with-resources가 cleanup을 parallelize하기 때문입니다"
            },
            {
              "icon": "",
              "label": "오직 B만 자동으로 close됩니다 — A는 여전히 finally block에서 manually close되어야 합니다"
            }
          ]
        },
        {
          "question": "int getValue() { try { return 1; } finally { return 2; } } getValue()를 호출하면 무엇을 반환할까요?",
          "options": [
            {
              "icon": "",
              "label": "1 — try block의 return value는 finally가 실행되기 전에 이미 committed되므로, finally가 그것을 change할 수 없습니다"
            },
            {
              "icon": "",
              "label": "runtime에 exception을 throw합니다, method가 두 곳에서 return될 수 없기 때문입니다"
            },
            {
              "icon": "",
              "label": "2 — finally 내부의 return statement가 try block에서 이미 in progress인 어떤 return이든지 override하고 replace하며, value 1을 버립니다"
            },
            {
              "icon": "",
              "label": "compile error — finally는 return statement를 contain하는 것이 allowed되지 않습니다"
            }
          ]
        },
        {
          "question": "try { ... } catch (IOException e) { ... } catch (FileNotFoundException e) { ... }, 그리고 FileNotFoundException이 IOException을 extend합니다. 이것을 컴파일하려고 할 때 무엇이 일어날까요?",
          "options": [
            {
              "icon": "",
              "label": "compile error — FileNotFoundException catch block은 unreachable입니다 왜냐하면 earlier, more general IOException catch block이 이미 모든 FileNotFoundException을 match하기 때문입니다"
            },
            {
              "icon": "",
              "label": "fine하게 compile되고, more specific FileNotFoundException block이 run할 때 정확히 그 type이 thrown됩니다"
            },
            {
              "icon": "",
              "label": "fine하게 compile되고, 둘 다 catch blocks이 FileNotFoundException에 대해 order로 실행합니다"
            },
            {
              "icon": "",
              "label": "compile time에는 fine하지만 FileNotFoundException이 actually occur할 때마다 runtime error를 throw합니다"
            }
          ]
        },
        {
          "question": "A class가 synchronized instance method process()와 synchronized static method configure()를 가집니다. 구체적으로, 각각이 무엇을 lock할까요?",
          "options": [
            {
              "icon": "",
              "label": "process()는 그것이 호출되는 특정 object instance의 monitor를 lock하며, configure()는 Class object 자체의 monitor를 lock합니다 — 모든 instance를 share하는"
            },
            {
              "icon": "",
              "label": "둘 다 entire JVM의 single global lock을 lock하며, instance나 class와 관계없이"
            },
            {
              "icon": "",
              "label": "process()는 Class object를 lock하고, configure()는 그것을 호출하는 어떤 instance든지 lock합니다"
            },
            {
              "icon": "",
              "label": "어느 것도 실제로 아무것도 lock하지 않습니다 synchronized block도 method body 내에서 사용되지 않으면"
            }
          ]
        },
        {
          "question": "Field가 volatile int counter = 0;로 declared되며, 여러 threads가 counter++을 concurrently 실행합니다. Volatile이 lost updates를 prevent할까요?",
          "options": [
            {
              "icon": "",
              "label": "네 — volatile이 field의 모든 operation을 atomic하게 만들며, increments 포함"
            },
            {
              "icon": "",
              "label": "아니요 — volatile이 오직 threads를 통해 latest write를 guarantee합니다 (visibility); counter++가 여러 steps를 가진 read-modify-write이며, volatile이 그 steps를 atomic하게 만드는 것을 아무것도 하지 않습니다"
            },
            {
              "icon": "",
              "label": "네, 하지만 오직 int와 long fields에 대해서만, JVM이 64-bit values를 어떻게 handle하는지 때문입니다"
            },
            {
              "icon": "",
              "label": "아니요, 그리고 volatile도 int 같은 primitive types에 대해 visibility를 guarantee하는 데 fail합니다"
            }
          ]
        },
        {
          "question": "Interface A와 interface B가 각각 default method describe()를 declare합니다. A class가 둘 다 implement하고 자체로 describe()를 override하지 않습니다. 무엇이 일어날까요?",
          "options": [
            {
              "icon": "",
              "label": "compiler가 자동으로 interface A's version을 pick합니다, 그것이 implements clause에서 먼저 listed되기 때문입니다"
            },
            {
              "icon": "",
              "label": "두 versions 모두 run하며, describe()가 호출될 때마다 하나 다음 하나씩"
            },
            {
              "icon": "",
              "label": "compile time에는 fine하지만, describe()가 처음 호출될 때 AmbiguousMethodException을 throw합니다"
            },
            {
              "icon": "",
              "label": "compile error — 두 interfaces가 같은 default method에 contribute할 때, implementing class는 자체로 그것을 override해야 해서 ambiguity를 resolve합니다, Java가 guess하지 않기 때문입니다 당신이 의도한 것을"
            }
          ]
        },
        {
          "question": "list.stream().filter(x -> x > 0).map(x -> x * 2); 작성되지만 result가 .collect()나 .forEach() 같은 terminal operation에 할당되지 않습니다. 이 줄이 실행할 때 실제로 무엇이 일어날까요?",
          "options": [
            {
              "icon": "",
              "label": "list's elements에 아무것도 일어나지 않습니다 — filter와 map은 lazy intermediate operations이며 pipeline description을 build하기만 합니다; terminal operation 없이, 그 pipeline의 아무것도 실제로 실행되지 않습니다"
            },
            {
              "icon": "",
              "label": "모든 element가 filtered되고 mapped되며, 정확히 terminal operation이 호출된 것처럼"
            },
            {
              "icon": "",
              "label": "오직 filter만 immediately 실행됩니다; map은 terminal operation이 나타날 때까지 deferred됩니다"
            },
            {
              "icon": "",
              "label": "IllegalStateException을 throw합니다, stream pipeline이 compile하기 위해 terminal operation을 require하기 때문입니다"
            }
          ]
        }
      ],
      "optionOrderVersion": "0154f9576054ad6f"
    }
  }
}
