← Academy Blog

5 питань з Java-тестів минулих років: від цікавого до елементарного

Навчання в Binary Studio Academy складається з декількох етапів, які дозволяють поступово віднаходити серед хороших студентів найкращих. З кожним із етапів ви все сильніше відчуватимете підтримку менторів та отримуватимете все більше порад щодо того, як можна покращити ваші навички, та над чим варто більше попрацювати, щоб наблизитися до вашої цілі – стати інженером, здатним створювати додатки світового рівня.

Щороку до Binary Studio Academy подаються кілька тисяч аплікантів, кожен з яких претендує на можливість дійти до дня презентації командних проектів та випуску Академії. Проте більшість з них відсіюються ще на першому етапі тестування. Хтось - через недостатню підготовку а хтось - через те, що намагався схитрувати. Так-так, доброчесність при складанні тесту є важливою частиною рішення, чи проходить певний студент на наступний етап.

Тестування є найбільш відповідальним та хвилюючим етапом як для студентів, так і для менторів Академії. Ми беремо на себе серйозне зобов'язання - створення тесту, який дозволяє максимально оцінити рівень знань кожного з вас, не втративши жодного студента, що доклав достатньо зусиль під час своєї підготовки. Саме тому ми не стоїмо на місці і щороку намагаємося покращити етап тестування. Аналізуємо попередні та додаємо нові питання. Для нас важливо перевірити не лише знання основ технологій обраного напрямку, але й розуміння, яким чином можна застосовувати наявні знання на практиці.

Ми знаємо, яким важливим для вас є проходження тесту і як важко зрозуміти, чого очікувати від цього етапу, тож я відібрав 5 питань з минулорічного тесту, які пропоную розглянути разом.

Але для початку спробуйте пройти цей тест самостійно, без підказок, ось за цим посиланням:

Пройти тест

Що ж, переходимо до розбору:

  1. Що буде виведено у консоль?
class NumericCalc {
  public Number add(Number a, Number b) {
    return a.doubleValue() + b.doubleValue();
  }
}
class IntegerCalc extends NumericCalc {
  public Integer add(Integer a, Integer b) {
    return a + b;
  }
}
class Main {
  public static void main(String[] args) {
    var calc = new IntegerCalc();
    System.out.println(calc.add(2.3, 3.2));
  }
}

Варіанти відповідей: а) 5.5 b) 5 c) Compilation error will occur d) Exception will be thrown

Цікаве питання, що покликане перевірити розуміння наслідування класів.

Впевнений, що багато кому на перший погляд здалося, що в класі IntegerCalc було перевизначено метод add(), а отже, він буде викликаний. Проте придивившись уважніше, можна помітити відсутність анотації @Override, що вже натякає, що метод add() не був перевизначений. А це означає, що екземпляр класу IntegerCalc містить два методи add, що приймають різні за типом параметри.

В методі main() ми викликаємо метод add() екземпляру класу IntegerCalc, передаючи у якості параметрів два літерали з плаваючою крапкою, яким по дефолту присвоюється тип double. Тепер залишилося лише обрати метод, що підходить нам за заданими параметрами. Таким є метод add() батьківського класу NumericCalc. Він приймає на вхід 2 параметри типу Number. В той же час клас Double наслідується від абстрактного класу Number, а отже, ми можемо подати на вхід 2 літерали з плаваючою крапкою.

Додавши значення двох змінних типу Double, ми отримаємо результат типу Double, тож правильна відповідь - 5.5.

  1. Ви розробляєте додаток. Додаток викликає метод, який повертає список цілих чисел з назвою customerIds. Ви визначаєте змінну з назвою customerIdToRemove і присвоюєте їй значення. Ви оголошуєте масив з назвою filteredCustomerIds. Ви маєте такі вимоги. -> Видаліть дублікати цілих чисел із масиву customerIds. -> Сортувати масив у порядку від найвищого до найнижчого значення. -> Видаліть значення, збережене в змінній customerIdToRemove, з масиву customerIds. Вам потрібно створити запит, який відповідає всім вимогам. Який сегмент коду слід використовувати?

Варіанти відповідей: а)

List <String> filteredCustomerIds = customerIds.stream()
  .distinct()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());

b)

List <String> filteredCustomerIds = customerIds.stream()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());

c)

List <String> filteredCustomerIds = customerIds.stream()
  .distinct()
  .filter(x -> x != custumerIdToRemove)
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());

d)

List <String> filteredCustomerIds = customerIds.stream()
  .filter(x -> x != custumerIdToRemove)
  .sorted(Comparator.naturalOrder())
  .collect(Collectors.toList());

Питання стосується java stream API і є досить об’ємним, через що може здатися складним, проте давайте розберемося в ньому.

Для початку виділимо основні вимоги до результату, який повинен повертати код:

  • відсутність дублікатів;
  • видалення елементу значення якого співпадає з тим, що міститься в змінній customerIdToRemove;
  • елементи відсортовані в порядку спадання.

