Перейти к основному содержимому

Вопросы на собеседовании по Java Flow Control (+ ответы)

· 8 мин. чтения

1. Введение

Операторы потока управления позволяют разработчикам использовать принятие решений, циклы и ветвления для условного изменения потока выполнения определенных блоков кода.

В этой статье мы рассмотрим некоторые вопросы интервью по управлению потоком, которые могут возникнуть во время интервью и, где это уместно; мы реализуем примеры, чтобы лучше понять их ответы.

2. Вопросы

Q1. Опишите операторы if-then и if-then-else . Какие типы выражений можно использовать в качестве условий?

Оба утверждения говорят нашей программе выполнять код внутри них только в том случае, если определенное условие оценивается как истинное . Однако оператор if-then-else предоставляет вторичный путь выполнения на случай, если условие if оценивается как false :

if (age >= 21) {
// ...
} else {
// ...
}

В отличие от других языков программирования, Java поддерживает только логические выражения в качестве условий. Если мы попытаемся использовать другой тип выражения, мы получим ошибку компиляции.

Q2. Опишите оператор switch . Какие типы объектов можно использовать в пункте switch ?

Переключатель позволяет выбрать несколько путей выполнения на основе значения переменных.

Каждый путь помечен case или default , оператор switch оценивает каждое выражение case на соответствие и выполняет все операторы, следующие за соответствующей меткой, пока не будет найден оператор break . Если он не может найти совпадение, вместо него будет выполнен блок по умолчанию :

switch (yearsOfJavaExperience) {
case 0:
System.out.println("Student");
break;
case 1:
System.out.println("Junior");
break;
case 2:
System.out.println("Middle");
break;
default:
System.out.println("Senior");
}

Мы можем использовать byte , short , char , int , их обернутые версии, enum и String в качестве значений переключателя .

Q3. Что происходит, когда мы забываем поставить оператор break в пункте case of switch ?

Оператор switch терпит неудачу. Это означает, что он продолжит выполнение всех меток case до тех пор, пока if не найдет оператор break , даже если эти метки не соответствуют значению выражения.

Вот пример, чтобы продемонстрировать это:

int operation = 2;
int number = 10;

switch (operation) {
case 1:
number = number + 10;
break;
case 2:
number = number - 4;
case 3:
number = number / 3;
case 4:
number = number * 10;
break;
}

После запуска кода число содержит значение 20 вместо 6. Это может быть полезно в ситуациях, когда мы хотим связать одно и то же действие с несколькими случаями.

Q4. Когда предпочтительнее использовать S - ведьму вместо оператора I f-Then-Else и наоборот?

Оператор switch лучше подходит для проверки одной переменной на множество одиночных значений или когда несколько значений будут выполнять один и тот же код:

switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 2:
days = 28;
break;
default:
days = 30;
}

Оператор if-then-else предпочтительнее, когда нам нужно проверить диапазоны значений или несколько условий:

if (aPassword == null || aPassword.isEmpty()) {
// empty password
} else if (aPassword.length() < 8 || aPassword.equals("12345678")) {
// weak password
} else {
// good password
}

Q5. Какие типы циклов поддерживает Java?

Java предлагает три разных типа циклов: for , while и do-while .

Цикл for обеспечивает способ перебора диапазона значений. Наиболее полезно, когда мы заранее знаем, сколько раз задача будет повторяться:

for (int i = 0; i < 10; i++) {
// ...
}

Цикл while может выполнять блок операторов, пока выполняется определенное условие :

while (iterator.hasNext()) {
// ...
}

Do-while — это вариант оператора while , в котором вычисление логического выражения находится в конце цикла. Это гарантирует, что код выполнится хотя бы один раз:

do {
// ...
} while (choice != -1);

Q6. Что такое расширенный цикл for?

Другой синтаксис оператора for , предназначенный для перебора всех элементов коллекции, массива, перечисления или любого объекта, реализующего интерфейс Iterable :

for (String aString : arrayOfStrings) {
// ...
}

Q7. Как вы можете заранее выйти из цикла?

Используя оператор break , мы можем немедленно прекратить выполнение цикла:

for (int i = 0; ; i++) {
if (i > 10) {
break;
}
}

Q8. В чем разница между немаркированным и помеченным оператором break ?

Непомеченный оператор break завершает самый внутренний оператор switch , for , while или do-while , тогда как помеченный break завершает выполнение внешнего оператора.

Давайте создадим пример, чтобы продемонстрировать это:

int[][] table = { { 1, 2, 3 }, { 25, 37, 49 }, { 55, 68, 93 } };
boolean found = false;
int loopCycles = 0;

