{
  "assessmentTests": {
    "java_test": {
      "name": "Java Test",
      "desc": "30 сценарних запитань про основний синтаксис, механіку OOP, колекції, узагальнені типи, винятки та конкурентність — дізнайтеся, чи ваш Java відповідає тому, що роботодавець мітить під словом «Java proficiency».",
      "recommendation": "Ваш профіль навичок Java",
      "results": {
        "beginner": {
          "name": "Beginner",
          "desc": "Ви можете писати робочі класи та методи, але запитання, які ви пропустили, групуються навколо ідентичності та стандартних значень, а не синтаксису — String == vs .equals, чому new String(\"x\") ніколи не перевикористовує пул рядків, що становить значення за замовчуванням числового поля в порівнянні з полем обгортки. Це не про погану роботу з Java; це специфічні правила, які збивають з пантелику людей, які вивчили мову методом спроб і помилок, а не розуміючи, як посилання та примітиви насправді працюють під капотом. Це має значення на роботі, тому що кожне з них — це місце, де код компілюється, виконується, і тихо робить неправильну річ.",
          "recommendation": "Почніть з трьох речей, у цьому порядку: чому == для двох об'єктів String порівнює посилання, а не символи (а .equals — це майже завжди те, що вам потрібно), як цілочисленне переповнення мовчки обертається замість генерування винятку, і чому неініціалізоване поле Integer встановлюється на null, а поле int встановлюється на 0. Офіційні підручники Oracle з Java та Baeldung обидва охоплюють усі три з виконуваними прикладами."
        },
        "intermediate": {
          "name": "Intermediate",
          "desc": "Ви комфортно працюєте з кодом щоденного застосування — класами, колекціями, прямолінійним потоком керування — і не будете уповільнені рутинною роботою функцій. Розрив між тут і Advanced переважно в тому, що відбувається, коли правила Java взаємодіють з конкурентністю та узагальненими типами: статичний метод, вирішуваний оголошеним типом замість об'єкта, на який ви розраховували, порядок ітерації HashMap, на який ви мовчки покладалися, ConcurrentModificationException від видалення елемента під час циклу. Це те, що за багатьма помилками проходить швидка перевірка й появляється лише за конкретної умови виконання.",
          "recommendation": "Зосередьтеся на тому, як статичне уявлення компілятора про ваш код відрізняється від того, що виконується: вибір перевантаження та приховування статичного методу оголошеним типом замість типу виконання, чому HashMap не дає жодної гарантії порядку ітерації, і чому зміна списку під час ітерації генерує ConcurrentModificationException. Потім узагальненість, оскільки це збиває з пантелику людей, які вже знають колекції окремо."
        },
        "advanced": {
          "name": "Advanced",
          "desc": "Це рівень, який мають на увазі більшість оголошень про роботу під словом \"strong Java\". Ви читаєте клас зі статичними блоками, блоками екземпляра та декількома конструкторами й можете передбачити точний порядок їхнього виконання, ви знаєте, чому блок finally може мовчки відкинути значення return блоку try, і ви звертаєтесь до try-with-resources замість ручного закриття finally, тому що знаєте гарантію упорядкування, яку це надає. Те, що відокремлює цей діапазон від найвищого — це конкурентна та інтерфейсна сторона роботи: що саме заблокує synchronized, що дає та не гарантує volatile, і як вирішуються конфлікти методів за замовчуванням.",
          "recommendation": "Зверніть увагу на ті частини, які захищають код, якого також торкаються інші потоки та інші інтерфейси: різниця між тим, що блокує синхронізований метод екземпляра порівняно зі статичним методом, чому volatile дає видимість, але не атомарність для counter++, і як Java змушує вас вирішити діамантовий конфлікт методу за замовчуванням вручну. Матеріал Baeldung і Java Concurrency in Practice щодо Java Memory Model — природний наступний крок для обох."
        },
        "expert": {
          "name": "Expert",
          "desc": "Ви набрали максимум в кожній категорії — основний синтаксис та типи, механіка OOP та область видимості, колекції та узагальненість, винятки, конкурентність та ідіоми. Практично це означає, що вам можуть дати незнайомий клас і попросити пояснити, чому відбувається помилка компіляції, виняток у виконанні або мовчки неправильне значення, не просто те, що синтаксис говорить, що це повинно робити, що є складнішою й ціннішою навичкою. На цьому рівні сама мова рідко є обмежувальним фактором; обмеження зазвичай у дизайні конкурентності або формі даних під ним.",
          "recommendation": "Найбільша віддача тепер — у дизайні та діагностиці: читання дампу потоку перш ніж припускати, що зависання — це deadlock, вибір між synchronized, замками з java.util.concurrent та atomics як компроміс, а не як стандартне рішення, та дизайн stream-pipeline, який залишається лінивим навмисно, а не випадково. Якщо вас скринять на роль, опишіть конкурентну помилку — наприклад, пропущений edge happens-before або ConcurrentModificationException, яку ви знайшли у production, — замість того, щоб перелічувати функції Java: це демонструє міркування, а не лише словниковий запас."
        }
      },
      "questions": [
        {
          "question": "Ви створюєте два об'єкти String: String a = new String(\"cat\"); String b = new String(\"cat\"); Що оцінює a == b?",
          "options": [
            {
              "icon": "",
              "label": "true — рядкові літерали з однаковими символами завжди один і той же об'єкт"
            },
            {
              "icon": "",
              "label": "true — new String(...) перевикористовує пул рядків, тому однаковий текст завжди користується тим самим об'єктом"
            },
            {
              "icon": "",
              "label": "Помилка компіляції — == не можна використовувати для порівняння об'єктів String"
            },
            {
              "icon": "",
              "label": "false — new String(...) завжди виділяє новий об'єкт у heap, тому == порівнює два різних посилання, хоча .equals(b) повернув би true"
            }
          ]
        },
        {
          "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 завжди кешуються незалежно від значення"
            },
            {
              "icon": "",
              "label": "false потім false — == на об'єктах Integer завжди є порівнянням посилань, тому рівні значення ніколи не збігаються"
            },
            {
              "icon": "",
              "label": "true потім false, але тільки тому, що 200 переповнює byte — межа кешу не пов'язана з -128..127"
            },
            {
              "icon": "",
              "label": "true потім false — значення autoboxed Integer від -128 до 127 кешуються і є спільними, тому 100 перевикористовує той самий об'єкт, але 200 виходить за межі кешу й autoboxes у два окремих об'єкти"
            }
          ]
        },
        {
          "question": "int max = Integer.MAX_VALUE; System.out.println(max + 1); Що відбувається?",
          "options": [
            {
              "icon": "",
              "label": "Це генерує ArithmeticException для переповнення цілих чисел"
            },
            {
              "icon": "",
              "label": "Це друкує Integer.MAX_VALUE знову, тому що Java затримує за максимум типу"
            },
            {
              "icon": "",
              "label": "Це друкує Integer.MIN_VALUE — цілочисленна арифметика мовчки обертається при переповненні замість виконання"
            },
            {
              "icon": "",
              "label": "Це помилка компіляції — компілятор виявляє переповнення заздалегідь"
            }
          ]
        },
        {
          "question": "System.out.println(0.1 + 0.2 == 0.3); Що це друкує й чому?",
          "options": [
            {
              "icon": "",
              "label": "false — 0.1, 0.2 та 0.3 не можуть бути представлені точно у двійковій плаваючій точці, тому сума несе помилку округлення, яка робить її не бітово рівною 0.3"
            },
            {
              "icon": "",
              "label": "true — Java округлює арифметику double до найближчого представленого десяткового перед порівнянням"
            },
            {
              "icon": "",
              "label": "true — додавання двох doubles завжди точне для значень з двома десятковими цифрами"
            },
            {
              "icon": "",
              "label": "Це генерує виняток, тому що == не визначена для double у Java"
            }
          ]
        },
        {
          "question": "Клас оголошує два поля екземпляра без ініціалізатора: int count; та Integer total; Перш ніж конструктор запускається, які їхні значення за замовчуванням?",
          "options": [
            {
              "icon": "",
              "label": "Обидва встановлюються на 0, тому що Integer autoboxes до int при ініціалізації поля"
            },
            {
              "icon": "",
              "label": "count становить 0, а total становить 0, автоматично упаковується в обгортку Integer при першому зчитуванні"
            },
            {
              "icon": "",
              "label": "count становить 0, а total становить null — примітивні числові поля встановлюються на нуль, але неініціалізоване поле посилання на обгортку встановлюється на null як будь-яке інше посилання на об'єкт"
            },
            {
              "icon": "",
              "label": "Обидва встановлюються на null до явного присвоєння, оскільки Java не має неявних числових стандартів"
            }
          ]
        },
        {
          "question": "Цикл виконується 1000 разів, кожен раз робить result += \"x\"; на рядку result. Що насправді відбувається під капотом під час кожної ітерації?",
          "options": [
            {
              "icon": "",
              "label": "Внутрішній масив символів існуючого об'єкта String розширюється на місці"
            },
            {
              "icon": "",
              "label": "Java автоматично групує конкатенації й виділяє лише один кінцевий об'єкт String"
            },
            {
              "icon": "",
              "label": "Він компілюється в один спільний виклик StringBuilder.append() для всіх 1000 ітерацій без виділення додаткового об'єкта на кожну ітерацію"
            },
            {
              "icon": "",
              "label": "Новий об'єкт String створюється й result переназначається на нього — попередній об'єкт String викидається, тому що String незмінний і += не може змінити його на місці"
            }
          ]
        },
        {
          "question": "final List<String> names = new ArrayList<>(); Яке з цих правдиво?",
          "options": [
            {
              "icon": "",
              "label": "names.add(\"Ana\") компілюється й працює добре — final лише запобігає переназначенню посилання names себе, а не мутації об'єкту, на який він вказує"
            },
            {
              "icon": "",
              "label": "names.add(\"Ana\") є помилкою компіляції, тому що final робить сам список незмінним"
            },
            {
              "icon": "",
              "label": "Список потокобезпечний для одночасних записів, тому що його оголошено final"
            },
            {
              "icon": "",
              "label": "final на локальній змінній не має ефекту, якщо тип також не оголошено незмінним"
            }
          ]
        },
        {
          "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 — == порівнює посилання масивів (два різних об'єкти масивів), тоді як Arrays.equals порівнює елементи"
            },
            {
              "icon": "",
              "label": "true потім true — масиви з однаковим вмістом є одним об'єктом у Java"
            },
            {
              "icon": "",
              "label": "false потім false — Arrays.equals працює лише для масивів об'єктів, а не примітивних масивів int"
            },
            {
              "icon": "",
              "label": "true потім false — == на масивах порівнює вміст, і Arrays.equals є надлишковим"
            }
          ]
        },
        {
          "question": "void show(Object o) { print(\"Object\"); } void show(String s) { print(\"String\"); } Object ref = \"hello\"; show(ref); Яке перевантаження запускається й чому?",
          "options": [
            {
              "icon": "",
              "label": "show(String) запускається, тому що Java перевіряє фактичний клас виконання об'єкта перед вибором перевантаження"
            },
            {
              "icon": "",
              "label": "show(Object) запускається — вибір перевантаження визначається під час компіляції з використанням оголошеного типу змінної, а не фактичного типу виконання об'єкта, на який він посилається"
            },
            {
              "icon": "",
              "label": "Це помилка компіляції — Object не можна передавати, де існує конкретніше перевантаження"
            },
            {
              "icon": "",
              "label": "Обидва методи запускаються по одному разу, тому що Java розв'язує перевантаження, пробуючи кожен збіг"
            }
          ]
        },
        {
          "question": "Клас Base має static void greet() { print(\"Base\"); }. Клас Derived розширює Base й також оголошує static void greet() { print(\"Derived\"); }. Ви пишете: Base ref = new Derived(); ref.greet(); Що друкується?",
          "options": [
            {
              "icon": "",
              "label": "Derived — статичні методи перевизначаються так само, як методи екземпляра, дотримуючись фактичного типу виконання об'єкта"
            },
            {
              "icon": "",
              "label": "Це помилка компіляції — статичні методи не можна викликати через посилання на екземпляр"
            },
            {
              "icon": "",
              "label": "Base — статичні методи не поліморфні; виклик одного з них через посилання вирішується оголошеним типом посилання під час компіляції, а не фактичним типом об'єкта"
            },
            {
              "icon": "",
              "label": "Друкуються і Base, і Derived, тому що виклик нібито стосується одразу обох версій — і прихованої, і тієї, що приховує"
            }
          ]
        },
        {
          "question": "Всередині методу ви пишете int count = 0; потім намагаєтесь посилатися на count всередину lambda, переданої іншому методу. Що повинно бути правдивим про count, щоб це компілювалося?",
          "options": [
            {
              "icon": "",
              "label": "count повинна бути effectively final — ніколи не переназначена де-небудь після її початкового значення — тому що lambda захоплює моментальний знімок, а не живе посилання на змінну локального визначення"
            },
            {
              "icon": "",
              "label": "Нічого — будь-яка локальна змінна може вільно читатися й переназначуватися всередину lambda"
            },
            {
              "icon": "",
              "label": "count повинна бути оголошена volatile, щоб lambda завжди бачила її останнє значення"
            },
            {
              "icon": "",
              "label": "count повинна бути полем, а не локальною змінною — lambdas взагалі не можуть захоплювати локальні"
            }
          ]
        },
        {
          "question": "void rename(StringBuilder sb) { sb = new StringBuilder(\"new\"); } викликається як StringBuilder original = new StringBuilder(\"old\"); rename(original); Що становить original після виклику?",
          "options": [
            {
              "icon": "",
              "label": "Стає \"new\" — об'єкти передаються за посиланням у Java, тому переназначення параметра змінює змінну виклику занадто"
            },
            {
              "icon": "",
              "label": "Стає \"new\" лише для мутабельних типів, таких як StringBuilder, але не для незмінних типів"
            },
            {
              "icon": "",
              "label": "Залишається \"old\" — Java передає саме посилання за значенням, тому переназначення параметра всередину методу лише переорієнтує локальну копію посилання, залишаючи оригінал у виклику недоторканим"
            },
            {
              "icon": "",
              "label": "Генерує виняток виконання, тому що sb було переназначено всередині методу"
            }
          ]
        },
        {
          "question": "Клас має статичний блок ініціалізатора, блок ініціалізатора екземпляра та конструктор у такому порядку вихідного коду. Ви створюєте два об'єкти цього класу один за іншим. У якому порядку вони запускаються?",
          "options": [
            {
              "icon": "",
              "label": "Статичний блок запускається рівно один раз, перший раз, коли клас завантажується; потім для кожного об'єкту блок екземпляра запускається, а потім запускається тіло конструктора"
            },
            {
              "icon": "",
              "label": "Усі три запускаються свіжо у порядку вихідного коду для кожного об'єкту, створюваного"
            },
            {
              "icon": "",
              "label": "Конструктор запускається спочатку для кожного об'єкту, потім блок екземпляра, потім статичний блок один раз наприкінці"
            },
            {
              "icon": "",
              "label": "Статичний блок запускається один раз на об'єкт, прямо перед блоком екземпляра"
            }
          ]
        },
        {
          "question": "Клас має void log(String s) та void log(String... args). Ви викликаєте log(\"hi\"). Який запускається?",
          "options": [
            {
              "icon": "",
              "label": "log(String... args) — перевантаження з varargs завжди має перевагу, коли підходять обидва варіанти"
            },
            {
              "icon": "",
              "label": "Це помилка компіляції — виклик неоднозначний між двома перевантаженнями"
            },
            {
              "icon": "",
              "label": "log(String s) — коли перевантаження з точно фіксованою кількістю аргументів збігається, Java завжди віддає йому перевагу перед перевантаженням з varargs, яке використовується лише в останню чергу"
            },
            {
              "icon": "",
              "label": "Той, який оголошено першим у вихідному файлі, запускається"
            }
          ]
        },
        {
          "question": "Перший рядок конструктора викликає this(0); для делегування іншому конструктору в тому самому класі. Де допускається з'являтися this()?",
          "options": [
            {
              "icon": "",
              "label": "Будь-де в тілі конструктора, поки він запускається перш ніж об'єкт повертається"
            },
            {
              "icon": "",
              "label": "Лише як перший оцінюваний вислів конструктора — this() (або super()) повинна бути першим рядком, і конструктор не може викликати як this(), так і super()"
            },
            {
              "icon": "",
              "label": "Лише як останній вислів, після того як вся ініціалізація поля завершена"
            },
            {
              "icon": "",
              "label": "Лише в конструкторах, явно позначених як делегувальні, з використанням ключового слова delegate"
            }
          ]
        },
        {
          "question": "Вам потрібен список, у якому елементи вставляються в передню частину надзвичайно часто, і випадкові читання доступу рідкі. Який краще структурний fit, й чому?",
          "options": [
            {
              "icon": "",
              "label": "ArrayList — його суміжний резервний масив робить кожну операцію, включаючи вставлення на передній частині, швидшою, ніж пов'язана структура"
            },
            {
              "icon": "",
              "label": "Вони діють однаково, тому що обидва реалізують інтерфейс List з однаковими гарантіями часу"
            },
            {
              "icon": "",
              "label": "LinkedList — вставлення в передній частині дорівнює O(1), оскільки це лише перелінкування вузла, тоді як ArrayList повинна зсунути кожний існуючий елемент на один, роблячи вставлення на передній частині O(n)"
            },
            {
              "icon": "",
              "label": "LinkedList, тому що вона підтримує випадковий доступ у O(1), тоді як ArrayList не підтримує"
            }
          ]
        },
        {
          "question": "Ви вставляєте п'ять записів у звичайну HashMap у певному порядку, потім повторюєте її циклом for-each. У якому порядку записи вихідні?",
          "options": [
            {
              "icon": "",
              "label": "В одному порядку записи були вставлені в, оскільки Java карти завжди зберігають порядок вставлення"
            },
            {
              "icon": "",
              "label": "Жодного гарантійого порядку взагалі — порядок ітерації HashMap залежить від розміщення відра хешу, не порядку вставлення, й можуть навіть змінюватися між запусками; LinkedHashMap - це те, що зберігає порядок вставлення"
            },
            {
              "icon": "",
              "label": "Сортовано по ключу автоматично, як же TreeMap поводиться"
            },
            {
              "icon": "",
              "label": "Зворотна порядку вставлення, тому що HashMap внутрішньо використовує стек"
            }
          ]
        },
        {
          "question": "Скільки null ключів може звичайна java.util.HashMap утримувати одночасно й скільки null значень?",
          "options": [
            {
              "icon": "",
              "label": "Ніяких null ключів і ніяких null значень ніколи не дозволено у будь-якій реалізації Map"
            },
            {
              "icon": "",
              "label": "Один null ключ максимум й будь-яке число null значень — HashMap дозволяє єдиний null ключ, на відміну від Hashtable, який не дозволяє ні"
            },
            {
              "icon": "",
              "label": "Необмежені null ключі й необмежені null значення, оскільки null розглядається як будь-який інший ключ"
            },
            {
              "icon": "",
              "label": "Один null ключ й один null значення максимум, обидва обмежені одним"
            }
          ]
        },
        {
          "question": "Ви повторюєте цикл List з циклом for-each й викликаєте list.remove(item) безпосередньо на списку з тіла циклу, деяких але не всіх елементів. Що відбувається?",
          "options": [
            {
              "icon": "",
              "label": "Це працює правильно й видаляє рівно задумані елементи"
            },
            {
              "icon": "",
              "label": "Це мовчки пропускає елемент після того, як видалено, але інше завершується без помилки"
            },
            {
              "icon": "",
              "label": "Це генерує IndexOutOfBoundsException як тільки цикл досягає старого розміру списку"
            },
            {
              "icon": "",
              "label": "Це генерує ConcurrentModificationException — модифікація структури списку безпосередньо, поки неявний ітератор проходить по ньому, виявляється й відхиляється; замість цього треба використовувати Iterator.remove()"
            }
          ]
        },
        {
          "question": "Під час виконання, дана List<String> list, що ви насправді можете визначити про параметр узагальненого типу через рефлексію або instanceof?",
          "options": [
            {
              "icon": "",
              "label": "Ви можете викликати list.getElementType(), щоб отримати String.class під час виконання"
            },
            {
              "icon": "",
              "label": "instanceof List<String> компілюється й правильно перевіряє тип елемента"
            },
            {
              "icon": "",
              "label": "JVM зберігає параметр типу як приховані метадані доступні через list.getGenericType()"
            },
            {
              "icon": "",
              "label": "Нічого — інформація про узагальнений тип стирається під час компіляції, тому під час виконання об'єкт просто List, й немає способу відновити, оголошена вона List<String> або List<Integer>"
            }
          ]
        },
        {
          "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 — кожний блок case у switch завжди зупиняється після своєї власної оцінки print"
            },
            {
              "icon": "",
              "label": "Тільки Sunday — збіг починається з case 6, але тільки остання відповідна мітка перед break виконується"
            },
            {
              "icon": "",
              "label": "Нічого не друкується, тому що day 6 не має відповідного case з його власним break безпосередньо під ним"
            },
            {
              "icon": "",
              "label": "Saturday потім Sunday — case 6 не має break, так що виконання падає через код наступного case перед тим як потрапити на наступний break"
            }
          ]
        },
        {
          "question": "Клас Product потребує один, очевидний \"натуральний\" порядок сортування за ціною, плюс здатність також сортувати за назвою або за рівнем запасів у різних місцях у коду. Яка комбінація є правильним дизайном?",
          "options": [
            {
              "icon": "",
              "label": "Реалізуйте Comparable<Product> три окремих разів, один за упорядкуванням, й дайте коду виклику самому обирати, яке compareTo запуститься"
            },
            {
              "icon": "",
              "label": "Реалізуйте Comparable<Product> для натурального упорядкування ціни, й напишіть окремі екземпляри Comparator<Product> для упорядкування за назвою й рівнем запасів, використовуваних в інших місцях"
            },
            {
              "icon": "",
              "label": "Використовуйте лише Comparator для кожного упорядкування, включаючи ціну, оскільки Comparable не має переваги тут"
            },
            {
              "icon": "",
              "label": "Використовуйте лише Comparable додаванням трьох перевантажених методів compareTo, один за упорядкуванням"
            }
          ]
        },
        {
          "question": "Метод викликає new FileReader(path), яка оголошує throws IOException. IOException розширює Exception, а не RuntimeException. Що ви повинні робити, щоб це компілювалося?",
          "options": [
            {
              "icon": "",
              "label": "Нічого — компілятор лише виконує це для винятків, що розширюють RuntimeException"
            },
            {
              "icon": "",
              "label": "Або зловіть IOException у try/catch, або оголосіть throws IOException у власному методі — перевірені винятки повинні бути оброблені або поширені явно, на відміну від неперевірених підкласів RuntimeException"
            },
            {
              "icon": "",
              "label": "Загорніть виклик у try/catch для RuntimeException, оскільки IOException автоматично неперевірений у сучасній Java"
            },
            {
              "icon": "",
              "label": "Оголошуйте метод як static — статичні методи звільнені від обробки перевіреного винятку"
            }
          ]
        },
        {
          "question": "try (Resource a = new Resource(\"A\"); Resource b = new Resource(\"B\")) { ... } використовує два ресурси, що реалізують AutoCloseable. Якщо блок try закінчується нормально, у якому порядку вони закриваються?",
          "options": [
            {
              "icon": "",
              "label": "A закривається спочатку, потім B, збігаючись з порядком оголошення"
            },
            {
              "icon": "",
              "label": "B закривається спочатку, потім A — try-with-resources закриває ресурси в зворотному порядку оголошення"
            },
            {
              "icon": "",
              "label": "Обидва закриваються одночасно, оскільки try-with-resources паралелізує очистку"
            },
            {
              "icon": "",
              "label": "Лише B закривається автоматично — A все ще повинна закриватися вручну у блоці finally"
            }
          ]
        },
        {
          "question": "int getValue() { try { return 1; } finally { return 2; } } Що повертає виклик getValue()?",
          "options": [
            {
              "icon": "",
              "label": "1 — значення return блоку try вже затверджено перед запуском finally, тому finally не може змінити його"
            },
            {
              "icon": "",
              "label": "Це генерує виняток під час виконання, тому що метод не може повернутися з двох місць"
            },
            {
              "icon": "",
              "label": "2 — інструкція return всередині finally перекриває й замінює повернення, яке вже виконувалося з блоку try, повністю відкидаючи значення 1"
            },
            {
              "icon": "",
              "label": "Це помилка компіляції — finally не дозволяється містити інструкцію return"
            }
          ]
        },
        {
          "question": "try { ... } catch (IOException e) { ... } catch (FileNotFoundException e) { ... }, і FileNotFoundException розширює IOException. Що відбувається, коли ви намагаєтесь компілювати це?",
          "options": [
            {
              "icon": "",
              "label": "Помилка компіляції — блок catch FileNotFoundException недосяжний, оскільки раніший, більш загальний блок catch IOException вже відповідає кожній FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Він компілюється добре й більш специфічний блок FileNotFoundException запускається, коли цей точно тип викидається"
            },
            {
              "icon": "",
              "label": "Він компілюється добре й обидва блоки catch запускаються по порядку для FileNotFoundException"
            },
            {
              "icon": "",
              "label": "Це добре під час компіляції, але генерує помилку виконання перший раз FileNotFoundException насправді відбувається"
            }
          ]
        },
        {
          "question": "Клас має синхронізований метод екземпляра process() і синхронізований статичний метод configure(). Що конкретно блокує кожен з них?",
          "options": [
            {
              "icon": "",
              "label": "process() блокує монітор конкретного екземпляра об'єкту, на якому його викликають, тоді як configure() блокує монітор об'єкту Class самого — спільне всіма екземплярами"
            },
            {
              "icon": "",
              "label": "Обидва блокують один і той же єдиний глобальний замок для всієї JVM, незалежно від екземпляра або класу"
            },
            {
              "icon": "",
              "label": "process() блокує об'єкт Class, і configure() блокує якийсь екземпляр, що його викликає"
            },
            {
              "icon": "",
              "label": "Ні один насправді не блокує нічого, якщо синхронізований блок також не використовується всередину тіла методу"
            }
          ]
        },
        {
          "question": "Поле оголошується volatile int counter = 0;, і декілька потоків запускають counter++ на ньому одночасно. Чи запобігає volatile втраті оновлень тут?",
          "options": [
            {
              "icon": "",
              "label": "Так — volatile робить кожну операцію на полі атомною, включаючи інкременти"
            },
            {
              "icon": "",
              "label": "Ні — volatile лише гарантує, що читання бачать найновіший запис в усіх потоках (видимість); counter++ - це читання-модифікація-запис з декількома кроками, й volatile не робить ці кроки атомними"
            },
            {
              "icon": "",
              "label": "Так, але лише для полів int і long конкретно, через те як JVM обробляє 64-бітні значення"
            },
            {
              "icon": "",
              "label": "Ні, й volatile також не гарантує видимість для примітивних типів, як int"
            }
          ]
        },
        {
          "question": "Інтерфейс A й інтерфейс B кожен оголошує метод за замовчуванням describe(). Клас реалізує як A, так й B й не перевизначає describe() сам. Що відбувається?",
          "options": [
            {
              "icon": "",
              "label": "Компілятор автоматично вибирає версію інтерфейсу A, оскільки вона перелічена першою в пропозиції implements"
            },
            {
              "icon": "",
              "label": "Обидві версії запускаються одна за іншою, коли describe() викликається"
            },
            {
              "icon": "",
              "label": "Це добре під час компіляції, але генерує AmbiguousMethodException перший раз describe() викликається"
            },
            {
              "icon": "",
              "label": "Помилка компіляції — коли два інтерфейси вносять один і той самий метод за замовчуванням, клас, що їх реалізує, повинен перевизначити його сам, щоб вирішити неоднозначність, оскільки Java не буде вгадувати, який варіант ви мали на увазі"
            }
          ]
        },
        {
          "question": "list.stream().filter(x -> x > 0).map(x -> x * 2); написана, але результат ніколи не присвоюється до терміналу операції, як .collect() або .forEach(). Що насправді відбувається, коли цей рядок виконується?",
          "options": [
            {
              "icon": "",
              "label": "Нічого не відбувається елементам списку взагалі — filter та map є лінивими проміжними операціями, які лише будують опис трубопроводу; без операції терміналу ніякий з цього трубопроводу ніколи насправді не запускається"
            },
            {
              "icon": "",
              "label": "Кожний елемент фільтрується й відображується одразу, рівно як якби операція терміналу була названа"
            },
            {
              "icon": "",
              "label": "Лише filter запускається одразу; map відкладається доки операція терміналу з'являється"
            },
            {
              "icon": "",
              "label": "Це генерує IllegalStateException, тому що трубопровід stream потребує операції терміналу для компіляції"
            }
          ]
        }
      ],
      "optionOrderVersion": "0154f9576054ad6f"
    }
  }
}
