Рубрики
Без рубрики

Подготовка к собеседованию на Java: 15 вопросов для собеседования на Java

Не все интервью будут сосредоточены на алгоритмах и структурах данных — часто интервью будет сосредоточено на этом… С пометкой “java”, “карьера”, “компьютерные науки”.

Не все интервью будут посвящены алгоритмам и структурам данных — часто интервью будет сосредоточено исключительно на языке или технологии, в которых вы утверждаете, что являетесь экспертом. В интервью, подобных этому, обычно нет никаких “правильных” вопросов, вместо этого они требуют, чтобы вы опирались на память и свой опыт работы с языком — другими словами, они проверяют ваши знания языка программирования.

Однако может быть легко забыть все тонкости такого языка, как Java, потому что, проще говоря, мы не занимаемся такими вопросами, как “Какой памятью управляет JVM?” и “Опишите полиморфизм на примере. “на ежедневной основе.

В этом посте излагаются некоторые популярные вопросы, задаваемые в интервью Java. Поскольку вопросы, связанные с Java, могут сильно различаться, этот пост служит руководством, чтобы заставить вас задуматься о различных типах вопросов, которые вы можете ожидать, и о том, к каким темам вам следует подготовиться. Если вы хотите ознакомиться с полным руководством по освоению языка Java, вы можете ознакомиться с “The Definitive Java Interview Handbook” .

Сегодня мы рассмотрим вопросы и ответы на интервью, связанные с:

  • Экосистема Java
  • Классы Java
  • Интерфейсы
  • Наследование
  • Многопоточность
  • Управление памятью
  • Сборники
  • Обработка исключений
  • Сериализация
  • Синглтон

Давайте начнем!

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

Компиляция в байт-код – это волшебство, лежащее в основе совместимости Java. Различные операционные системы и аппаратные архитектуры имеют JVM, специально разработанные для себя, и все JVM могут запускать один и тот же байт-код. Поэтому, если вы напишете Java-программу в Linux, она будет без проблем выполняться на JVM, разработанной для операционной системы Windows, что делает код независимым от базового оборудования и операционной системы.

  • JRE (Java Runtime Environment) включает виртуальную машину Java и стандартные API-интерфейсы Java (основные классы и вспомогательные файлы.). JRE содержит ровно столько, чтобы выполнить Java-приложение, но недостаточно для его компиляции.

  • JDK (Java Development Kit) – это JRE плюс компилятор Java и набор других инструментов для компиляции и отладки кода. JRE состоит из библиотек платформы Java, виртуальной машины Java (JVM), плагина Java и Java Web Start для запуска приложений Java. JRE как автономная программа не содержит компиляторов и средств отладки. Если вам нужно разрабатывать программы на Java, вам нужен полный пакет Java SDK. JRE недостаточно для разработки программ. Только полный пакет Java SDK содержит компилятор Java, который превращает ваши исходные файлы .java в файлы байт-кода .class.

  • JVM (виртуальная машина Java) – это реализация спецификации, детализирующей поведение, ожидаемое от JVM. Любая реализация, соответствующая спецификации JVM, должна иметь возможность запускать код, скомпилированный в байт-код Java, независимо от языка, на котором код был первоначально написан. В языке программирования Java весь исходный код сначала записывается в виде обычных текстовых файлов, заканчивающихся расширением .java. Эти исходные файлы затем компилируются в файлы .class компилятором javac. Файл .class не содержит кода, который является родным для вашего процессора; вместо этого он содержит байт—коды – машинный язык виртуальной машины Java. Затем средство запуска java запускает ваше приложение с экземпляром виртуальной машины Java.

Для частного пакета нет явного модификатора. При отсутствии какого-либо модификатора переменные класса или члена являются частными для пакета. Участник с пометкой package private виден только в своем собственном пакете. Рассмотрим приведенный ниже класс.

// class can be accessed by other classes within the same
// package but not outside of it.
class IamPackagePrivateClass {

