JavaRush /Java блог /Random UA /Java Core. Запитання до співбесіди, ч. 1
Andrey
26 рівень

Java Core. Запитання до співбесіди, ч. 1

Стаття з групи Random UA
Для тих, хто вперше чує слово Java Core, це фундаментальні основи мови. З цими знаннями вже можна сміливо йти на стажування/інтернатуру. Java Core.  Запитання до співбесіди, ч. 1 - 1Наведені питання допоможуть вам освіжити знання перед співбесідою, або почерпнути щось нове. Для отримання практичних навичок займайтеся на JavaRush .
  1. Як створити незмінний об'єкт у Java? Перерахуйте всі переваги

    Незмінний клас – це клас, стан якого може бути змінено після створення. Тут станом об'єкта по суті вважаються значення, що зберігаються в екземплярі класу, будь то примітивні типи або типи посилань.

    Для того щоб зробити клас незмінним, необхідно виконати такі умови:

    1. Не надавайте сеттери або методи, які змінюють поля або об'єкти, що посилаються на поля. Сетери мають на увазі зміну стану об'єкта, а це те, чого ми хочемо тут уникнути.
    2. Зробіть всі поля finalта private. Поля, позначені private, будуть недоступними зовні класу, а позначення їх finalгарантує, що ви не зміните їх випадково.
    3. Не дозволяйте субкласам перевизначати методи. Найпростіший спосіб це зробити – оголосити клас як final. Фіналізовані класи Java не можуть бути перевизначені.
    4. Завжди пам'ятайте, що ваші екземпляри змінних можуть бути змінюваними або незмінними. Визначте їх та повертайте нові об'єкти з скопійованим вмістом для всіх змінних об'єктів (посилальні типи). Змінні змінні (примітивні типи) можуть бути безпечно повернені без додаткових зусиль.

    Також, вам необхідно пам'ятати наступні переваги незмінних класів. Можливо, вони знадобляться вас на співбесіді. Незмінні класи:

    • легко конструювати, тестувати та використовувати
    • автоматично потокобезпечні та не мають проблем синхронізації
    • не вимагають конструктора копіювання
    • дозволяють виконати «ліниву ініціалізацію» хешкода і кешувати значення, що повертається
    • не вимагають захищеного копіювання, коли використовуються як поле
    • роблять хороші Mapключі та Setелементи (ці об'єкти не повинні змінювати стан, коли знаходяться в колекції)
    • роблять свій клас постійним, одного разу створивши його, а він не потребує повторної перевірки
    • завжди мають «атомарність по відношенню до збою» (failure atomicity, термін застосував Джошуа Блох): якщо незмінний об'єкт кидає виняток, він ніколи не залишиться у небажаному чи невизначеному стані.

    Подивіться приклад, написаний у цьому пості .

  2. У Java передача за значенням чи за посиланням?

    Java специфікація свідчить, що це Java передається за значенням. Немає такого поняття, як «передача за посиланням» Java. Ці умови пов'язані з викликом методів і передачі змінних, як параметрів методу. Добре, примітивні типи завжди передаються за значенням без будь-якої плутанини. Проте, концепція має бути зрозумілою у контексті параметра методу складних типів.

    У Java, коли ми передає посилання складного типу як будь-який параметр методу, завжди адресаа пам'яті копіюється в нову змінну змінну крок за кроком. Подивіться на зображення:

    Java Core.  Запитання до співбесіди, ч. 1 - 2

    У наведеному прикладі, біти адресаи першого екземпляра копіюються інший посилальної змінної, в результаті чого обидві посилання вказують на одну ділянку пам'яті, де зберігається об'єкт. Пам'ятайте, що привласнивши друге посилання null, ви не надасте null першому посиланні. Але зміна стану об'єкта з однією змінною, що посилається, буде відображено і в іншому посиланні.

    Подробиці дивіться тут .

  3. Яке застосування блоку finally? Чи гарантує цей блок виконання свого коду? Коли finallyблок не викликається?

    Блок finallyзавжди викликається, якщо tryє блок. Це гарантує, що блок finallyвикликається навіть якщо трапляється несподіваний виняток. Але finallyє більш корисним, ніж просто для обробки винятків – цей блок дозволяє виконати чищення коду, який випадково обійшов через return, continueабо break. Розміщення коду, що очищає, в блок finallyзавжди є хорошою практикою, навіть коли не очікується жодних винятків.

    Якщо віртуальна машина завершує роботу під час виконання блоку tryабо catch, тоді блок finallyне буде виконано. Аналогічно, якщо нитка, виконуючи блок tryабо catch, буде перервана або вбита, блок finallyне буде виконаний, навіть не дивлячись на те, що програма продовжує працювати.

  4. Чому існує два класи Date, один в java.util packageінший в java.sql?

    java.util.Dateпредставляє дату та час, а java.sql.Dateпредставляє лише дату. Доповненням java.sql.Dateє клас java.sql.Time, який представляє тільки час.

    Клас java.sql.Dateє субкласом (розширенням) класу java.util.Date. Отже, що змінилося в java.sql.Date:

    • toString()формує інше уявлення рядка: yyyy-mm-dd
    • статичний метод valueOf(String)створює дату з рядка з вказаним вище поданням
    • виключені гетери та сеттери для годин, хвабон та секунд

    Клас java.sql.Dateвикористовується в JDBC і призначений, щоб не мати складову часу, тобто години, хвабони, секунди та мілісекунди повинні бути нульовими… але це не є обов'язковим для класу.

  5. Поясніть маркери.

    Шаблон інтерфейсу-маркера – це шаблон проектування в комп'ютерних науках, який використовується мовами програмування, які надають інформацію про об'єкти під час виконання . Це надає спосіб асоціації метаданих класу, де мова не має явної підтримки таких метаданих . Java для цього використовуються інтерфейси без вказівки методів.

    Хорошим прикладом застосування інтерфейсу-маркера Java є інтерфейс Serializable. Клас реалізує цей інтерфейс для вказівки, що його transientдані можуть бути записані в потік байтів або на файлову систему.

    Головною проблемою інтерфейсу-маркера є те, що інтерфейс визначає угоду для класів, що її реалізують, і ця угода успадковується всіма субкласами. Це означає, що ви не зможете "де-реалізувати" маркер. У наведеному прикладі, якщо ви створите субклас, який ви не хотіли б серіалізувати (можливо тому, що він перебуває в минущому (transient) стані), ви повинні вдатися до явного кидання NotSerializableException.

  6. Чому метод main()оголошено як public static void?

    Чому public? Метод mainмає модифікатор доступу public, тому він може бути доступний скрізь і для будь-якого об'єкта, який захоче використовувати цей метод для запуску програми. Тут я не кажу, що JDK/JRE мають подібну нагоду, оскільки java.exe або javaw.exe (для windows) використовують Java Native Interface (JNI) виклик для запуску методу, тому вони можуть викликати його в будь-якому випадку, незалежно від модифікатора доступу .

    Чому це? Давайте припустимо, що ми метод mainне статичний. Тепер для виклику будь-якого методу вам необхідний екземпляр класу. Правильно? Java дозволяє мати перевантажені конструктори, це ми всі знаємо. Тоді який із них має бути використаний, і звідки візьмуться параметри для перевантаженого конструктора?

    Чому void? Немає застосування для значення, що повертається у віртуальній машині, яка фактично викликає цей метод. Єдине, що програма захоче повідомити процесу, що викликав, - це нормальне або ненормальне завершення. Це вже можливо використовуючи System.exit(int). Чи не нульове значення має на увазі ненормальне завершення, інакше все в порядку.

  7. У чому різниця між створенням рядка як new()і літералом (за допомогою подвійних лапок)?

    Коли ми створюємо рядок, використовуючи new(), вона створюється в хіпі і також додається в пул рядків, у той же час рядок, створений за допомогою літералу, створюється тільки в пулі рядків.

    Вам необхідно ознайомитися з поняттям пула рядків глибше, щоб відповісти на це або подібні запитання. Моя порада - як слід вивчіть клас String і пул рядків .

    У нас у перекладах вже є гарна стаття про рядки та рядковий пул: Частина 1 , Частина 2 .
  8. Як працює метод substring()класу String?

    Як і в інших мовах програмування, рядки Java є послідовністю символів. Цей клас більше схожий на службовий клас для роботи з цією послідовністю. Послідовність символів забезпечується наступною змінною:

    /** The value is used for character storage. */
    /** Значение используется для хранения символов */
    private final char value[];
    Для доступа к этому массиву в различных сценариях используются следующие переменные/** The offset is the first index of the storage that is used. */
    /** Смещение – это первый индекс используемого хранабоща. */
    private final int offset;
    
    /** The count is the number of characters in the String. */
    /** Счет – это количество символов в строке. */
    private final int count;

    Щоразу, коли ми створюємо підрядок від існуючого екземпляра рядка, метод substring()лише встановлює нові значення змінних offsetта count. Внутрішній масив символів не змінюється. Це можливе джерело витоку пам'яті, якщо метод substring()використовувати необережно:

    Початкове значення value[]не змінюється. Тому якщо ви створите рядок довжиною 10000 символів і створите 100 підрядків з 5-10 символами в кожному, всі 101 об'єкти будуть містити один і той же символьний масив довжиною 10000 символів. Це без сумніву марнотратство пам'яті.

    Цього можна уникнути, змінивши код таким чином:

    замінити original.substring(beginIndex)на new String(original.substring(beginIndex)), де original– вихідний рядок.

    Примітка перекладача: я не можу сказати до якої версії Java це застосовно, але на даний момент у Java 7 цей пункт статті не актуальний. Метод substring()викликає конструктор класу new String(value, beginIndex, subLen), що у свою чергу звертається до методу Arrays.copyOfRange(value, offset, offset+count). Це означає, що у нас буде щоразу нове значення змінної value[], що містить наше нове кількість символів.
  9. Поясніть роботу HashMap. Як вирішено проблему дублікатів?

    Більшість з вас, напевно, погодиться, що HashMapнайбільш улюблена тема для дискусій на інтерв'ю в даний час. Якщо хтось попросить мене розповісти «Як працює HashMap?», я просто відповім: «За принципом хешування». Так просто, як це є.

    Отже, хешування по суті є способом призначити унікальний код будь-якої змінної/об'єкта після застосування будь-якої формули/алгоритму до своїх властивостей.

    Визначення картки ( Map) таке: «Об'єкт, який прив'язує ключі до значень». Дуже просто, правда? Отже, HashMapмістить власний внутрішній клас Entry, який має вигляд:

    static class Entry implements Map.Entry
    {
    final K key;
    V value;
    Entry next;
    final int hash;//More code goes here
    }

    Коли хтось намагається помістити пару ключ-значення в HashMap, відбувається таке:

    • Насамперед об'єкт ключа перевіряється на null. Якщо ключ null, значення зберігається у позицію table[0]. Тому що хешкод nullзавжди 0.
    • Потім, наступним кроком обчислюється хеш значення викликаючи у змінної ключа свій метод hashCode(). Цей хеш використовується для обчислення індексу в масиві для зберігання об'єкта Entry. Розробники JDK чудово розуміли, що метод hashCode()може бути погано написаний і може повертати дуже велике чи дуже маленьке значення. Для вирішення цієї проблеми вони ввели інший hash()метод і передають хешкод об'єкту цьому методу для приведення цього значення до діапазону розміру індексу масиву.
    • Тепер викликається метод indexFor(hash, table.length)для обчислення точної позиції для зберігання об'єкта Entry.
    • Наразі головна частина. Як ми знаємо, два неоднакові об'єкти можуть мати однакове значення хешкода, як два різні об'єкти зберігатимуться в однаковому розташуванні в архіві [називається кошиком]?

    Відповідь – LinkedList. Якщо пам'ятаєте, клас Entryмає властивість “next”. Ця властивість завжди вказує на наступний об'єкт у ланцюзі. Така поведінка дуже схожа на LinkedList.

    Отже, у разі збігів хешкод, об'єкти Entry зберігаються у формі LinkedList. Коли об'єкт Entryнеобхідно розмістити на конкретному індексі, HashMapперевіряє, чи існує на цьому місці інший об'єкт Entry? Якщо там немає запису, наш об'єкт збережеться у цьому місці.

    Якщо в нашому індексі вже знаходиться інший об'єкт, перевіряється його поле next. Якщо воно рівне null, наш об'єкт стає наступним вузлом в LinkedList. Якщо next не дорівнює null, ця процедура повторюється, доки знайдено поле nextрівне null.

    Що буде, якщо ми додамо інше значення ключа, що дорівнює доданому раніше? Логічно, що вона має замінити старе значення. Як це відбувається? Після визначення індексу позиції для об'єкта Entry, пробігаючи по LinkedListрозташованому на нашому індексі, HashMapвикликає метод equals()для значення ключа для кожного об'єкта Entry. Всі ці об'єкти Entryмають LinkedListоднакове значення хешкода, але метод equals()перевірятиме на справжню рівність. Якщо ключ. equals(k)буде true , тоді обидва сприйматимуться як однаковий об'єкт. Це викликає заміну лише об'єкта-значення всередині об'єкта Entry.

    У такий спосіб HashMapзабезпечує унікальність ключів.

  10. Відмінності між інтерфейсами та абстрактними класами?

    Це дуже поширене питання, якщо ви проходите співбесіду з програмістом рівня junior. Найбільш значущі відмінності наведені нижче:

    • В інтерфейсах Java змінні апріорі final. Абстрактні класи можуть містити не finalзмінні.
    • Інтерфейс Java беззастережно не може мати реалізації. Абстрактний клас може мати екземпляри методів, що реалізують базову поведінку.
    • Складові інтерфейсу повинні бути public. Анотація клас може мати модифікатори доступу на будь-який смак.
    • Інтерфейс має бути реалізований ключовим словом implements. Абстрактний клас має бути розширений за допомогою ключового слова extends .
    • Java клас може реалізовувати безліч інтерфейсів, але може успадковуватися тільки від одного абстрактного класу.
    • Інтерфейс повністю абстрактний і не може мати екземплярів. Абстрактний клас також може мати примірників класу, але може бути викликаний, якщо існує метод main().
    • Абстрактний клас трохи швидше за інтерфейс, тому що інтерфейс передбачає пошук перед викликом будь-якого перевизначеного методу в Java. У більшості випадків це незначна відмінність, але якщо ви пишите критичний час додаток, вам необхідно врахувати і цей факт.
  11. Коли ви перевизначаєте методи hashCode()та equals()?

    Методи hashCode()і equals()визначені у класу Object, який є батьківським класом для всіх об'єктів Java. З цієї причини всі об'єкти Java успадковують базову реалізацію цих методів.

    Метод hashCode()використовується для отримання унікального значення integer для цього об'єкта. Це значення використовується визначення розташування кошика, коли об'єкт необхідно зберігати у структурі даних на кшталт HashTable. За замовчуванням метод hashCode()повертає ціле чисельне подання адресаи пам'яті, де зберігається об'єкт.

    Метод equals(), як передбачає назву, використовується для простої еквівалентності об'єктів. Базова реалізація методу полягає у перевірці посилань двох об'єктів для перевірки їхньої еквівалентності.

    Зверніть увагу, що зазвичай необхідно перевизначати метод hashCode()щоразу, коли перевизначено метод equals(). Це необхідно для підтримки загальної угоди методу hashCode, в якому говориться, що рівні об'єкти повинні мати рівні хешкоди.

    Метод equals() повинен визначати рівність відносин (він має бути зворотним, симетричним та транзитивним). На додаток, він повинен бути стійким (якщо об'єкт не змінювався, метод повинен повертати те саме значення). Крім того, o.equals(null)завжди має повертати false .

    hashCode()повинен бути також стійким (якщо об'єкт не змінювався за умовами методу equals(), він повинен продовжувати повертати те саме значення.

    Відношення між двома методами таке: завжди, якщо a.equals(b), тоді a.hashCode()має бути таким самим, як і b.hashCode().

Удачі у навчанні!! Автор статті Lokesh Gupta Посилання на інші частини: Java Core. Запитання до співбесіди, ч. 2 Java Core. Запитання до співбесіди, ч. 3
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