«1. Введение

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

2. Вопросы

Q1. Опишите место класса объекта в иерархии типов. Какие типы наследуются от объекта, а какие нет? Наследуются ли массивы от объекта? Можно ли присвоить лямбда-выражение объектной переменной?

Объект java.lang.Object находится на вершине иерархии классов в Java. Все классы наследуются от него либо явно, либо неявно (когда в определении класса опущено ключевое слово extends), либо транзитивно через цепочку наследования.

Однако есть восемь примитивных типов, которые не наследуются от Object, а именно: boolean, byte, short, char, int, float, long и double.

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

Лямбда-выражения нельзя назначать непосредственно переменной объекта, поскольку объект не является функциональным интерфейсом. Но вы можете назначить лямбду переменной функционального интерфейса, а затем назначить ее переменной объекта (или просто назначить ее переменной объекта, одновременно приведя ее к функциональному интерфейсу).

Q2. Объясните разницу между примитивными и ссылочными типами.

Ссылочные типы наследуются от верхнего класса java.lang.Object и сами являются наследуемыми (за исключением конечных классов). Примитивные типы не наследуются и не могут быть подклассами.

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

Примитивные типы обычно хранятся с использованием базовых аппаратных типов значений.

Например, для хранения значения int может использоваться 32-битная ячейка памяти. Ссылочные типы вводят накладные расходы на заголовок объекта, который присутствует в каждом экземпляре ссылочного типа.

Размер заголовка объекта может быть довольно значительным по сравнению с размером простого числового значения. Вот почему в первую очередь были введены примитивные типы — для экономии места на служебных объектах. Недостатком является то, что не все в Java технически является объектом — примитивные значения не наследуются от класса Object.

Q3. Опишите различные типы примитивов и объем памяти, который они занимают.

Java имеет 8 примитивных типов:

boolean — логическое значение true/false. Размер логического значения не определяется спецификацией JVM и может различаться в разных реализациях. byte — 8-битное значение со знаком, short — 16-битное значение со знаком, char — 16-битное значение без знака, int — 32-битное значение со знаком, long — 64-битное значение со знаком, float — 32-битное значение с плавающей запятой одинарной точности, соответствующее стандарту IEEE 754, double — 64-битное значение с плавающей запятой двойной точности, соответствующее стандарту IEEE 754.

Q4. В чем разница между абстрактным классом и интерфейсом? Каковы варианты использования одного и другого?

    Абстрактный класс — это класс с модификатором abstract в определении. Его нельзя создать, но можно создать подкласс. Интерфейс — это тип, описываемый ключевым словом interface. Его также нельзя реализовать, но можно реализовать.

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

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

Абстрактный класс также может реализовывать некоторые базовые методы, необходимые во всех подклассах. Например, большинство коллекций карт в JDK наследуются от класса AbstractMap, который реализует многие методы, используемые подклассами (например, метод equals).

«Интерфейс определяет некоторый контракт, с которым соглашается класс. Реализованный интерфейс может обозначать не только основное назначение класса, но и некоторые дополнительные контракты.

Например, если класс реализует интерфейс Comparable, это означает, что экземпляры этого класса можно сравнивать, какой бы ни была основная цель этого класса.

Q5. Каковы ограничения для членов (полей и методов) типа интерфейса?

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

Все методы интерфейса также неявно общедоступны. Они также могут быть (неявно) абстрактными или стандартными.

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

Проще говоря, вложенный класс — это класс, определенный внутри другого класса.

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

Вот пример внутреннего класса — вы можете видеть, что он может получить доступ к ссылке на экземпляр внешнего класса в виде конструкции OuterClass1.this:

Чтобы создать экземпляр такого внутреннего класса, вам нужно иметь экземпляр внешнего класса:

Статический вложенный класс совсем другой. Синтаксически это просто вложенный класс с модификатором static в его определении.

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

Для создания экземпляра такого класса вам не нужен экземпляр внешнего класс:

public class OuterClass1 {

    public class InnerClass {

        public OuterClass1 getOuterInstance() {
            return OuterClass1.this;
        }

    }

}