   int IamPackagePrivate;
   private int IamPrivate;

   public IamPackagePrivate(int a, int b) {
      this.IamPackagePrivate = a;
      this.IamPrivate = b;
   }
}

Пакет private – это несколько более широкая форма private. Одна приятная особенность package-private заключается в том, что вы можете использовать его для предоставления доступа к методам, которые вы в противном случае считали бы частными для классов модульного тестирования. Итак, если вы используете вспомогательные классы, которые не имеют другого применения, кроме как помогать вашим общедоступным классам делать то, что нужно клиентам, имеет смысл сделать их закрытыми, поскольку вы хотите, чтобы все было как можно проще для пользователей библиотеки.

Класс Object предоставляет метод обратного вызова finalize(), который может быть вызван для объекта, когда он становится мусором. Реализация объекта finalize() ничего не делает — вы можете переопределить finalize() для выполнения очистки, например, для освобождения ресурсов.

Метод finalize() может быть вызван системой автоматически, но когда он будет вызван или даже если он будет вызван, неизвестно. Таким образом, вы не должны полагаться на этот метод, чтобы выполнить очистку за вас. Например, если вы не закрываете дескрипторы файлов в своем коде после выполнения ввода-вывода и ожидаете, что finalize() закроет их за вас, у вас могут закончиться дескрипторы файлов.

Вот несколько альтернатив:

  • Идиома try-with-resources может использоваться для очистки объектов. Для этого требуется реализовать автоматически закрываемый интерфейс.
  • Использование PhantomReference для выполнения очистки, когда объект собирается из мусора
  • Использование класса Cleaner для выполнения действий по очистке.
  • Реализуйте метод close() , который выполняет очистку и документирует вызов метода.
final int[] array = new int[5];
array[0] = 1;

Это может показаться нелогичным, но мы действительно можем изменить содержимое массива, даже если оно помечено как окончательное. Переменная array указывает на определенное начальное местоположение в памяти, куда помещается содержимое массива. Местоположение или адрес памяти изменить нельзя. Например, следующий код не будет компилироваться:

final int[] array = new int [5]
array = new int[10];

Однако следующий код будет работать.

public class FinalArrayExample {
  final int[] array = new int[5];

  // allowed
  void changeArrayContents(int i, int val) {
    array[i] = val;
  }

  // not allowed and will not compile
  /*

  void changeArray() {
    array = new int [10]

  }*/

}

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

Интерфейс – это полностью “абстрактный класс”, который используется для группировки связанных методов с пустыми телами.

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

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

  • Абстрактный класс может иметь статические, абстрактные или неабстрактные методы. Интерфейс может иметь статические, абстрактные или стандартные методы.

  • Члены абстрактного класса могут иметь различную видимость private, protected или public. Принимая во внимание, что в интерфейсе все методы и константы являются общедоступными.

  • Класс может только расширять другой класс, но он может реализовывать несколько интерфейсов. Аналогично, интерфейс может расширять несколько интерфейсов. Интерфейс никогда не реализует класс или интерфейс.

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

Используйте интерфейс, если вы ожидаете, что несвязанные классы будут реализовывать ваш интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными классами. Интерфейсы также используются в случаях, когда требуется множественное наследование типа.

Полиморфизм – это способность в программировании представлять один и тот же интерфейс для различных базовых форм или типов данных. Полиморфизм – это когда вы можете рассматривать объект как общую версию чего-либо, но когда вы обращаетесь к нему, код определяет, к какому именно типу он относится, и вызывает связанный код. Это означает, что полиморфизм позволяет вашему коду работать с разными классами без необходимости знать, какой класс он использует.

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

Классическим примером полиморфизма является класс Shape . Мы получаем Классы Circle , Triangle и Rectangle из родительского класса Shape , который предоставляет абстрактный метод draw() . Производные классы предоставляют свои пользовательские реализации для метода draw() . Теперь очень легко визуализировать различные типы фигур, содержащихся в одном и том же массиве, вызывая метод draw() для каждого объекта. Это избавляет нас от создания отдельных методов рисования для каждой фигуры, например draw Triangle() , drawCircle() и т.д.

