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

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

Стаття з групи Random UA
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 і не прокидаючи далі за методами. Достатньо лише в винятку Handler -е конвертувати виняток у деяку 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 ), у них повинен бути однаковий хеш-код. При цьому не варто забувати, що однаковий хеш-код може бути у двох різних об'єктів. Щоб розібратися, чому ж 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 - це масив, з якого записуються дані в Buffered OutputStream . З нього вони пишуться в 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
Інші матеріали серії:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