ООП
Абстрактные классы и интерфейсы, перегрузка и переопределение, вложенные классы и проблема ромба.
12 вопросов
JuniorТеорияОчень частоКакие в Java четыре модификатора доступа и что открывает каждый?
Какие в Java четыре модификатора доступа и что открывает каждый?
Уровней четыре. private ограничивает член объявляющим классом; package-private (ключевое слово не написано) открывает его тому же пакету; protected добавляет подклассы в любом пакете; public открывает всем. Инкапсуляция — это держать поля private и открывать поведение через методы, чтобы класс мог менять своё устройство, не ломая вызывающий код.
Типичные ошибки
- ✗Думать, что член без модификатора —
public, а не package-private - ✗Считать, что
protectedисключает классы того же пакета, хотя он их включает - ✗Называть класс инкапсулированным, когда у каждого поля есть публичный getter и setter
Уточняющие вопросы
- →Почему
protectedчлен нельзя прочитать у произвольной ссылки типа родителя из другого пакета? - →Как вложенный класс добирается до
privateполей охватывающего класса?
MiddleТеорияОчень частоЧем абстрактный класс и interface отличаются в Java?
Чем абстрактный класс и interface отличаются в Java?
Абстрактный класс может хранить состояние экземпляра, конструкторы и частично реализованные методы, но класс наследует только один через extends, поэтому он моделирует отношение "является". Interface — чистый контракт, которых класс может реализовать много, моделируя способность "умеет делать"; с Java 8 он может нести default и static методы, но его поля — константы, а конструктора и состояния экземпляра у него нет.
Типичные ошибки
- ✗Утверждать, что у interface не может быть тел методов, забывая про
defaultиstaticметоды с Java 8 - ✗Считать, что класс может наследовать несколько абстрактных классов, тогда как одиночное наследование допускает только один
- ✗Думать, что interface хранит состояние экземпляра, тогда как его поля — неявные
public static finalконстанты
Уточняющие вопросы
- →Зачем в Java добавили
defaultметоды в interface и какую проблему они решили? - →Когда вы выберете абстрактный класс вместо interface для общего поведения?
JuniorТеорияЧастоЧто такое интерфейс-маркер и почему Serializable — его пример?
Что такое интерфейс-маркер и почему Serializable — его пример?
Интерфейс-маркер — это пустой интерфейс без объявленных методов; его реализация просто помечает класс метаданными. JVM или библиотека затем проверяет этот тип во время выполнения, чтобы решить, как себя вести. Serializable помечает класс как пригодный к сериализации, а Cloneable позволяет Object.clone() отработать. Класс не получает методов — он лишь несёт метку.
Типичные ошибки
- ✗Ожидать, что интерфейс-маркер объявит методы для реализации, тогда как по определению он пуст
- ✗Считать, что
Serializableдобавляет логику сериализации, а не просто разрешает JVM сериализовать класс - ✗Забывать, что отсутствие маркера вроде
Cloneableприводит к выбросу исключения во время выполнения
Уточняющие вопросы
- →Как аннотации в основном вытеснили интерфейсы-маркеры в современной Java?
- →Что произойдёт во время выполнения при вызове
clone()на классе безCloneable?
JuniorТеорияЧастоВ чём разница между перегрузкой и переопределением метода?
В чём разница между перегрузкой и переопределением метода?
Перегрузка — это несколько методов с одним именем, но разными списками параметров; компилятор выбирает нужный по типам аргументов, то есть это разрешается на этапе компиляции. Переопределение — подкласс переопределяет метод суперкласса с той же сигнатурой; JVM выбирает версию по фактическому типу объекта во время выполнения, то есть это динамическая диспетчеризация.
Типичные ошибки
- ✗Считать, что одного лишь другого типа возврата достаточно для перегрузки при неизменном списке параметров
- ✗Думать, что перегрузка разрешается во время выполнения, как переопределение, а не фиксируется при компиляции
- ✗Путать, какому из них нужна связь наследования — переопределению нужна, перегрузке нет
Уточняющие вопросы
- →Можно ли переопределить private или static метод, и что произойдёт при попытке?
- →Как динамическая диспетчеризация запускает переопределённый метод вопреки типу ссылки?
JuniorТеорияЧастоЧто делает ключевое слово super и где может стоять super(...)?
Что делает ключевое слово super и где может стоять super(...)?
super ссылается на непосредственный суперкласс изнутри подкласса. super.method() вызывает родительскую версию переопределённого метода, а super.field обращается к скрытому полю родителя. super(...) вызывает конструктор суперкласса и должен быть первой инструкцией в конструкторе подкласса; если он опущен, компилятор вставляет неявный вызов super() к конструктору родителя без аргументов.
Типичные ошибки
- ✗Ставить
super(...)после другой инструкции, что не компилируется, ведь он должен быть первым - ✗Полагать, что конструктор родителя вовсе пропускается, если явный
super(...)не написан - ✗Думать, что
superможет напрямую достать прародителя, а не только непосредственный суперкласс
Уточняющие вопросы
- →Почему
super(...)должен быть самой первой инструкцией в конструкторе подкласса? - →Чем
super.method()отличается от обычного вызова, когда метод переопределён?
JuniorТеорияЧастоНа что ссылается ключевое слово this и когда оно нужно?
На что ссылается ключевое слово this и когда оно нужно?
this — это ссылка на текущий объект, на котором выполняется метод экземпляра или конструктор. Его частое применение — разрешение неоднозначности: this.name = name отличает поле от одноимённого параметра, который его затеняет. Форма this(...) вызывает другой конструктор того же класса для переиспользования кода и, как и super(...), должна быть первой инструкцией конструктора.
Типичные ошибки
- ✗Опускать
this, когда параметр конструктора затеняет поле, из-за чего присваивание молча пишет в параметр - ✗Пытаться вызвать
this(...)не первой строкой конструктора, что не компилируется - ✗Считать, что
thisдоступно в статическом контексте, где нет текущего объекта
Уточняющие вопросы
- →Какая ошибка возникнет, если в одном конструкторе написать и
this(...), иsuper(...)? - →Как компилятор разрешает поле против параметра, когда
thisопущено?
MiddleТеорияЧастоКакие виды вложенных классов есть в Java и чем они различаются?
Какие виды вложенных классов есть в Java и чем они различаются?
Java вкладывает классы четырьмя способами. Внутренний (нестатический) класс держит неявную ссылку на охватывающий экземпляр, поэтому читает члены внешнего объекта напрямую. Статический вложенный класс такой связи не имеет и ведёт себя как класс верхнего уровня, ограниченный областью другого. Локальный класс объявляется внутри метода, а анонимный — это одноразовый внутренний класс, заданный и инстанцированный в одном выражении.
Типичные ошибки
- ✗Считать, что статический вложенный класс имеет доступ к нестатическим полям внешнего экземпляра, не неся ссылки на внешний объект
- ✗Забывать, что нестатический внутренний экземпляр требует уже существующего внешнего экземпляра для создания
- ✗Путать анонимный класс с именованным, упуская, что он задаётся и инстанцируется сразу
Уточняющие вопросы
- →Как добраться до внешнего
thisизнутри внутреннего класса при конфликте имён? - →Почему переменные, захваченные локальным или анонимным классом, должны быть фактически final?
MiddleТеорияЧастоЧто делают методы default, static и private в interface?
Что делают методы default, static и private в interface?
default даёт методу interface тело, которое каждый реализующий класс наследует и может переопределить — именно так Java 8 смогла добавлять методы в опубликованные интерфейсы, не ломая их. Методы static принадлежат самому interface, вызываются как Iface.of(...) и не наследуются. Методы private, добавленные в Java 9, — это помощники, разделяющие код между телами default и static, невидимые для реализующих классов.
Типичные ошибки
- ✗Думать, что метод
defaultможет хранить состояние экземпляра — поля interface остаются константамиpublic static final - ✗Ожидать, что метод
staticв interface наследуется и вызывается на реализующем классе - ✗Полагать, что метод
privateв interface виден реализующим классам или переопределяется ими
Уточняющие вопросы
- →Что обязан сделать класс, унаследовавший два конфликтующих метода
defaultиз разных интерфейсов? - →Почему метод
defaultне может переопределитьequalsилиhashCodeизObject?
MiddleТеорияИногдаЧто такое double-brace initialization и почему её избегают?
Что такое double-brace initialization и почему её избегают?
Double-brace initialization — это new HashSet<>() {{ add("a"); }}: внешние скобки создают анонимный подкласс, а внутренние — его instance-initializer block, выполняющий вызовы. Это анти-паттерн: создаётся лишний class-файл, нельзя наследовать final-классы, а анонимный экземпляр держит неявную ссылку this$0 на внешний объект, что может вызвать утечку памяти.
Типичные ошибки
- ✗Думать, что удвоенные скобки — особый синтаксис, а не анонимный подкласс плюс блок инициализатора
- ✗Полагать, что она создаёт неизменяемую коллекцию
- ✗Упускать неявную ссылку на внешний экземпляр, вызывающую утечки
Уточняющие вопросы
- →Почему анонимный экземпляр хранит ссылку на внешний объект?
- →Какая более безопасная современная альтернатива для создания малой заполненной коллекции?
SeniorТеорияИногдаПочему интерфейс-маркер Cloneable считают неудачным в Java?
Почему интерфейс-маркер Cloneable считают неудачным в Java?
Cloneable не объявляет clone() — он лишь велит Object.clone() не бросать исключение, а сам Object.clone() — protected и native, поэтому каждый класс обязан заново объявить публичный clone() и вызвать super.clone(). Полученная копия обходит конструкторы, она поверхностная (изменяемые поля остаются общими) и не может присвоить final поля. Рекомендуемая замена — конструктор копирования или статическая фабрика.
Типичные ошибки
- ✗Считать, что
Cloneableобъявляетclone()— он не объявляет ни одного метода - ✗Ожидать от
Object.clone()глубокой копии, хотя изменяемые поля остаются общими с оригиналом - ✗Забывать, что
clone()обходит конструкторы, поэтому инварианты иfinalполя не устанавливаются
Уточняющие вопросы
- →Как конструктор копирования избавляет от проблем, создаваемых
Cloneable? - →Что
clone()обязан сделать вручную, чтобы глубоко скопировать изменяемые поля класса?
SeniorТеорияИногдаЧто такое проблема ромба и как именно Java её избегает?
Что такое проблема ромба и как именно Java её избегает?
Проблема ромба — это неоднозначность, когда тип наследует один и тот же член по двум путям и компилятор не может определить, какой использовать. Java запрещает наследовать более одного класса через extends, поэтому наследование состояния остаётся однозначным. Она допускает несколько интерфейсов, и когда два дают конфликтующий default метод, класс обязан переопределить его и явно выбрать версию через Interface.super.method().
Типичные ошибки
- ✗Утверждать, что Java допускает множественное наследование классов, тогда как разрешена лишь реализация нескольких интерфейсов
- ✗Считать, что конфликтующий
defaultметод разрешается сам, а не требует явного переопределения - ✗Забывать про синтаксис
Interface.super.method(), нужный для явного выбора default одного из родителей
Уточняющие вопросы
- →Почему конфликт
defaultметодов не компилируется, а две абстрактные сигнатуры сливаются без проблем? - →Как C++ обработал бы тот же ромб и почему Java выбрала другое правило?
SeniorТеорияИногдаМожно ли переопределить private, static и final методы в Java?
Можно ли переопределить private, static и final методы в Java?
Нет. private метод не виден подклассам, final метод запечатан от переопределения, а static метод принадлежит классу, а не экземпляру. Метод static с той же сигнатурой в подклассе не переопределяет, а скрывает родительский: вызов разрешается по типу ссылки на этапе компиляции, а не по типу объекта во время выполнения, поэтому полиморфизма нет — в отличие от настоящего переопределения экземпляра.
Типичные ошибки
- ✗Называть
staticметод подкласса переопределением, тогда как он лишь скрывает родительский и обходит полиморфизм - ✗Ожидать, что скрытый
staticметод диспетчеризуется по типу объекта, а не по типу ссылки - ✗Думать, что
privateметод участвует в переопределении, тогда как подклассы его даже не видят
Уточняющие вопросы
- →Что сделает компилятор, если добавить
@Overrideк скрытомуstaticметоду? - →Как сокрытие метода ведёт себя при вызове через ссылку родительского типа?