Перейти к основному содержимому

Вопросы для собеседования по Java

· 18 мин. чтения

Оглавление

1. Введение

Эта статья содержит ответы на некоторые из наиболее важных вопросов собеседования о базовой Java. Ответы на некоторые из них могут быть неочевидными, поэтому эта статья поможет разобраться.

2. Основные вопросы по Java для начинающих

Q1. Передаются ли данные по ссылке или по значению в Java?

Хотя ответ на этот вопрос довольно прост, этот вопрос может сбить с толку новичков. Во-первых, давайте уточним, о чем вопрос:

  1. Передача по значению — означает, что мы передаем копию объекта в качестве параметра в метод.
  2. Передача по ссылке — означает, что мы передаем ссылку на объект в качестве параметра в метод.

Чтобы ответить на этот вопрос, мы должны проанализировать два случая. Они представляют два типа данных, которые мы можем передать методу: примитив и объект.

Когда мы передаем примитивы в метод, их значение копируется в новую переменную. Когда дело доходит до объектов, значение ссылки копируется в новую переменную. Таким образом, мы можем сказать, что Java — язык строгой передачи по значению .

Мы можем узнать больше об этом в одной из наших статей: Pass-By-Value as a Parameter Passing Mechanism in Java .

Q2. В чем разница между импортом и статическим импортом?

Мы можем использовать обычный импорт для импорта определенного класса или всех классов, определенных в другом пакете:

import java.util.ArrayList; //specific class
import java.util.*; //all classes in util package

Мы также можем использовать их для импорта публичных вложенных классов включающего класса:

import com.foreach.A.*

Однако мы должны знать, что приведенный выше импорт не импортирует сам класс A.

Есть также статические импорты, которые позволяют нам импортировать статические члены или вложенные классы:

import static java.util.Collections.EMPTY_LIST;

В результате мы можем использовать статическую переменную EMPTY_LIST без добавления полного имени класса, т. е. как если бы она была объявлена в текущем классе.

Q3. Какие модификаторы доступа доступны в Java и какова их цель?

В Java есть четыре модификатора доступа :

  1. частный
  2. по умолчанию (пакет)
  3. защищенный
  4. публичный

Модификатор private гарантирует, что члены класса не будут доступны за пределами класса. Его можно применять к методам, свойствам, конструкторам, вложенным классам, но не к самим классам верхнего уровня.

В отличие от частного модификатора, мы можем применить модификатор по умолчанию ко всем типам членов класса и к самому классу. Мы можем применить видимость по умолчанию , вообще не добавляя никаких модификаторов доступа. Если мы используем видимость по умолчанию , наш класс или его члены будут доступны только внутри пакета нашего класса. Мы должны иметь в виду, что модификатор доступа по умолчанию не имеет ничего общего с ключевым словом по умолчанию .

Подобно модификатору по умолчанию , все классы в одном пакете могут получить доступ к защищенным членам. Более того, модификатор protected позволяет подклассам обращаться к защищенным членам суперкласса, даже если они не находятся в одном пакете. Мы не можем применить этот модификатор доступа к классам, только к членам класса.

Модификатор public можно использовать вместе с ключевым словом class и всеми членами класса. Это делает классы и члены классов доступными во всех пакетах и для всех классов.

Мы можем узнать больше в статье Модификаторы доступа Java .

Q4. Какие другие модификаторы доступны в Java и какова их цель?

В Java доступны еще пять модификаторов:

  • статический
  • окончательный
  • Аннотация
  • синхронизированный
  • изменчивый

Они не контролируют видимость.

Прежде всего, мы можем применить ключевое слово static к полям и методам. Статические поля или методы являются членами класса, а нестатические — членами объекта . Членам класса не нужен экземпляр для вызова. Они вызываются с именем класса вместо имени ссылки на объект. В этой статье более подробно рассматривается ключевое слово static .

Затем у нас есть последнее ключевое слово. Мы можем использовать его с полями, методами и классами. Когда final используется для поля, это означает, что ссылка на поле не может быть изменена. Поэтому его нельзя переназначить другому объекту. Когда final применяется к классу или методу, это гарантирует, что этот класс или метод не может быть расширен или переопределен. Последнее ключевое слово более подробно объясняется в этой статье .

Следующее ключевое слово — абстрактное . Это может описывать классы и методы. Когда классы являются абстрактными , они не могут быть созданы. Вместо этого они должны быть подклассами. Когда методы являются абстрактными , они остаются без реализации и могут быть переопределены в подклассах.

