JavaRush /Java блог /Random /Разбор вопросов и ответов с собеседований на Java-разрабо...
Константин
36 уровень

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16

Статья из группы Random
Hello, friend! Как много времени нужно потратить, чтобы стать разработчиком? Я спрашивал много разных людей и слышал много разных ответов. Для чего-то и кого-то может хватить и месяца, ну а кому-то и года будет мало. Но я знаю точно, что становление Java-разработчиком — это тернистый и долгий путь, независимо от твоих начальных способностей. Ведь важны не столько способности, сколько упертость и трудолюбие. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 1Поэтому сегодня мы все так же целеустремленно продолжаем разбирать самые популярные вопросы с собеседований на Java-разработчика. Их изучение постепенно приблизит тебя к заветной цели. Приступим!

17. Приведите примеры удачного и неудачного использования Optional

Предположим, у нас есть некоторый ряд значений, по которому мы проходимся стримом, и в итоге получаем некоторый Optional как результат:

Optional<String> stringOptional = Stream.of("a", "ab", "abc", "abcd")
   .filter(str -> str.length() >= 3)
   .findAny();
Нам, как полагается, нужно достать из этого Optional значение. Просто использовать get() — плохой способ:

String result = stringOptional.get();
Но ведь этот метод должен достать значение из Optional и вернуть нам? Это, конечно, так, но если в нём есть значение. Ну а если значения в стриме были другие, и в итоге мы получили пустой Optional, при попытке взять значение из него посредством использования метода get() будет выброшено: Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 2Что не есть хорошо. В таком случае лучше использовать конструкции:
  1. 
    String result = null;
    if (stringOptional.isPresent()) {
     stringOptional.get();
    }
    

    В данном случае мы проверяем, есть ли элемент в Optional. Если нет — результирующая строка имеет свое старое значение.

  2. 
    String result = stringOptional.orElse("default value");
    

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

  3. 
    String result = stringOptional.orElseThrow(() -> new CustomException());
    

    В данном случае мы сами выбрасываем исключение при пустом Optional.

Это бывает удобно в приложении, когда, к примеру, используется метод Spring JPA — findById(), который возвращает Optional значения. В таком случае данным методом мы пытаемся взять значение, и если его нет — бросаем некоторое Runtime исключение, которое обрабатывается на уровне контроллеров с помощью ExceptionHandler и конвертируется в HTTP ответ со статусом 404 - NOT FOUND. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 3

18. Можно ли объявлять main method как final?

Да, несомненно, ничто не мешает нам объявить метод main() как final. Компилятор не выдаст ошибок. Но стоит помнить, что какой-либо метод после объявления его как final станет последним методом — не переопределяемым. Хотя, кто будет переопределять main??? Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 4

19. Можно ли импортировать те же package / class дважды? Какие могут быть последствия?

Да можно. Последствия? У нас будет пара ненужных импортов, которые Intelijj IDEA будет отображать как серые, т.е. неиспользуемые. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 5Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 6

20. Что такое Casting? Когда можем получить исключение ClassCastException?

Casting, или приведение типов — это процесс преобразования одного типа данных в другой тип данных: вручную (неявное приведение) или автоматически (явное приведение типов). Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 7Автоматическое преобразование выполняет компилятор, а ручное — разработчик. Приведение типов для примитивов и классов несколько отличается, поэтому и рассмотрим их по отдельности. Примитивные типы Пример автоматического приведения примитивных типов:

int value = 17;
double convertedValue = value;
Как видите, никаких дополнительных манипуляций помимо знака = тут не нужно. Пример ручного приведения примитивных типов:

double value = 17.89;
int convertedValue = (int)value;
В этом случае мы можем наблюдать ручное приведение, которое реализуется с помощью (int), при этом часть за запятой будет отброшена, и convertedValue будет иметь значение - 17. Подробнее о приведении примитивных типов читайте в этой статье. Ну а теперь давайте перейдем к объектам. Ссылочные типы Для ссылочных типов автоматическое приведение возможно для классов наследников к классам родителям. Это так же называется полиморфизмом. Предположим, у нас есть класс Lion, который наследуется от класса Cat. В этом случае автоматическое преобразование будет выглядеть так:

Cat cat = new Lion();
А вот с явным приведением всенесколько сложнее, ведь нет функционала обрезания лишнего, как у примитивов. И сделав просто явно преобразование вида:

Lion lion= (Lion)new Cat();
Вы и получите ошибку: Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 8В самом деле, вы можете классу наследнику Lion добавить методы, которых не было изначально в классе Cat, и потом пытаться их вызвать, ведь типа объекта у вас станет Lion. Ну а в этом логики никакой нет. Поэтому, сужение типа возможно лишь когда изначальный объект типа Lion, но был позже приведен к классу родителя:

Lion lion = new Lion();
Cat cat = lion;
Lion newLion = (Lion)cat;
Также, для большей надежности, сужающее приведение для объектов рекомендуется с использованием конструкции instanceOf:

if (cat instanceof Lion) {
 newLion = (Lion)new Cat();
}
Подробнее о приведениях ссылочных типов — в этой статье.

21. Почему современные фреймворки используют в основном только unchecked exceptions?

Думаю, это все потому, что обработка checked исключений — это ещё тот спагетти код, который повсеместно повторяется, при этом не во всех случаях действительно нужен. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 9В таких случаях легче уже сделать обработку внутри фреймворка, чтобы лишний раз не перекладывать это на плечи разработчиков. Да, несомненно, аварийная ситуация может возникнуть, но эти самые uncheked исключения можно обрабатывать более удобным способом, не заморачиваясь над обработкой в try-catch и не прокидывая дальше по методам. Достаточно всего лишь в exceptionHandler-е конвертировать исключение в некоторый HTTP-ответ.

22. Что такое static import?

При использовании статических данных (методов, переменных) можно не создавать сам объект, а делать это по имени класса, но и в таком случае нам необходима ссылка на класс. С ней все просто: она добавляется при помощи обычного импорта. Но что если мы заходим использовать статический метод без написания имени класса, как будто это статический метод текущего класса? Это возможно с помощью статического импорта! В таком случае мы должны прописывать static import и ссылку на тот метод. Как вот например, статический метод класса Math для вычисления значения косинуса:

import static java.lang.Math.cos;
В итоге мы можем использовать метод без указания имени класса:

double result = cos(60);
Также элементарно мы можем подгрузить сразу все статические методы класса с помощью статического импорта:

import static java.lang.Math.*;
Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 10

23. Какая связь между методами hashCode() и equals()?

Согласно Oracle, существует следующее правило: Если два объекта равны (т.е. метод equals() возвращает true), у них должен быть одинаковый хэш-код. При этом не стоит забывать, что одинаковый хэш-код может быть у двух разных объектов. Xтобы разобраться, почему же equals() и hashCode() переопределяют всегда в паре, рассмотрим следующие случаи:
  1. Оба метода переопределенные.

    В таком случае два разных объекта с одинаковыми внутренними состояниями будут возвращать при equals()true, в то время как и hashCode() будет у обоих возвращать одно и то же число.

    Получается, все окей, ибо правило выполняется.

  2. Оба метода не переопределенные.

    В таком случае два разных объекта с одинаковыми внутренними состояниями при equals() будут возвращать false, так как сравнение идёт по ссылке через оператор ==.

    Метод hashCode() также вернет разные значения (скорее всего), так как он выдает преобразованное значение адреса ячейки памяти. Но у одного и того же объекта это значение будет одинаковым, как и equals() в данном случае вернет true, только когда ссылки указывают на один и тот же объект.

    Получается, и в данном случае все окей и правило выполняется.

  3. Переопределен equals(), не переопределен hashCode().

    В таком случае для двух разных объектов с одинаковыми внутренними состояниями equals() будет возвращать true, а hashCode() будет возвращать (скорее всего) разные значения.

    Происходит нарушение правила, поэтому так делать не рекомендуется.

  4. Не переопределен equals(), переопределен hashCode().

    В таком случае для двух разных объектов с одинаковыми внутренними состояниями equals() будет возвращать false, а hashCode() будет возвращать одинаковые значения.

    Происходит нарушение правила, поэтому подход неверный.

Как вы видите, выполнение правила возможно лишь когда equals() и hashCode() переопределяются оба либо оба не переопределяются вовсе. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 11Подробнее об equals() и hashCode() читайте в данной статье.

24. Когда используют BufferedInputStream и BufferedOutputStream классы?