outer: for (int[] rows : table) {
for (int row : rows) {
loopCycles++;
if (row == 37) {
found = true;
break outer;
}
}
}

Когда число 37 найдено, помеченный оператор break завершает самый внешний цикл for , и больше циклы не выполняются. Таким образом, loopCycles заканчивается значением 5.

Однако немаркированный разрыв завершает только самый внутренний оператор, возвращая поток управления самому внешнему оператору, который продолжает цикл до следующей строки в табличной переменной, в результате чего loopCycles завершается со значением 8.

Q9. В чем разница между немеченым и помеченным оператором continue ?

Оператор continue без метки переходит к концу текущей итерации в самом внутреннем цикле for , while или do- while, тогда как оператор continue с меткой переходит к внешнему циклу, отмеченному данной меткой.

Вот пример, демонстрирующий это:

int[][] table = { { 1, 15, 3 }, { 25, 15, 49 }, { 15, 68, 93 } };
int loopCycles = 0;

outer: for (int[] rows : table) {
for (int row : rows) {
loopCycles++;
if (row == 15) {
continue outer;
}
}
}

Аргументация та же, что и в предыдущем вопросе. Помеченный оператор continue завершает самый внешний цикл for .

Таким образом, loopCycles завершается со значением 5, тогда как немаркированная версия завершает только самый внутренний оператор, в результате чего loopCycles завершается со значением 9.

Q10. Опишите поток выполнения внутри конструкции try-catch-finally .

Когда программа вошла в блок try и внутри него генерируется исключение, выполнение блока try прерывается, и поток управления продолжается блоком catch , который может обработать выброшенное исключение.

Если такого блока не существует, выполнение текущего метода останавливается, и исключение выдается предыдущему методу в стеке вызовов. В качестве альтернативы, если исключения не возникает, все блоки catch игнорируются, и выполнение программы продолжается в обычном режиме.

Блок finally всегда выполняется независимо от того, было ли выброшено исключение внутри тела блока try или нет .

Q11. В каких ситуациях блок finally может не выполняться?

Когда JVM завершается во время выполнения блоков try или catch , например, путем вызова System.exit(), или когда исполняемый поток прерывается или уничтожается, тогда блок finally не выполняется.

Q12. Каков результат выполнения следующего кода?

public static int assignment() {
int number = 1;
try {
number = 3;
if (true) {
throw new Exception("Test Exception");
}
number = 2;
} catch (Exception ex) {
return number;
} finally {
number = 4;
}
return number;
}

System.out.println(assignment());

Код выводит число 3. Несмотря на то, что блок finally выполняется всегда, это происходит только после выхода из блока try .

В примере оператор return выполняется до завершения блока try-catch . Таким образом, присваивание числа в блоке finally не имеет никакого эффекта, так как переменная уже возвращена в вызывающий код метода присваивания .

Q13. В каких ситуациях можно использовать блокировку try-finally, даже если исключения не могут быть выброшены?

Этот блок полезен, когда мы хотим убедиться, что мы случайно не обходим очистку ресурсов, используемых в коде, сталкиваясь с оператором break , continue или return :

HeavyProcess heavyProcess = new HeavyProcess();
try {
// ...
return heavyProcess.heavyTask();
} finally {
heavyProcess.doCleanUp();
}

Кроме того, мы можем столкнуться с ситуациями, в которых мы не можем локально обработать выбрасываемое исключение, или мы хотим, чтобы текущий метод все еще выдавал исключение, позволяя нам высвободить ресурсы:

public void doDangerousTask(Task task) throws ComplicatedException {
try {
// ...
task.gatherResources();
if (task.isComplicated()) {
throw new ComplicatedException("Too difficult");
}
// ...
} finally {
task.freeResources();
}
}

Q14. Как работает попытка с ресурсами ?

Оператор try-with-resources объявляет и инициализирует один или несколько ресурсов перед выполнением блока try и автоматически закрывает их в конце оператора независимо от того, завершился ли блок нормально или внезапно. В качестве ресурса может использоваться любой объект, реализующий интерфейсы AutoCloseable или Closeable :

try (StringWriter writer = new StringWriter()) {
writer.write("Hello world!");
}

3. Заключение

В этой статье мы рассмотрели некоторые из наиболее часто задаваемых вопросов, возникающих в технических интервью для разработчиков Java, относительно операторов потока управления. Это следует рассматривать только как начало дальнейших исследований, а не как исчерпывающий список.

Удачи на собеседовании.