Замальовка 1. "Здавалося б простий метод"
Напишіть, як би ви реалізували метод, що повертає результат поділу числа а на число b Співрозмовний пише на листкуint divide(int a, int b) {
}
*Я недовірливо зиркнув на листок із сигнатурою методу. У чому каверза?* Пишу:
int divide(int a, int b) {
return a/b;
}
А якісь проблеми можуть бути з цим методом? *Ловлю дииииикий тупняк* Начебто ні.. Далі слідує законне питання: А якщо b = 0? *Воу, мене зараз виженуть з цього кабінету, якщо продовжу в тому ж дусі!* Ах так, зрозуміло. Тут у нас аргументи типу int, тому буде викинуто Arithmetic Exception. Якщо б аргументи були типу float або double, вийшла б Infinity. А що з цим робитимемо? Починаю писати try/catch
int divide(int a, int b) {
try {
return a/b;
} catch (Exception e) {
e.printStackTrace();
return ... // ??? what the hack?
}
}
*Доходжу до return і підвисаю: адже щось треба повернути у разі помилки. Але як це “щось” відрізнити від результату обчислення?* А що повертатимемо? Гм… Я б змінив тип змінної, що повертається на Integer і в разі ексепшна повертав би null. Уявімо, що не можемо змінювати тип. Чи можемо якось викрутитися? Може, можемо ще щось зробити з ексепшном? *Тут дійшло* Можемо ще прокинути у зухвалий метод! Правильно. Як він виглядатиме?
int divide(int a, int b) throws ArithmeticException{
return a/b;
}
void callDivide(int a, int b) {
try {
divide(a, b);
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
А обробляти виняток обов'язково? Так, ми явно прокидаємо його з методу divide. (*Тут я помабовся! Далі слідують навідні питання співбесідуючого, щоб прийти до правильної відповіді*) А Arithmetic Exception - це який виняток – checked чи unchecked? Це Runtime exception, означає unchecked. *Тут слідує вбивче питання* Тобто виходить, за Вашими словами, якщо ми в сигнатурі методу вказали throws Arithmetic Exception, то воно стало checked винятком? * Ух е! * Напевно ... ні. Так, не стало. Якщо ми вказали в сигнатурі throws /unchecked exception/ ми всього лише попереджаємо, що метод може викидати виняток, але обробляти його в методі, що викликає, не обов'язково. Із цим розібралися. А ще щось можемо зробити, щоб уникнути помилок? * Після деяких роздумів * Так, можемо ще перевіряти, if (b = = 0). І виконувати якусь логіку. Правильно. Таким чином, ми можемо піти трьома шляхами:
- try/catch
- throws - прокид у викликальний метод
- перевірка аргументів
divide
яким із методів Вам здається кращим? Я вибрав прокид виключення в зухвалий спосіб, т.к. У методі divide не зрозуміло, як цей ексепшн обробити і який результат типу int
повернути у разі помилки. А в зухвалому методі використав би перевірку аргументу на рівність нулю. Начебто така відповідь задовольнила співбесідуючого, але якщо чесно, не впевнений, що ця відповідь однозначна))
Зарисовка 2. "Хто швидше?"
Після стандартного питання, чим ArrayList відрізняється від LinkedList, був такий: Що відбудеться швидше - вставка елемента в серединуArrayList
або в середину LinkedList
? *Тут мене перемкнуло, я згадав, що скрізь читав щось на кшталт "використовуйте LinkedList
для вставки або видалення елементів у середину списку". Удома навіть перевіряв ще раз по лекціях JavaRush, там фраза: “якщо ти збираєшся вставляти (або видаляти) в середину колекції багато елементів, то тобі краще використовувати LinkedList
. У решті випадків – ArrayList
.” Відповів на автоматі* Швидше буде з LinkedList
. Поясніть будь ласка
- Для того щоб вставити елемент у середину
ArrayList
, ми за константний час знаходимо елемент у списку, а потім перераховуємо індекси елементів праворуч від лінійного часу, що вставляється. - Для
LinkedList
.. Ми спочатку за лінійний час доходимо до середини, а потім за константний час вставляємо елемент, змінюючи для сусідніх елементів посилання.
LinkedList
ж таки швидше? Виходить, що колись вставляємо в першу половину списку. Наприклад, якщо вставляти в самий початок, ArrayList
доведеться перерахувати всі індекси до самого хвоста, а LinkedList
всього лише поміняти посилання у першого елемента. Мораль: не вірте дослівно всьому, що написано, навіть на JavaRush!)
Замальовка 3. “Куди ж без equals та hashcode!”
Розмова про equals і hashcode була дуже довга – як перевизначаємо, яка реалізація вObject
, що відбувається під капотом, коли вставляється елемент HashMap
і т.д. Наведу лише низку цікавих на мій погляд моментів* Уявіть, що ми створабо клас
public class A {
int id;
public A(int id) {
this.id = id;
}
}
І не перевизначабо equals
і hashcode
. Опишіть, що станеться під час виконання коду
A a1 = new A(1);
A a2 = new A(1);
Map<A, String> hash = new HashMap<>();
hash.put(a1, "1");
hash.get(a2);
*Добре що перед співбесідою спеціально приділив пару днів на розуміння основних алгоритмів, їх складності та структур даних – дуже допомогло, дякую CS50!*
-
Створюємо два екземпляри класу A
-
Створюємо порожню карту, яка за замовчуванням має 16 кошиків. Як ключ виступає об'єкт класу A, в якому не перевизначені методи
equals
іhashcode
. -
Кладемо
a1
в карту. Для цього спочатку обчислюємо хешa1
.Чому дорівнюватиме хеш?
Адреса осередку в пам'яті – це реалізація методу з класу
Object
-
Виходячи з хеша, обчислюємо індекс корзини.
А як ми можемо його вирахувати?
*Тут на жаль я не дав зрозумілу відповідь. У вас є довге число - хеш, і є 16 кошиків - як визначити індекс, щоб об'єкти з різним хеш рівномірно розподілялися по кошиках? Я міг би припустити, що індекс обчислюється так:
int index = hash % buckets.length
Вже вдома подивився, що оригінальна реалізація у вихідниках дещо відрізняється.
static int indexFor(int h, int length) { return h & (length - 1); }
-
Перевіряємо, що не сталося колізій та вставляємо a1.
-
Переходимо до методу
get
. Примірники a1 і a2 гарантовано мають різнуhash
(різну адресау в пам'яті), тому ми нічого не знайдемо за цим ключемА якщо перевизначимо тільки
hashcode
в класі A і спробуємо вставити в хешмап спочатку пару з ключем a1, а потім a2?Тоді спочатку знайдемо потрібний кошик
hashcode
- ця операція буде виконана коректно. Далі почнемо йти по об'єктахEntry
у прикріпленому до кошика ЛінкедЛисту та порівнювати ключі поequals
. Т.к.equals
не перевизначено, то береться базова реалізація з класуObject
– порівняння за посиланням. a1 і a2 гарантовано мають різні посилання, тому ми “промахнемося” повз вставлений елемент a1, і a2 буде поміщений в ЛінкедЛіст як новий вузл.Який висновок? Чи можна використовувати як ключ в
HashMap
об'єкт із неперевизначенимиequalshashcode
?Ні, не можна.
Замальовка 4. "Давайте навмисне зламаємо!"
Після запитань про Error і Exception виникло таке запитання: Напишіть простий приклад, коли функція викине StackOverflow. * Тут я згадав, як мене кумарив цей ерор, коли я намагався написати якусь рекурсивну функцію . *Далі я став щось мудрувати, в результаті співбесідующий допоміг, все виявилося просто*void sof() {
sof();
}
А чим цей ерор відрізняється від OutOfMemory
? *Тут я не відповів, вже потім зрозумів, що це було питання на знання Stack
та Heap
пам'яті Java (у Stack зберігаються виклики та посилання на об'єкти, а в Heap пам'яті – самі об'єкти). Відповідно, StackOverflow викидається, коли більше немає місця в Stack
пам'яті для чергового виклику методу, а OutOfMemory
місце під об'єкти закінчилося в Heap
пам'яті*
Ось такі моменти із співбесіди мені запам'яталися. На стажування в результаті мене взяли, так що попереду у мене 2,5 місяці навчання і, якщо все складеться добре, робота в компанії) Якщо буде цікаво, можу написати ще одну статейку, вже менше, з розбором простого, але показового завдання, яке мені дали на співбесіді до іншої компанії. На цьому у мене все, сподіваюся, комусь ця стаття допоможе поглибити або розставити свої знання по поличках. Усім приємне навчання!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