Синхронизированное ключевое слово может быть самым сложным . Мы можем использовать его с экземпляром, а также со статическими методами и блоками кода. Когда мы используем это ключевое слово, мы заставляем Java использовать блокировку монитора для обеспечения синхронизации данного фрагмента кода. Более подробную информацию о синхронизированных можно найти в этой статье .

Последнее ключевое слово, которое мы собираемся обсудить, — volatile . Мы можем использовать его только вместе с полями экземпляра. Он объявляет, что значение поля должно быть прочитано и записано в основную память, минуя кеш процессора. Все операции чтения и записи для volatile-переменной являются атомарными. Ключевое слово volatile подробно описано в этой статье .

Q5. В чем разница между JDK, JRE и JVM?

JDK означает Java Development Kit , набор инструментов, необходимых разработчикам для написания приложений на Java. Существует три типа сред JDK:

  • Standard Edition - комплект разработчика для создания портативных настольных или серверных приложений.
  • Enterprise Edition - расширение Standard Edition с поддержкой распределенных вычислений или веб-сервисов.
  • Micro Edition — платформа для разработки встраиваемых и мобильных приложений.

В JDK включено множество инструментов, которые помогают программистам писать, отлаживать или поддерживать приложения . Самые популярные из них — это компилятор ( javac ), интерпретатор ( java ), архиватор ( jar ) и генератор документации ( javadoc ).

JRE — это среда выполнения Java . Это часть JDK, но он содержит минимальную функциональность для запуска приложений Java . Он состоит из виртуальной машины Java , базовых классов и вспомогательных файлов. Например, у него нет компилятора.

JVM — это аббревиатура от Java Virtual Machine , которая представляет собой виртуальную машину, способную запускать программы, скомпилированные в байт-код. Это описано в спецификации JVM, так как важно обеспечить совместимость между различными реализациями. Наиболее важной функцией JVM является предоставление пользователям возможности развертывать одно и то же Java-приложение в различных операционных системах и средах, не беспокоясь о том, что скрывается за ним .

Для получения дополнительной информации давайте проверим статью « Разница между JVM, JRE и JDK ».

Q6. В чем разница между стеком и кучей?

Есть две части памяти, где все переменные и объекты хранятся JVM. Первый — это стек, а второй — куча .

Стек — это место , где JVM резервирует блоки для локальных переменных и дополнительных данных . Стек представляет собой структуру LIFO (последний пришел — первый ушел). Это означает, что при каждом вызове метода новый блок резервируется для локальных переменных и ссылок на объекты. Каждый вызов нового метода резервирует следующий блок. Когда методы завершают свое выполнение, блоки освобождаются в порядке, обратном их запуску.

Каждый новый поток имеет свой собственный стек.

Мы должны знать, что в стеке гораздо меньше места в памяти, чем в куче. И когда стек заполнен, JVM выдаст StackOverflowError . Это может произойти, когда есть плохой рекурсивный вызов, и рекурсия идет слишком глубоко.

Каждый новый объект создается в куче Java , которая используется для динамического распределения . Существует сборщик мусора, который отвечает за стирание неиспользуемых объектов, которые делятся на молодые (питомники) и старые пространства. Доступ памяти к куче медленнее, чем доступ к стеку. JVM выдает ошибку OutOfMemoryError , когда куча заполнена.

Мы можем найти более подробную информацию в статье Память стека и пространство кучи в Java .

Q7. В чем разница между интерфейсами Comparable и Comparator ?

Иногда, когда мы пишем новый класс, мы хотели бы иметь возможность сравнивать объекты этого класса. Это особенно полезно, когда мы хотим использовать отсортированные коллекции. Это можно сделать двумя способами: с помощью интерфейса Comparable или с помощью интерфейса Comparator .

Во-первых, давайте посмотрим на интерфейс Comparable :

public interface Comparable<T> {
int compareTo(T var1);
}

Мы должны реализовать этот интерфейс с помощью класса, объекты которого мы хотим отсортировать.

Он имеет метод compareTo() и возвращает целое число. Он может возвращать три значения: -1, 0 и 1, что означает, что этот объект меньше, равен или больше сравниваемого объекта.

Стоит отметить, что переопределенный метод compareT0() должен быть согласован с методом equals() .

