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, относительно операторов потока управления. Это следует рассматривать только как начало дальнейших исследований, а не как исчерпывающий список.
Удачи на собеседовании.