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

Структура классов Java и вопросы интервью по инициализации

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

1. Введение

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

Q1. Опишите значение конечного ключевого слова применительно к классу, методу, полю или локальной переменной.

Ключевое слово final имеет несколько разных значений применительно к разным языковым конструкциям:

  • Конечный класс — это класс, который не может быть подклассом
  • Окончательный метод — это метод, который нельзя переопределить в подклассах .
  • Конечное поле — это поле, которое должно быть инициализировано в блоке конструктора или инициализатора и не может быть изменено после этого .
  • Конечная переменная — это переменная, которая может быть назначена (и должна быть назначена) только один раз и никогда не модифицируется после этого .

Q2. Что такое метод по умолчанию?

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

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

Хорошим примером этого является интерфейс Iterator , который позволяет классу быть целью цикла for-each. Этот интерфейс впервые появился в Java 5, но в Java 8 он получил два дополнительных метода, forEach и spliterator . Они определены как методы по умолчанию с реализациями и, таким образом, не нарушают обратную совместимость:

public interface Iterable<T> {

Iterator<T> iterator();

default void forEach(Consumer<? super T> action) { /* */ }

default Spliterator<T> spliterator() { /* */ }
}

Q3. Что такое статические члены класса?

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

Q4. Можно ли объявить класс абстрактным, если в нем нет абстрактных членов? Какова может быть цель такого класса?

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

Q5. Что такое цепочка конструкторов?

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

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

Вот пример с классом, который моделирует скидку в процентах, доступную в течение определенного количества дней. Значения по умолчанию 10% и 2 дня используются, если мы не указываем их при использовании конструктора без аргументов:

public class Discount {

private int percent;

private int days;

public Discount() {
this(10);
}

public Discount(int percent) {
this(percent, 2);
}

public Discount(int percent, int days) {
this.percent = percent;
this.days = days;
}

}

Q6. Что такое переопределение и перегрузка методов? Насколько они разные?

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

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

Вот пример перегрузки в абстрактном классе java.io.Writer . Оба следующих метода называются write , но один из них получает int , а другой — массив char .

public abstract class Writer {

public void write(int c) throws IOException {
// ...
}

public void write(char cbuf[]) throws IOException {
// ...
}

}

Q7. Можно ли переопределить статический метод?

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

Q8. Что такое неизменяемый класс и как его создать?

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

Чтобы сделать класс неизменяемым, вы должны убедиться в следующем:

  • Все поля должны быть объявлены закрытыми и окончательными ; это означает, что они должны быть инициализированы в конструкторе и с тех пор не изменены;
  • В классе не должно быть сеттеров или других методов, изменяющих значения полей;
  • Все поля класса, которые были переданы через конструктор, должны быть либо тоже неизменяемыми, либо их значения должны быть скопированы перед инициализацией поля (иначе мы могли бы изменить состояние этого класса, удерживая эти значения и модифицируя их);
  • Методы класса не должны быть переопределяемыми; либо все методы должны быть final , либо конструктор должен быть закрытым и вызываться только через статический фабричный метод.

Q9. Как сравнить два значения перечисления: с помощью equals() или с помощью ==?

На самом деле, вы можете использовать оба. Значения перечисления являются объектами, поэтому их можно сравнить с equals() , но они также реализованы как статические константы под капотом, поэтому вы также можете сравнить их с == . В основном это вопрос стиля кода, но если вы хотите сэкономить символьное пространство (и, возможно, пропустить ненужный вызов метода), вам следует сравнивать перечисления с == .

Q10. Что такое блок инициализатора? Что такое статический блок инициализатора?

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

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

Статический блок инициализатора — это блок кода в фигурных скобках с модификатором static перед ним. Он выполняется один раз при загрузке класса и может использоваться для инициализации статических полей или для некоторых побочных эффектов.

Q11. Что такое маркерный интерфейс? Каковы известные примеры интерфейсов маркеров в Java?

Маркерный интерфейс — это интерфейс без каких-либо методов. Обычно он реализуется классом или расширяется другим интерфейсом для обозначения определенного свойства. Наиболее широко известными интерфейсами маркеров в стандартной библиотеке Java являются следующие:

  • Serializable используется для явного указания того, что этот класс может быть сериализован;
  • Cloneable позволяет клонировать объекты с помощью метода clone (без интерфейса Cloneable этот метод выдает исключение CloneNotSupportedException );
  • Remote используется в RMI для указания интерфейса, методы которого можно вызывать удаленно.

Q12. Что такое синглтон и как его можно реализовать в Java?

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

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

public class SingletonExample {

private static SingletonExample instance = new SingletonExample();

private SingletonExample() {}

public static SingletonExample getInstance() {
return instance;
}
}

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

Q13. Что такое Вар-Арг? Каковы ограничения для Var-Arg? Как вы можете использовать его внутри тела метода?

Var-arg — это аргумент переменной длины для метода. У метода может быть только один var-arg, и он должен стоять последним в списке аргументов. Он указывается как имя типа, за которым следует многоточие и имя аргумента. Внутри тела метода var-arg используется как массив указанного типа.

Вот пример из стандартной библиотеки — метод Collections.addAll , который получает коллекцию, переменное количество элементов и добавляет все элементы в коллекцию:

public static <T> boolean addAll(
Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}

Q14. Можно ли получить доступ к переопределенному методу суперкласса? Можно ли получить доступ к переопределенному методу супер-суперкласса аналогичным образом?

Чтобы получить доступ к переопределенному методу суперкласса, вы можете использовать ключевое слово super . Но у вас нет аналогичного способа доступа к переопределенному методу суперсуперкласса.

В качестве примера из стандартной библиотеки, класс LinkedHashMap расширяет HashMap и в основном повторно использует его функциональность, добавляя связанный список к его значениям для сохранения порядка итерации. LinkedHashMap повторно использует метод clear своего суперкласса, а затем очищает начальные и конечные ссылки своего связанного списка:

public void clear() {
super.clear();
head = tail = null;
}