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() буде викинуто: Що не є добре. У такому разі краще використовувати конструкції:
-
String result = null; if (stringOptional.isPresent()) { stringOptional.get(); }
В даному випадку ми перевіряємо, чи є елемент у Optional . Якщо ні — результуючий рядок має старе значення.
-
String result = stringOptional.orElse("default value");
У даному випадку ми вказуємо деяке значення за замовчуванням, яке буде задане результуючий рядок у разі порожнього Optional .
-
String result = stringOptional.orElseThrow(() -> new CustomException());
В даному випадку ми самі викидаємо виняток при порожньому Optional .
18. Чи можна оголошувати main method як final?
Так, безперечно, ніщо не заважає нам оголосити метод main() як final . Компілятор не випустить помилок. Але варто пам'ятати, що будь-який метод після оголошення його як final стане останнім методом – не перевизначуваним. Хоча, хто перевизначатиме main ???19. Чи можна імпортувати ті самі package/class двічі? Які можуть бути наслідки?
Так можна. Наслідки? Ми матимемо пару непотрібних імпортів, які Intelijj IDEA буде відображати як сірі, тобто. не використовуються.20. Що таке Casting? Коли ми можемо отримати виняток ClassCastException?
Casting, або приведення типів - це процес перетворення одного типу даних на інший тип даних: вручну (неявне приведення) або автоматично (явне приведення типів). Автоматичне перетворення виконує компілятор, а ручне розробник. Приведення типів для примітивів та класів дещо відрізняється, тому й розглянемо їх окремо. Примітивні типи Приклад автоматичного приведення примітивних типів: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();
Ви і отримаєте помилку: Насправді, ви можете класу спадкоємцю 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 винятків – це ще той спагетті код, який повсюдно повторюється, при цьому не завжди дійсно потрібен. У таких випадках легше зробити обробку всередині фреймворку, щоб зайвий раз не перекладати це на плечі розробників. Так, безперечно, аварійна ситуація може виникнути, але ці самі 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.*;
23. Який зв'язок між методами hashCode() та equals()?
Відповідно до Oracle існує таке правило: Якщо два об'єкти рівні (тобто метод equals() повертає true ), у них повинен бути однаковий хеш-код. При цьому не варто забувати, що однаковий хеш-код може бути у двох різних об'єктів. Щоб розібратися, чому ж equals() і hashCode() перевизначають завжди в парі, розглянемо наступні випадки:-
Обидва методи перевизначені.
У такому разі два різних об'єкти з однаковими внутрішніми станами повертатимуть при equals() — true , тоді як і hashCode() буде в обох повертати те саме число.
Виходить все окей, бо правило виконується.
-
Обидва методи не перевизначені.
У такому разі два різні об'єкти з однаковими внутрішніми станами при equals() повертатимуть false , оскільки порівняння йде за посиланням через оператор == .
Метод hashCode() також поверне різні значення (швидше за все), оскільки він видає перетворене значення адресаи осередку пам'яті. Але в одного і того ж об'єкта це значення буде однаковим, як і equals() в даному випадку поверне true тільки коли посилання вказують на той самий об'єкт.
Виходить, і в цьому випадку все окей і правило виконується.
-
Перевизначено equals() , не перевизначено hashCode() .
У такому разі для двох різних об'єктів з однаковими внутрішніми станами equals() повертатиме true , а hashCode() повертатиме (швидше за все) різні значення.
Відбувається порушення правила, тому робити так не рекомендується.
-
Чи не перевизначений equals() , перевизначений hashCode() .
У такому разі для двох різних об'єктів з однаковими внутрішніми станами equals() повертатиме false , а 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 .
-
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) — повертає незмінне уявлення переданого списку.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