С другой стороны, мы можем использовать интерфейс Comparator . Его можно передать методам sort() интерфейса Collection или при создании отсортированных коллекций. Вот почему он в основном используется для создания одноразовой стратегии сортировки.

Более того, это также полезно, когда мы используем сторонний класс, который не реализует интерфейс Comparable.

Как и метод compareTo() , переопределенные методы compare() должны быть согласованы с методом equals() , но они могут опционально разрешать сравнение с нулевыми значениями.

Давайте посетим статью Comparator and Comparable in Java для получения дополнительной информации.

Q8. Что такое тип void и когда мы его используем?

Каждый раз, когда мы пишем метод на Java, он должен иметь возвращаемый тип. Если мы хотим, чтобы метод не возвращал значения, мы можем использовать ключевое слово void .

Мы также должны знать, что существует класс Void . Это класс-заполнитель, который можно использовать, например, при работе с дженериками. Класс Void не может быть ни создан, ни расширен.

Q9. Что такое методы класса объекта и что они делают?

Важно знать, какие методы содержит класс Object и как они работают. Это также очень полезно, когда мы хотим переопределить эти методы:

  • clone() — возвращает копию этого объекта
  • equals() — возвращает true , когда этот объект равен объекту, переданному в качестве параметра
  • finalize() — сборщик мусора вызывает этот метод во время очистки памяти.
  • getClass() — возвращает класс времени выполнения этого объекта.
  • hashCode() — возвращает хеш-код этого объекта. Мы должны знать, что это должно быть согласовано с методом equals()
  • notify () — отправляет уведомление одному потоку, ожидающему монитор объекта.
  • notifyAll () — отправляет уведомление всем потокам, ожидающим монитор объекта.
  • toString() — возвращает строковое представление этого объекта
  • wait() — существует три перегруженных версии этого метода. Это заставляет текущий поток ждать указанное количество времени, пока другой поток не вызовет notify( ) или notifyAll() для этого объекта.

Q10. Что такое Enum и как мы можем его использовать?

Enum — это тип класса, который позволяет разработчикам указывать набор предопределенных постоянных значений. Чтобы создать такой класс, мы должны использовать ключевое слово enum . Представим перечисление дней недели:

public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

Для перебора всех констант мы можем использовать метод static values() . Более того, перечисления позволяют нам определять члены, такие как свойства и методы, такие как обычные классы.

Хотя это особый тип класса, мы не можем создать подкласс. Однако перечисление может реализовывать интерфейс.

Еще одним интересным преимуществом Enum является то, что они потокобезопасны и поэтому широко используются как синглтоны.

Мы можем найти больше о Enums в одном из наших руководств .

Q11. Что такое JAR ?

JAR — это ярлык для архива Java . Это архивный файл, упакованный в формате ZIP. Мы можем использовать его для включения файлов классов и вспомогательных ресурсов, необходимых для приложений. Он имеет много особенностей:

  • Безопасность — мы можем подписывать файлы JAR цифровой подписью
  • Сжатие — при использовании JAR мы можем сжимать файлы для эффективного хранения.
  • Портативность — мы можем использовать один и тот же файл JAR на разных платформах.
  • Управление версиями — файлы JAR могут содержать метаданные о содержащихся в них файлах.
  • Запечатывание — мы можем запечатать пакет в файле JAR. Это означает, что все классы из одного пакета должны быть включены в один и тот же файл JAR.
  • Расширения — мы можем использовать формат файла JAR для упаковки модулей или расширений для существующего программного обеспечения.

Q12. Что такое исключение NullPointerException ?

NullPointerException , пожалуй, самое распространенное исключение в мире Java. Это непроверенное исключение и, таким образом, расширяет RuntimeException . Мы не должны пытаться справиться с этим.

Это исключение возникает, когда мы пытаемся получить доступ к переменной или вызвать метод нулевой ссылки, например, когда:

  • вызов метода нулевой ссылки
  • установка или получение поля нулевой ссылки
  • проверка длины нулевой ссылки на массив
  • установка или получение элемента нулевой ссылки на массив
  • выбрасывание нуля

Q13. Какие два типа приведения в Java? Какое исключение может быть выброшено при кастинге? Как мы можем этого избежать?

Мы можем различать два типа приведения типов в Java. Мы можем выполнять повышающее приведение, которое приводит объект к супертипу, или понижающее приведение, которое приводит объект к подтипу.

