Топ-50 Java Core вопросов и ответов на собеседовании. Часть 1
![Топ-50 Java Core вопросов и ответов на собеседовании. Часть 2 - 1]()
![Топ-50 Java Core вопросов и ответов на собеседовании. Часть 2 - 2]()
На диаграмме видно, что в целом все исключения делятся на две группы — exceptions и error.
Error — используются JVM для отображения ошибок, после которых работа приложения уже не имеет смысла. Например StackOverFlowError, которая говорит, что стек заполнен и программа уже не может работать.
Exception — исключения, которые генерируются программно в коде. Есть разные исключения, проверяемые и непроверяемые, но главное, что они есть, и их можно перехватить и продолжить работу приложения.
Exceptions, в свою очередь, еще делятся на тех, кто наследуется от RuntimeException и других наследников Exception.
В рамках этого вопроса информации достаточно. О том, что такое проверяемые / непроверяемые исключения, поговорим ниже.
Рассмотрим следующую Java-программу. Код прекрасно компилируется, но при запуске выдает исключение

Collections
25. Что имеется в виду под Collections в Java?
Collection — это фреймворк, который создан для сохранения и манипуляции объектами. Используется для выполнения следующих операций:- поиск;
- сортировка;
- манипуляция;
- добавление;
- удаление.
java.util
пакете.
26. Какие классы и интерфейсы доступны в Collection фреймворке?
Интерфейсы:- Collection;
- List;
- Set;
- Map;
- Sorted Set;
- Sorted Map;
- Queue.
- Lists:
- ArrayList;
- LinkedList;
- Vector(deprecated).
- Sets:
- HashSet;
- LinkedHashSet;
- TreeSet.
- Maps:
- HashMap
- TreeMap
- HashTable (deprecated)
- LinkedHashMap
- Queue
- Priority Queue.
27. Что подразумевается под sorted и ordered в коллекциях?
Ordered (упорядочивание):
Это означает, что элементы, которые хранятся в коллекции, основаны на значениях, добавленных в коллекцию. Таким образом, мы можем перебирать значения из коллекции в определенном порядке. Другими словами это значит, что у элементов коллекции есть свой специфический порядок, согласно которому они расположены. Для лучшего понимания, коллекция, которая не упорядочена (ordered), хранит элементы в произвольном порядке. Например, Set.Sorted (отсортированный):
Это значит, что группа элементов отсортирована в коллекции на основе данных элемента коллекции. То есть не только коллекция упорядочена (ordered), но еще и порядок элементов зависит от их значений. Этот порядок может меняться, если отсортировать по другому значению элемента.28. Какие есть коллекции с List интерфейсом? Как происходит работа с List?
Значения элементов в лист базируются на их индексе — они упорядочены по индексу. Повторения элементов разрешены (то есть можно добавить один и тот же объект в коллекцию несколько раз, и это будет нормально).ArrayList:
Самая распространенная коллекция. По сути, это массив с динамически расширяемым размером. Работа по управлению размером массива лежит на коллекции. Для нас важно понять, что в большинстве случаев это то, что нам нужно использовать. Особенности:- быстрый перебор и быстрый поиск по индексу;
- коллекция упорядочена по индексу, но не сортирована;
- реализует RandomAccess интерфейс;
- медленное добавление в середину списка.
public class A {
public static void main(String[] args) {
ArrayList names = new ArrayList<>();
names.add("John");
names.add("John");
names.add("Roman");
names.add("Ivan");
}
}
>> output
[John, John, Roman, Ivan]
В выводе видно, что это повторяемые элементы. Они выведены в порядке, в котором их записали.
Что еще почитать? Да море информации, даже выходить из JavaRush не нужно:
Linked List:
Это коллекция, в которой каждый элемент имеет ссылку на предыдущий и следующий элементы. По этим ссылкам можно переходить от одного элемента к другому. При добавлении элемента просто меняются ссылки на предыдущий и следующий элементы:
- элементы связаны друг с другом, то есть реализован двусвязный список;
- общая скорость работы заметно ниже, чем в ArrayList;
- отличный выбор для большого количества вставок и удалений в середину массива;
- реализует интерфейсы списков Queue и Deque, поэтому и имеет их методы для работы.
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("One");
linkedList.add("Two");
linkedList.add("Three");
29. Расскажите о коллекции Map и ее реализациях?
Map — это коллекция ключ-значение (key-value). Есть уникальный ключ и значение, которое соответствует этому значению. Используетсяequals()
и hashcode()
методы для определения уникальности ключа.
HashMap:
- не отсортирован и не упорядочен;
- используют если не важны порядок и сортировка;
- поддерживает null ключ.
public class CollectionExample {
public static void main(String[] args) {
HashMap positions = new HashMap<>();
positions.put("junior", "Ivan");
positions.put("middle", "Roman");
positions.put("senior", "Vasily");
positions.put("team lead", "Anton");
positions.put("arthitect", "Andrew");
positions.put("senior", "John");
System.out.println(positions);
}
}
// вывод в консоль
// {junior=Ivan, middle=Roman, senior=John, team lead=Anton, arthitect=Andrew}
Ключи всегда уникальны, поэтому записан только один senior.
LinkedHashMap:
- поддерживает порядок вставки;
- медленнее, чем HashMap;
- ожидается, что итерация быстрее, чем в HashMap.
public class CollectionExample {
public static void main(String[] args) {
LinkedHashMap<String, String> positions = new LinkedHashMap<>();
positions.put("junior", "Ivan");
positions.put("middle", "Roman");
positions.put("senior", "Vasily");
positions.put("team lead", "Anton");
positions.put("arthitect", "Andrew");
positions.put("senior", "John");
System.out.println(positions);
}
}
// вывод в консоль
// {junior=Ivan, middle=Roman, senior=John, team lead=Anton, arthitect=Andrew}
TreeMap:
Реализация мапы, которая сохраняет записи отсортированными в соответствии с естественным порядком их ключей или, что еще лучше, с использованием компаратора, если он предоставляется в конструкторе при создании мапы. Пример:Без компаратора
public class CollectionExample { public static void main(String[] args) { TreeMap<Integer, String> positions = new TreeMap<>(); positions.put(1, "Ivan"); positions.put(3, "Roman"); positions.put(2, "Vasily"); positions.put(10, "Anton"); positions.put(7, "Andrew"); positions.put(1, "John"); System.out.println(positions); } } // вывод в консоль // {1=John, 2=Vasily, 3=Roman, 7=Andrew, 10=Anton}
С компаратором
public class CollectionExample { public static void main(String[] args) { //используем реализацию Strategy Pattern'a и добавим компаратор: TreeMap<Integer, String> positions = new TreeMap<>(Comparator.reverseOrder()); positions.put(1, "Ivan"); positions.put(3, "Roman"); positions.put(2, "Vasily"); positions.put(10, "Anton"); positions.put(7, "Andrew"); positions.put(1, "John"); System.out.println(positions); } } // вывод в консоль // {10=Anton, 7=Andrew, 3=Roman, 2=Vasily, 1=John}
30. Расскажите о коллекции Set и ее реализациях?
Set — это множество уникальных элементов, и это ее главная особенность. То есть Set не допускает повторения одних и тех же элементов. Здесь важно, чтобы у объектов, которые добавляются, был реализован методequals
.
HashSet:
- не отсортирован и не упорядочен. Под капотом там HashMap с заглушкой для значения. Посмотрите сами ;)
- использует hashCode для добавления объектов;
- стоит использовать, когда нужно иметь уникальные объекты и их порядок не важен.
public class CollectionExample {
public static void main(String[] args) {
HashSet<String> positions = new HashSet<>();
positions.add("junior");
positions.add("junior");
positions.add("middle");
positions.add("senior");
positions.add("team lead");
positions.add("architect");
System.out.println(positions);
}
}
// вывод в консоль
// [senior, middle, team lead, architect, junior]
Здесь видно, что элемент “junior”, который дважды добавлен, присутствует только в единичном экземпляре. И порядок не такой же, как при добавлении.
LinkedHashSet:
- упорядоченная версия HashSet;
- поддерживает двусвязный список для всех элементов;
- использовать его, когда требуется упорядоченность при итерации.
public class CollectionExample {
public static void main(String[] args) {
LinkedHashSet<String> positions = new LinkedHashSet<>();
positions.add("junior");
positions.add("junior");
positions.add("middle");
positions.add("senior");
positions.add("team lead");
positions.add("architect");
System.out.println(positions);
}
}
// вывод в консоль
// [senior, middle, team lead, architect, junior]
TreeSet:
- одна из двух сортированных коллекций;
- использует структуру красно-черного дерева и гарантирует, что элементы будут в возрастающем порядке;
- под капотом это TreeMap с заглушкой на значениях. А элементами TreeSet являются ключи к TreeMap (также посмотрите ;)).
public class CollectionExample {
public static void main(String[] args) {
TreeSet<String> positions = new TreeSet<>();
positions.add("junior");
positions.add("junior");
positions.add("middle");
positions.add("senior");
positions.add("team lead");
positions.add("architect");
System.out.println(positions);
}
}
// вывод в консоль
// [architect, junior, middle, senior, team lead]
Exceptions
31. Что такое Exception?
Exception — это проблема, которая может возникнуть в runtime. Это исключительная ситуация, которая возникает из-за каких-то причин. Диаграмма наследования исключений выглядит так (нужно знать ее назубок ;) ):
32. Как JVM обрабатывает исключения?
Как это работает? Как только где-то создается исключение, runtime создает Exception Object (обозначим как ExcObj). В нем хранится вся необходимая для работы информация — само исключение, которое вызывалось и место, где это произошло. СозданиеExcObj
и передача в runtime есть ничто иное как “выбрасывание исключения”.
ExcObj
содержит методы, по которым можно дойти до место создания исключения. Это множество методов называется Call Stack.
Далее, runtime система ищет метод в Call Stack, который сможет обработать наше исключение.
Если он таки находит соответствующий обработчик, то есть тип исключения совпадает с типом в обработчике, все хорошо.
Если не находит, то runtime передает всё в default exception handler, который подготавливает ответ и завершает работу.
Как это выглядит наглядно:
/**
* Пример, в котором показываются две опции — когда находится обработчик для исключения и когда нет.
*/
class ThrowerExceptionExample {
public static void main(String[] args) throws IllegalAccessException {
ThrowerExceptionExample example = new ThrowerExceptionExample();
System.out.println(example.populateString());
}
/**
* Здесь происходит перехват одного из возможных исключений — {@link IOException}.
* А вот второй будет пробрасываться дальше вверх по вызову.
*/
private String populateString() throws IllegalAccessException {
try {
return randomThrower();
} catch (IOException e) {
return "Caught IOException";
}
}
/**
* Здесь две опции: или бросается {@link IOException} или {@link IllegalAccessException}.
* Выбирается случайным образом.
*/
private String randomThrower() throws IOException, IllegalAccessException {
if (new Random().nextBoolean()) {
throw new IOException();
} else {
throw new IllegalAccessException();
}
}
}
В нашем случае CallStack схематично будет иметь вид:
randomThrower() => populateString() => main(String[] args)
Есть две опции: случайным образом будет выброшено одно или другое исключение. Для IOException все ок, если будет сгенерировано оно, то результатом работы будет: "Caught IOException"
.
А вот если будет второе исключение, обработчика на которого нет, программа будет остановлена с таким выводом:
Exception in thread "main" java.lang.IllegalAccessException
at ThrowerExceptionExample.randomThrower(CollectionExample.java:38)
at ThrowerExceptionExample.populateString(CollectionExample.java:24)
at ThrowerExceptionExample.main(CollectionExample.java:15)
33. Как программистам обрабатывать исключения?
В вопросах выше уже использовались те или иные ключевые слова для работы с исключениями, теперь нужно поговорить о них более подробно. Какие есть ключевые слова?- try
- catch
- throw
- throws
- finally
try-catch-finally
— это конструкция, при помощи которой можно правильным образом перехватить и обработать исключение.try
— может быть только один раз, в нем и происходит логика;catch
— блок, который принимает какой-то тип исключения, их может быть множество. Например, в блоке try будет генерироваться несколько исключений, которые никак друг с другом не связаны;finally
— “наконец-то” и этот блок. Это блок, который выполнится в любом случае, независимо от того, что делается в try, catch.
try {
// сюда передают тот код, который может вызвать исключение.
} catch (IOException e) {
// первый catch блок, который принимает IOException и все его подтипы(потомки).
// Например, нет файла при чтении, выпадает FileNotFoundException, и мы уже соответствующе
// обрабатываем это.
} catch (IllegalAccessException e) {
// если нужно, можно добавить больше одного catch блока, в этом нет проблем.
} catch (OneException | TwoException e) {
// можно даже объединять несколько в один блок
} catch (Throwable t) {
// а можно сказать, что мы принимаем ВСЁ))))
} finally {
// этот блок может быть, а может и не быть.
// и он точно выполнится.
}
Внимательно вчитайтесь в описание примера и будет всё ясно)
34. throw и throws в Java
throw
throw
используют в случае, когда нужно явно создать новое исключение. Применяют его для создания и выбрасывания пользовательских исключений.
Например, исключения, связанные с валидацией. Обычно для валидации наследуются от RuntimeException
.
Пример:
// пример пробрасывания исключения
throw new RuntimeException("because I can :D");
Важно, что использовать эту конструкцию можно только тем, что наследуется от Throwable
. То есть, нельзя сказать так:
throw new String("как тебе такое, Илон Маск?");
Далее, работа потока обрывается и начинается поиск обработчика, который смог бы обработать его. Когда не находит, идет к методу, который вызвал его, и так поиск будет идти наверх по строке вызовов пока либо не найдет соответствующий обработчик, либо оставит работу приложения.
Смотрим:
// Пример, который демонстрирует работу throw
class ThrowExample {
void willThrow() throws IOException {
throw new IOException("Because I Can!");
}
void doSomething() {
System.out.println("Doing something");
try {
willThrow();
} catch (IOException e) {
System.out.println("IOException was successfully handled.");
}
}
public static void main(String args[]) {
ThrowExample throwExample = new ThrowExample();
throwExample.doSomething();
}
}
Если запустить программу, получим такой результат:
Doing something
IOException was successfully handled.
throws
throws
— механизм, при помощи которого метод может выбрасывать одно или более исключений. Добавляются они через запятую.
Смотрим как это легко и просто:
private Object willThrow() throws RuntimeException, IOException, FileNotFoundException
Причем важно отметить, что могут быть как проверяемые таки непроверяемые исключения.
Разумеется, что непроверяемые исключения можно и не добавлять в throws
, но правила хорошего тона говорят об обратном.
Если это проверяемые, то используя метод, который их генерирует, нужно как-то его обработать. Есть два варианта:
- Написать
try-catch
с соответствующим и выше по наследованию исключением. - Использовать
throws
точно так же, чтобы эта проблема была уже у кого-то другого :D
35. Checked и Unchecked исключения в Java
B Java есть два типа исключений — checked и unchecked.Checked исключения:
Это исключения, которые проверяются во время компиляции. Если какой-то код в методе во время исключения выдает checked исключение, метод обязан либо обработать его при помощиtry-catch
, либо пробросить его дальше
На примере, который считывает картинку из пути "/users/romankh3/image.png", обновляет ее каким-то образом(для нас это не важно) и сохраняет ее обратно.
class CheckedImageExample {
public static void main(String[] args) {
File imageFile = new File("/users/romankh3/image.png");
BufferedImage image = ImageIO.read(imageFile);
updateAndSaveImage(image, imageFile);
}
private static void updateAndSaveImage(BufferedImage image, File imageFile) {
ImageIO.write(image, "png", imageFile);
}
}
Такой код компилироваться не будет, так как статические методы ImageIO.read()
и ImageIO.write()
выбрасывают исключение IOException, которое является checked (проверяемым) и должно соответственно быть обработанным.
Здесь две опции, которые мы уже обсудили выше: или использовать try-catch
, или пробросить дальше. Для лучшего усвоения сделаем и так, и эдак.
То есть в методе updateAndSave
просто пробросим, а уже в главном методе воспользуемся try-catch
:
class CheckedImageExample {
public static void main(String[] args) {
File imageFile = new File("/users/romankh3/image.png");
try {
BufferedImage image = ImageIO.read(imageFile);
updateAndSaveImage(image, imageFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void updateAndSaveImage(BufferedImage image, File imageFile) throws IOException {
ImageIO.write(image, "png", imageFile);
}
}
Unchecked исключения:
Это те исключения, которые на этапе компиляции не проверяются. То есть метод может генерировать RuntimeException, а компилятор не напомнит каким-то образом это обработать. Как показано ниже, у нас все, кто наследуются от RuntimeException и Error являются непроверяемыми.
ArrayIndexOutOfBoundsException
. Компилятор позволяет его компилировать, потому что ArrayIndexOutOfBoundsException
является непроверенным исключением.
Обычная ситуация с массивом, которая может быть:
class CheckedImageExample {
public static void main(String[] args) {
int[] array = new int[3];
array[5] = 12;
}
}
Результатом будет:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at main(CheckedImageExample.java:12)
Кстати, вы уже заметили, что в Java короткие имена никто не дает? Чем больше, тем лучше. Он, Spring Framework, в этом при успел очень сильно: возьми только какой-нибудь BeanFactoryPostProcessor класс)))
36. Что такое try-with-resources?
Это механизм, при помощи которого нужно правильно закрывать все ресурсы. Как-то не понятно, да?) Для начала, что такое ресурс... Ресурс — это объект, после работы с которым нужно закрыть его, то есть вызвать методclose()
. Ресурсом называются все объекты, которые реализуют интерфейс AutoClosable
, который, в свою очередь реализует интерфейс Closeable
.
Для нас важно понять, что все InputStream
, OutpuStream
являются ресурсами и их нужно правильно и успешно высвобождать. Вот как раз для этого и нужно нам использовать try-with-resource
конструкцию.
Вот как она выглядит:
private void unzipFile(File zipFile) throws IOException {
try(ZipInputStream zipOutputStream = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry zipEntry = zipOutputStream.getNextEntry();
while (zipEntry != null) {
}
}
}
private void saveZipEntry(ZipEntry zipEntry) {
// логика сохранения
}
Вот этом примере ресурс — это ZipInputStream
, после работы с которым нужно будет закрыть его. И чтоб не думать о том, что нужно вызвать метод close()
, мы просто определяем эту переменную в блоке try, как показано в примере и в рамках этого блока выполняем все необходимое. Что делает пример? Он разархивирует zip архив. Для этого нужно воспользоваться InputStream
’ом.
Определять можно больше одной переменной, разделяют их точкой с запятой.
А в чем проблема? Но ведь можно использовать finally
блок, — возможно, скажете вы. Вот статья, в которой подробно описываются проблемы с этим подходом. Также в ней описывается весь перечень неудач, которые могут постигнуть того, кто пренебрежет использованием этой конструкции. Рекомендую к прочтению ;)
В завершающей части — вопросы/ответы по теме Multithreading.
Мой профиль на GitHub
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