Q7. Есть ли в Java множественное наследование?

OuterClass1 outerClass1 = new OuterClass1();
OuterClass1.InnerClass innerClass = outerClass1.new InnerClass();

Java не поддерживает множественное наследование для классов, что означает, что класс может наследоваться только от одного суперкласса.

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

public class OuterClass2 {

    public static class StaticNestedClass {
    }

}

Q8. Что такое классы-оболочки? Что такое автобокс?

OuterClass2.StaticNestedClass staticNestedClass = new OuterClass2.StaticNestedClass();

Для каждого из восьми примитивных типов в Java существует класс-оболочка, который можно использовать для обертывания примитивного значения и использования его как объекта. Эти классы, соответственно, Boolean, Byte, Short, Character, Integer, Float, Long и Double. Эти оболочки могут быть полезны, например, когда вам нужно поместить примитивное значение в общую коллекцию, которая принимает только ссылочные объекты.

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

Q9. Опишите разницу между equals() и ==

Оператор == позволяет сравнивать два объекта на предмет «одинаковости» (т. е. того, что обе переменные ссылаются на один и тот же объект в памяти). Важно помнить, что ключевое слово new всегда создает новый объект, который не будет соответствовать равенству == ни с каким другим объектом, даже если кажется, что они имеют одинаковое значение:

Кроме того, оператор == позволяет для сравнения примитивных значений:

Метод equals() определен в классе java.lang.Object и поэтому доступен для любого ссылочного типа. По умолчанию он просто проверяет, что объект тот же, с помощью оператора ==. Но обычно это переопределяется в подклассах, чтобы обеспечить конкретную семантику сравнения для класса.

List<Integer> list = new ArrayList<>();
list.add(new Integer(5));

Например, для класса String этот метод проверяет, содержат ли строки одинаковые символы:

List<Integer> list = new ArrayList<>();
list.add(5);
int value = list.get(0);

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

«Вы не можете использовать ключевое слово instanceof в этом случае, потому что оно работает только в том случае, если вы предоставляете фактическое имя класса как литерал.

String string1 = new String("Hello");
String string2 = new String("Hello");

assertFalse(string1 == string2);

К счастью, в классе Class есть метод isInstance, который позволяет проверить, является ли объект экземпляром этого класса:

int i1 = 5;
int i2 = 5;

assertTrue(i1 == i2);

Q11. Что такое анонимный класс? Опишите вариант его использования.

String string1 = new String("Hello");
String string2 = new String("Hello");

assertTrue(string1.equals(string2));

Анонимный класс — это одноразовый класс, который определяется там же, где требуется его экземпляр. Этот класс определен и создан в одном и том же месте, поэтому ему не нужно имя.

До Java 8 вы часто использовали анонимный класс для определения реализации интерфейса с одним методом, например Runnable. В Java 8 вместо отдельных интерфейсов абстрактных методов используются лямбда-выражения. Но у анонимных классов все еще есть варианты использования, например, когда вам нужен экземпляр интерфейса с несколькими методами или экземпляр класса с некоторыми дополнительными функциями.

Вот как можно создать и заполнить карту:

«

Class<?> integerClass = new Integer(5).getClass();
assertTrue(integerClass.isInstance(new Integer(4)));

 

Q11. What Is an Anonymous Class? Describe Its Use Case.

Anonymous class is a one-shot class that is defined in the same place where its instance is needed. This class is defined and instantiated in the same place, thus it does not need a name.

Before Java 8, you would often use an anonymous class to define the implementation of a single method interface, like Runnable. In Java 8, lambdas are used instead of single abstract method interfaces. But anonymous classes still have use cases, for example, when you need an instance of an interface with multiple methods or an instance of a class with some added features.

Here’s how you could create and populate a map:

Map<String, Integer> ages = new HashMap<String, Integer>(){{
    put("David", 30);
    put("John", 25);
    put("Mary", 29);
    put("Sophie", 22);
}};
Next »

Java Concurrency Interview Questions (+ Answers)

« Previous

Java Collections Interview Questions