← Academy Blog

5 вопросов из Java- тестов прошлых годов: от интересного к элементарному

Translated into:

Обучение в Binary Studio Academy состоит из нескольких этапов, которые позволяют постепенно отыскивать среди хороших студентов лучших. С каждым из этапов вы все сильнее будете чувствовать поддержку менторов и получать все больше советов касательно того, как можно улучшить ваши навыки, и над чем стоит работать больше, чтобы приблизиться к вашей цели - стать инженером, способным создавать world-class приложения.

Каждый год в 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.

Проверьте полезные ресурсы на портале Академии, которые мы рекомендуем для подготовки, возможно, среди них вы найдете такую тему, в которой вы пока “плаваете”, и возьмитесь за нее. :)

Удачи на тесте!