Upcasting очень прост, так как мы всегда можем это сделать. Например, мы можем преобразовать экземпляр String в тип Object :

Object str = "string";

В качестве альтернативы мы можем преобразовать переменную вниз . Это не так безопасно, как повышение класса, поскольку включает проверку типа. Если мы неправильно приведем объект, JVM выдаст ClassCastExcpetion во время выполнения. К счастью, мы можем использовать ключевое слово instanceof для предотвращения недопустимого приведения:

Object o = "string";
String str = (String) o; // it's ok

Object o2 = new Object();
String str2 = (String) o2; // ClassCastException will be thrown

if (o2 instanceof String) { // returns false
String str3 = (String) o2;
}

Подробнее о приведении типов мы можем узнать в этой статье .

3. Основные вопросы по Java для продвинутых программистов

Q1. Почему String является неизменяемым классом?

Мы должны знать, что объекты String обрабатываются JVM не так, как другие объекты . Одно отличие состоит в том, что объекты String неизменяемы. Это означает, что мы не можем изменить их после того, как мы их создали. Есть несколько причин, почему они так себя ведут:

  1. Они хранятся в пуле строк, который является специальной частью памяти кучи. Он отвечает за экономию места.
  2. Неизменность класса String гарантирует, что его хеш-код не изменится. В связи с этим строки можно эффективно использовать в качестве ключей при хешировании коллекций. Мы можем быть уверены, что не перезапишем данные из-за изменения хэш-кодов.
  3. Их можно безопасно использовать в нескольких потоках. Ни один поток не может изменить значение объекта String , поэтому мы бесплатно получаем потокобезопасность.
  4. Строки неизменяемы, чтобы избежать серьезных проблем с безопасностью. Конфиденциальные данные, такие как пароли, могут быть изменены ненадежным источником или другим потоком.

Мы можем узнать больше о неизменяемости строк в этой статье .

Q2. В чем разница между динамической привязкой и статической привязкой?

Связывание в Java — это процесс связывания вызова метода с соответствующим телом метода. В Java можно выделить два типа привязки: статическое и динамическое.

Основное различие между статической привязкой и динамической привязкой заключается в том, что статическая привязка выполняется во время компиляции, а динамическая — во время выполнения.

Статическая привязка использует информацию о классе для привязки. Он отвечает за разрешение членов класса, которые являются закрытыми или статическими , а также окончательными методами и переменными. Кроме того, статическая привязка связывает перегруженные методы.

Динамическая привязка , с другой стороны, использует информацию об объекте для разрешения привязок. Вот почему он отвечает за разрешение виртуальных и переопределенных методов.

Q3. Что такое JIT?

JIT означает «точно вовремя». Это компонент JRE, который работает во время выполнения и повышает производительность приложения. В частности, это компилятор, который запускается сразу после запуска программы.

Это отличается от обычного компилятора Java, который компилирует код задолго до запуска приложения. JIT может ускорить работу приложения разными способами.

Например, компилятор JIT отвечает за компиляцию байт-кода в собственные инструкции на лету для повышения производительности. Кроме того, он может оптимизировать код для целевого процессора и операционной системы.

Кроме того, он имеет доступ ко многим статистическим данным времени выполнения, которые можно использовать для перекомпиляции для достижения оптимальной производительности. При этом он также может выполнять некоторую глобальную оптимизацию кода или переупорядочивать код для лучшего использования кеша.

Q4. Что такое отражение в Java?

Отражение — очень мощный механизм в Java. Отражение — это механизм языка Java, который позволяет программистам проверять или изменять внутреннее состояние программы (свойства, методы, классы и т. д.) во время выполнения. Пакет java.lang.reflect предоставляет все необходимые компоненты для использования отражения.

При использовании этой функции мы можем получить доступ ко всем возможным полям, методам, конструкторам, включенным в определение класса. Мы можем получить к ним доступ независимо от их модификатора доступа. Это означает, что, например, мы можем получить доступ к закрытым членам. Для этого нам не обязательно знать их имена. Все, что нам нужно сделать, это использовать некоторые статические методы класса .

Стоит знать, что есть возможность ограничить доступ через отражение. Для этого мы можем использовать диспетчер безопасности Java и файл политики безопасности Java. Они позволяют нам предоставлять разрешения классам.