Почнемо з видалення дублікатів. Дублікати можна прибрати за допомогою метода distinct() - отже, ми зразу можемо відкинути варіанти d) та b), оскільки вони не містять виклику цього методу.

Видалення елементу. Цього можна досягти за допомогою метода filter(), відфільтрувавши всі значення, що співпадають зі значенням змінної customerIdToRemove. За цієї умови нам підходить лише варіант с).

Перевіримо, чи виконується умова сортування для варіанту с). Метод reverseOrder() інтерфейсу Comparator повертає компаратор, що порівнює об’єкти у порядку, зворотному до natural order, тобто за спаданням. Отже, варіант с) є правильною відповіддю.

  1. Що буде містити message?
import java.util.Queue;
import java.util.LinkedList;
public class Main {
  public static void main(String[] args) {
    CallMeLater call = (Queue <String> messages) -> messages.remove().toLowerCase();
    Queue <String> messages = new LinkedList <String>();
    messages.add("Message 1");
    messages.add("Message 2");
    messages.add("Message 3");
    messages.add("Message 4");
    messages.add("Message 5");
    String message = call.findAndModifyMessage(messages);
  }
}
interface CallMeLater {
  String findAndModifyMessage(Queue <String> messages);
}

Варіанти відповідей: а) Message 5 b) message 5 c) Message 1 d) message 1 e) Compilation error

Це досить цікаве питання покриває одразу декілька тем, а саме: принцип роботи Que та лямбда вирази в java.Почнемо з лямбда виразу. Він реалізує функціональний інтерфейс CallMeLater, який в свою чергу повинен мати лише один метод без реалізації. Ця умова виконується, тож перейдемо до тіла лямбда виразу. В ньому ми видаляємо верхній елемент, повертаючи його. Щоб зрозуміти, який саме елемент буде повернуто, потрібно згадати, що черга працює за принципом FIFO, а отже, виклик методу remove() поверне перший елемент, доданий до колекції. В нашому випадку це "Message 1". Далі над результатом методу remove() виконується метод toLowerCase(), що переводить всі символи рядка в нижній регістр. Відповідь message 1

  1. Яке значення матиме статична змінна BitsCounter.bits після виконання даного коду?
public class Main {
  public static void main(String[] args) {
    BitsCounter bcF = new BitsCounter();
    BitsCounter bcS = new BitsCounter();
    bcF.incrementByte();
    bcS.incrementByte();
  }
}
class BitsCounter {
  public static byte bits = 8;
  public void incrementByte() {
    bits *= 2;
  }
}

Варіанти відповідей: а) 8 b) 16 c) 32 d) Compilation error

Це досить просте і водночас влучне питання демонструє, чи розумієте ви різницю між статичними та не статичним полями класу.

Тож давайте розберемося. Статичне поле класу існує в одному екземплярі на весь клас, а отже, його копії не створюються для кожного екземпляру класу. Розуміння цієї особливості відразу відкриває нам ключ до правильної відповіді. Тож незважаючи на те, що ми викликаємо метод incrementByte() на різних екземплярах класу BitsCounter, всередині цього методу ми виконуємо операцію над однією і тією ж статичною змінною bits, а отже, 8 два рази множимо на 2. Правильна відповідь 32.

  1. В якому випадку виконується блок finally в зв'язці з try-catch-finally?

Варіанти відповідей: а) Лише у випадку, якщо в блоці catch виникла виключна ситуація. b) Лише у випадку, якщо в блоці try виникла виключна ситуація. c) В будь-якому випадку d) Лише у випадку, якщо відсутній блок catch і в блоці try виникла виключна ситуація.

Це питання було покликане перевірити розуміння принципу роботи конструкції try-catch-finally. Проте на практиці воно виявилося занадто легким для студентів, тож воно буде виключено з банку питань. Відповідь очевидна: блок finally викликається в будь-якому випадку, адже він використовується для очистки ресурсів блоку try. Попри очевидність питання, воно потрапило до цього списку через те, що все ж існує кейс, в якому блок finally не буде виконаний. А саме - у випадку, якщо JVM “помре”: в такому разі finally буде недосяжним і не буде виконаний, оскільки відбувся примусовий вихід з програми.

Підсумок

Сподіваюся, завдяки цій статті ви побачили певні теми, над якими варто трішки краще попрацювати, щоб більш впевнено почати тестування в день Х :) Не сумнівайтеся у своїх силах: приділивши підготовці достатньо часу і сил, ви з легкістю пройдете не лише цей етап, але й усі наступні етапи Binary Studio Academy. Перевірте корисні ресурси на порталі Академії, які ми рекомендуємо для підготовки, можливо, серед них ви знайдете саме ту тему, в якій ви поки “плаваєте”, візьміться за неї :) Нехай щастить на тесті!