JavaRush /Java блог /Random UA /Топ-50 Java Core питань та відповідей на співбесіді. Част...
Roman Beekeeper
35 рівень

Топ-50 Java Core питань та відповідей на співбесіді. Частина 2

Стаття з групи Random UA
Топ-50 Java Core питань та відповідей на співбесіді. Частина 1 Топ-50 Java Core питань та відповідей на співбесіді.  Частина 2 - 1

Collections

25. Що мають на увазі під Collections в Java?

Collection – це фреймворк, створений для збереження та маніпуляції об'єктами. Використовується для виконання таких операцій:
  • пошук;
  • сортування;
  • маніпуляція;
  • додавання;
  • видалення.
Усі класи та інтерфейси для Collection фреймворку знаходяться у java.utilпакеті.

26. Які класи та інтерфейси доступні у Collection фреймворку?

Інтерфейси:
  • Collection;
  • List;
  • Set;
  • Map;
  • Sorted Set;
  • Sorted Map;
  • Queue.
Класи:
  • Lists:
    1. ArrayList;
    2. LinkedList;
    3. Vector(deprecated).
  • Sets:
    1. HashSet;
    2. LinkedHashSet;
    3. TreeSet.
  • Maps:
    1. HashMap
    2. TreeMap
    3. HashTable (deprecated)
    4. LinkedHashMap
  • Queue
    1. 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:

Це колекція, де кожен елемент має посилання на попередній і наступний елементи. За цими посиланнями можна переходити від одного елемента до іншого. При додаванні елемента просто змінюються посилання на попередній та наступний елементи: Топ-50 Java Core питань та відповідей на співбесіді.  Частина 2 - 2
  • елементи пов'язані один з одним, тобто реалізовано двозв'язковий перелік;
  • загальна швидкість роботи помітно нижче, ніж у 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:

Реалізація карти, яка зберігає записи відсортованими відповідно до природного порядку їх ключів або, що краще, з використанням компаратора, якщо він надається в конструкторі при створенні карти. Приклад:
  1. Без компаратора

    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}
  2. З компаратором

    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}
Бачимо, що стандартно реалізовано сортування у порядку зростання, але це можна змінити, додавши компаратор у конструктор. Добре описана TreeMap тут .

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. Це виняткова ситуація, яка виникає з якихось причин. Діаграма успадкування винятків виглядає так (потрібно знати її назубок ;) ): Топ-50 Java Core питань та відповідей на співбесіді.  Частина 2 - 3На діаграмі видно, що загалом усі винятки поділяються на дві групи – exceptions та error. Error — використовуються JVM для відображення помилок, після яких робота програми вже не має сенсу. Наприклад, StackOverFlowError, яка говорить, що стек заповнений і програма вже не може працювати. Exception- Винятки, які генеруються програмно в коді. Є різні винятки, що перевіряються та неперевірювані, але головне, що вони є, і їх можна перехопити та продовжити роботу програми. Exceptions, у свою чергу, ще поділяються на тих, хто успадковується від RuntimeException та інших спадкоємців Exception. В рамках цього питання інформації достатньо. Про те, що таке виключення, що перевіряються / неперевірені, поговоримо нижче.

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
Важливо, що catch, throw і throws можна використовувати лише з java.lang.Throwable. З іншими типами працювати не буде. Зараз обговоримо саме try, catch та 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, але правила хорошого тону говорять про інше. Якщо це перевіряються, то використовуючи метод, який їх генерує, потрібно якось його обробити. Є два варіанта:
  1. Написати try-catchз відповідним і вище за успадкуванням винятком.
  2. Використовувати throwsтак само, щоб ця проблема була вже в когось іншого :D

35. Checked і Unchecked винятки Java

B Java є два типи винятків - checked і unchecked.

Checked винятки:

Це винятки, що перевіряються під час компіляції. Якщо якийсь код у методі під час виключення видає checked виняток, метод зобов'язаний або обробити його за допомогою , або try-catchпрокинути його далі. (для нас це не важливо) і зберігає її назад.
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 є неперевіреними. Топ-50 Java Core питань та відповідей на співбесіді.  Частина 2 - 4Розглянемо таку Java-програму. Код чудово компілюється, але при запуску видає виняток 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
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