При работе с модулями начиная с Java 9 мы должны знать, что по умолчанию мы не можем использовать отражение для классов, импортированных из другого модуля. Чтобы разрешить другим классам использовать отражение для доступа к закрытым членам пакета, мы должны предоставить разрешение «Отражение».

В этой статье более подробно рассматривается Java Reflection.

Q5. Что такое загрузчик классов?

Загрузчик классов — один из важнейших компонентов Java. Это часть JRE.

Проще говоря, загрузчик классов отвечает за загрузку классов в JVM. Мы можем выделить три типа загрузчиков классов:

  • Загрузчик классов Bootstrap — он загружает основные классы Java. Они расположены в каталоге <JAVA_HOME>/jre/lib .
  • Загрузчик классов расширения — он загружает классы, расположенные в <JAVA_HOME>/jre/lib/ext или по пути, определенному свойством java.ext.dirs.
  • Системный загрузчик классов — он загружает классы в путь к классам нашего приложения.

Загрузчик классов загружает классы «по запросу». Это означает, что классы загружаются после их вызова программой. Более того, загрузчик классов может загрузить класс с заданным именем только один раз. Однако если один и тот же класс загружается двумя разными загрузчиками классов, эти классы не проходят проверку на равенство.

Дополнительные сведения о загрузчиках классов см. в статье « Загрузчики классов в Java ».

Q6. В чем разница между статической и динамической загрузкой классов?

Статическая загрузка классов происходит, когда у нас есть исходные классы, доступные во время компиляции. Мы можем использовать это, создавая экземпляры объекта с новым ключевым словом.

Динамическая загрузка классов относится к ситуации, когда мы не можем предоставить определение класса во время компиляции. Тем не менее, мы можем сделать это во время выполнения. Чтобы создать экземпляр класса, мы должны использовать метод Class.forName() :

Class.forName("oracle.jdbc.driver.OracleDriver")

Q7. Какова цель сериализуемого интерфейса?

Мы можем использовать интерфейс Serializable , чтобы обеспечить сериализуемость класса, используя Java Serialization API. Сериализация — это механизм сохранения состояния объекта в виде последовательности байтов, а десериализация — это механизм восстановления состояния объекта из последовательности байтов. Сериализованный вывод содержит состояние объекта и некоторые метаданные о типе объекта и типах его полей.

Мы должны знать, что подтипы сериализуемых классов также сериализуемы. Однако, если мы хотим сделать класс сериализуемым, но его супертип не сериализуем, мы должны сделать две вещи:

  • реализовать сериализуемый интерфейс
  • убедиться, что в суперклассе присутствует конструктор без аргументов

Подробнее о Сериализации мы можем прочитать в одной из наших статей .

Q8. Есть ли деструктор в Java?

В Java сборщик мусора автоматически удаляет неиспользуемые объекты, чтобы освободить память. Разработчикам не нужно помечать объекты для удаления, что чревато ошибками. Так что разумно, что в Java нет доступных деструкторов.

Если объекты содержат открытые сокеты, открытые файлы или соединения с базой данных, сборщик мусора не сможет восстановить эти ресурсы . Мы можем освободить ресурсы в методе close и использовать синтаксис try-finally для последующего вызова метода перед Java 7, например, классы ввода-вывода FileInputStream и FileOutputStream . Начиная с Java 7, мы можем реализовать интерфейс AutoCloseable и использовать оператор try -with-resources для написания более короткого и чистого кода . Но возможно, что пользователи API забывают вызвать метод close , поэтому метод finalize и Cleaner класс возникает, чтобы действовать как сеть безопасности. Но имейте в виду, что они не эквивалентны деструктору.

Нет гарантии, что и метод finalize , и класс Cleaner будут запущены быстро. У них даже нет возможности запуститься до выхода JVM. Хотя мы могли бы вызвать System.runFinalization , чтобы предложить JVM запустить методы финализации любых объектов, ожидающих финализации, это все еще недетерминировано.

Более того, метод finalize может вызывать проблемы с производительностью, тупиковые ситуации и т. д. Дополнительную информацию мы можем найти, просмотрев одну из наших статей: Руководство по методу finalize в Java .

Начиная с Java 9 , класс Cleaner добавляется вместо метода finalize из-за его недостатков. В результате мы лучше контролируем поток, выполняющий действия по очистке.

Но спецификация Java указывает, что поведение очистителей во время System.exit зависит от реализации, и Java не дает никаких гарантий, будут ли вызываться действия очистки или нет.