InputStream используется для побайтового чтения данных из некоторого ресурса, а OutputStream — для побайтовой записи. Но побайтовые операции могут быть весьма неудобными и требуют дополнительной обработки (чтобы нормально считывать/записывать тексты). Собственно, для упрощения таких байтовых записей ввели BufferedOutputStream, а для чтения BufferedInputStream. Эти классы являются не чем иным как буферами, накапливающими данные, позволяющими работать с данными не побайтово, а целыми пакетами данных (массивами). При создании BufferedInputStream принимает в конструктор экземпляр типа InputStream, с которого идёт считка данных:

BufferedInputStream bufferedInputStream = new BufferedInputStream(System.in);
byte[] arr = new byte[100];
bufferedInputStream.read(arr);
System.in — это объект типа InputStream, который считывает данные с консоли. То есть с помощью данного объекта BufferedInputStream мы можем читать данные с InputStream, записывая их в переданный массив. Получается своего рода обертка класса InputStream. Массив arr из данного примера — массив которому достаются данные из BufferedInputStream. Тот в свою очередь читает данные из InputStream другим массивом, который по умолчанию имеет размер 2048 байт. Аналогично и для BufferedOutputStream: в конструктор необходимо передать экземпляр типа OutputStream, в который мы будем писать данные целыми массивами:

byte[] arr = "Hello world!!!".getBytes();
BufferedOutputStream bufferedInputStream = new BufferedOutputStream(System.out);
bufferedInputStream.write(arr);
bufferedInputStream.flush();
System.out — это объект типа OutputStream, который записывает данные в консоли. Метод flush() отправляет данные с BufferedOutputStream в OutputStream, очищая при этом BufferedOutputStream. Без этого метода ничего записываться и не будет. И аналогично предыдущему примеру: arr — это массив, из которого записываются данные в BufferedOutputStream. С него же они пишутся в OutputStream уже другим массивом, который по умолчанию имеет размер 512 байт. Подробнее об этих двух классах — в статье.

25. Какая разница между классами java.util.Collection и java.util.Collections?

Collection — интерфейс, который является головой в иерархии коллекций. Он представляет классы, позволяющие создавать, содержать и изменять целые группы объектов. Для этого предоставляется множество методов, вроде add(), remove(), contains() и других. Основные интерфейсы класса Collection:
  • Set — интерфейс, описывающий множество, которое содержит неупорядоченные уникальные (неповторяющиеся) элементы.

  • List — интерфейс, описывающий структуру данных, которая хранит упорядоченную последовательность объектов. Эти объекты получают свой индекс (номер), используя который можно взаимодействовать с ними: брать, удалять, изменять, перезаписывать.

  • Queue — интерфейс, описывающий структуру данных с хранением элементов в виде очереди, которая следует правилу — FIFO — First In First Out.

Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 12Подробнее о Collection. Collections — утилитный класс, предоставляющий множество всевозможных служебных методов. Например:
  • addAll(Collection<? super T> collection, T…element) — добавляет в collection переданные элементы типа Т.

  • сopy(List<? super T> dest, List<? extends T> src) — копирует все элементы из списка src в список в dest.

  • emptyList() — возвращает пустой список.

  • max(Collection<? extends T> collection, Comparator<? super T> comp) — возвращает максимальный элемент данной коллекции в соответствии с порядком, установленным указанным компаратором.

  • unmodifiableList(List<? extends T> list) — возвращает неизменяемое представление переданного списка.

И таких разнообразных удобных методов в Collections — великое множество. Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 13С полным списком данных методов можно ознакомиться на сайте Oracle. Я не зря сказал, что они удобные. Ведь они все статические. То есть, вам не нужно каждый раз создавать объект данного класса, чтобы вызвать у него необходимый метод. Вам достаточно лишь прописать название класса, вызвать у него нужный метод и передать все требуемые аргументы. Подводя черту, Collection — корневой интерфейс структуры коллекций. Collections — вспомогательный класс для более удобной обработки объектов, принадлежащих типу из структуры коллекций. Ну а на сегодня всё. Всем добра!Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 16 - 14
Другие материалы серии:
Комментарии (4)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
fedyaka Уровень 36
31 октября 2022
Расходимся народ, продолжения не будет, даже статья с 250+ вопросами не находится
Sekator Уровень 41
14 октября 2022
пропустили кусок це робота з файлами типу -> НІО
2 октября 2022
вручную (неявное приведение) или автоматически (явное приведение типов). Наоборот
Юрий Уровень 31
3 мая 2022
Привет! Когда планируете продолжение????