Да, основной метод, который является статическим методом, может быть перегружен. Но только public static void main(String[] args) будет использоваться при запуске вашего класса JVM, даже если вы укажете один или два аргумента командной строки. Однако программно можно вызвать перегруженные версии метода main.

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

public void childrenNames(string... names) {
   for(int i= 0; i < names.length; i++)
   system.out.println(names[i]);

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

Приведенный выше метод может быть вызван следующим образом: Вызов метода Varargs

childrenNames();
childrenNames("Jane");
childrenNames("Jane", "Tom", "Peter");

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

Это подводит нас к понятию “собственность”. Мьютекс принадлежит потоку, получающему его, до тех пор, пока он не освободит его, в то время как для семафора нет понятия собственности.

Нужно освежить знания о многопоточности? Ознакомьтесь с этой статьей “Многопоточность и параллелизм Java: что нужно знать, чтобы пройти собеседование со старшим инженером”/| .

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

Интерфейс Externalizable расширяет интерфейс Serializable и предоставляет два метода для сериализации и десериализации объекта, writeExternal() и readExternal() .

Несколько типов исключений, создаваемых фрагментом кода, могут обрабатываться несколькими предложениями блока catch, за которыми следует блок try. Ниже приведен пример фрагмента обработки исключений:

void process(int val)  {
   try {
        if (val == 1)
            //checked exception
            throw new FileNotFoundException();

        if (val == 2)
            // runtime exception
            throw new NullPointerExxception();

        if (val == 3)
            // error exception
            throw new StackOverflowError

   } catch (RuntimeException re) {
            // catches all unchecked  exceptions

   } catch (Exception e) {
            // catches all checked exceptions

   } catch (Error err) {
            // catches all errors

   }

}

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

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

Вот три ключевых шага, которые вы можете предпринять, чтобы увеличить объем памяти:

  • Ограничение области действия локальных переменных. Каждый раз, когда всплывает верхняя область из стека, ссылки из этой области теряются, и это может сделать объекты пригодными для сборки мусора.
  • Явно устанавливайте ссылки на переменные равными null, когда это не требуется. Это сделает объекты пригодными для сборки мусора.
  • Избегайте финализаторов. Они замедляют работу программы и ничего не гарантируют.

Лучший способ реализовать синглтон в соответствии с Джошем Блохом – это использовать тип перечисления для синглтона. Поскольку Java гарантирует, что когда-либо создается только один экземпляр enum, класс singleton, реализованный с помощью enums, защищен от атак отражения и сериализации.

class Demonstration {
    public static void main( String args[] ) {
        Superman superman = Superman.INSTANCE;
        superman.fly();
    }
}

enum Superman {
    INSTANCE;

    private final String name = "Clark Kent";
    private String residence = "USA";

    public void fly() {
        System.out.println("I am flyyyyinggggg ...");
    }
}

В этом посте было много рассказано о языке программирования Java, начиная от экосистемы Java (вопрос 1) и заканчивая многопоточностью (вопрос 10) и исключениями (вопрос 12). Это те типы вопросов для интервью на Java, которые вы можете ожидать. Лучше всего использовать материал, изложенный выше, в качестве руководства для тем, которые вы захотите изучить, и типов вопросов, которые вы можете ожидать.

Однако материал здесь просто царапает поверхность. Есть еще много концепций, которые нужно пересмотреть или изучить, таких как объектно-ориентированное программирование, статические переменные и перегрузка методов. Если вы хотите глубоко погрузиться в Java и изучить еще сотни вопросов по темам, упомянутым выше, то “The Definitive Java Interview Handbook” станет отличным ресурсом, который поможет вам ознакомиться с основными принципами Java — от основ до более продвинутых функций.

Счастливого обучения!

Оригинал: “https://dev.to/educative/java-interview-prep-15-java-interview-questions-i64”