Классы JAVA

Вопросы к собеседованию

Вопросы:

Какие виды классов есть в JAVA?

К классам верхнего уровня модификатор static неприменим.

Расскажите про вложенные классы.
В каких случаях они применяются?

Класс называется вложенным (Nested class), если он определен внутри другого класса.

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

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

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

Существуют четыре категории вложенных классов:

Такие категории классов, за исключением первого, также называют внутренними (Inner class).

Внутренние классы ассоциируются не с внешним классом, а с экземпляром внешнего.

Каждая из категорий имеет рекомендации по своему применению:

Каким образом из вложенного класса получить доступ к полю внешнего класса?

Статический вложенный класс имеет прямой доступ только к статическим полям обрамляющего класса.

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

Например: Outer.this.field.

Что такое локальный класс?
Каковы его особенности?

Local inner class (Локальный класс) - это вложенный класс, который может быть декларирован в любом блоке, в котором разрешается декларировать переменные.

Как и простые внутренние классы (Member inner class) локальные классы имеют имена и могут использоваться многократно.

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

Локальные классы имеют следующие особенности:

Что такое анонимные классы?
Где они применяются?

Это вложенный локальный класс без имени, который разрешено декларировать в любом месте обрамляющего класса, разрешающем размещение выражений.

Создание экземпляра анонимного класса происходит одновременно с его объявлением.

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

Анонимные классы имеют несколько ограничений:

Анонимные классы обычно применяются для:

Что такое перечисления (enum)?

Перечисления представляют набор логически связанных констант.

Объявление перечисления происходит с помощью оператора enum, после которого идет название перечисления. Затем идет список элементов перечисления через запятую.

Каждый из них явно объявлен как открытый статический финальный член класса.

Особенности Enum классов:

Конструкторы

Что такое конструктор по умолчанию?

Если у какого-либо класса не определить конструктор, то компилятор сгенерирует конструктор без аргументов - так называемый «конструктор по умолчанию».

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

Могут ли быть приватные конструкторы?
Для чего они нужны?

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

Также доступ к нему разрешен вложенным классам и может использоваться для их нужд.

Запрещает создание экземпляра класса вне методов самого класса, например, чтобы гарантировать существование только одного объекта определённого класса, предположим какого-то ресурса, например БД.

Чем отличаются конструкторы по-умолчанию, конструктор копирования и конструктор с параметрами?

У конструктора по умолчанию отсутствуют какие-либо аргументы.

Конструктор копирования принимает в качестве аргумента уже существующий объект класса для последующего создания его клона.

Конструктор с параметрами имеет в своей сигнатуре аргументы (обычно необходимые для инициализации полей класса).

Какие модификаторы доступа есть в Java?

Модификаторы доступа

private(приватный): члены класса доступны только внутри класса.

default, package-private, package level (доступ на уровне пакета): видимость класса/членов класса только внутри пакета. Является модификатором доступа по умолчанию.

protected(защищённый): члены класса доступны внутри пакета и в наследниках.

public (публичный): класс/члены класса доступны всем.

Во время наследования возможно изменения модификаторов доступа в сторону большей видимости (для поддержания соответствия принципу подстановки Барбары Лисков).

Модификатор static

Модификатор, применяемый к полю, блоку, методу, внутреннему классу.

Данный модификатор указывает на привязку субъекта к текущему классу.

Модификатор final

Модификатор final может применяться к переменным, параметрам методов, полям и методам класса или самим классам.

Использование модификаторов


Может ли статический метод быть переопределён или перегружен?

Статические методы не могут быть переопределены в точном смысле слова, но они могут скрыть родительские статические методы (затирать).

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


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

Да, могут.


Можно ли сузить уровень доступа/тип возвращаемого значения при переопределении метода?

При переопределении метода сужать модификатор доступа нельзя, т.к. это приведет к нарушению принципа подстановки Барбары Лисков. Расширение уровня доступа возможно.

Изменять тип возвращаемого значения при переопределении метода разрешено только в сторону сужения типа (вместо родительского класса - наследника).

При изменении типа, количества, порядка следования аргументов вместо переопределения будет происходить overloading (перегрузка) метода.

Секцию throws метода можно не указывать, но стоит помнить, что она остаётся действительной, если уже определена у метода родительского класса.

Также, возможно добавлять новые исключения, являющиеся наследниками от уже объявленных или исключения RuntimeException.

Порядок следования таких элементов при переопределении значения не имеет.


Могут ли классы быть статическими?

Да

Объект статического класса не хранит ссылку на конкретный экземпляр внешнего класса.

Объект статического вложенного класса вполне может существовать сам по себе.

Статический вложенный класс может обращаться только к статическим полям и методам внешнего класса.

Объекты статического класса не содержат ссылок на объекты внешнего класса. А самих объектов мы можем создать сколько угодно.

Абстрактные классы


Что такое абстрактные классы?
Чем они отличаются от обычных?

Класс помеченный модификатором abstract называется абстрактным классом.

Создавать экземпляры самого абстрактного класса не разрешается.

Такие классы могут выступать только предками для других классов.

При этом наследниками абстрактного класса могут быть как другие абстрактные классы, так и классы, допускающие создание объектов.

Метод помеченный ключевым словом abstract - абстрактный метод, т.е. метод, который не имеет реализации.

Если в классе присутствует хотя бы один абстрактный метод, то весь класс должен быть объявлен абстрактным.

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

Особенности абстрактных классов:


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

Да


Могут ли быть конструкторы у абстрактных классов? Для чего они нужны?

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

Интерфейсы

Что такое интерфейсы?

Ключевое слово interface используется для создания полностью абстрактных классов.

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

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

Начиная с Java 8 в интерфейсах разрешается размещать реализацию методов по умолчанию default и статических static методов.

Какие модификаторы по умолчанию имеют поля и методы интерфейсов?

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

Что такое static метод интерфейса?

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

Статические методы в интерфейсе являются частью интерфейса без возможности использовать их для объектов класса реализации;

Методы класса java.lang.Object нельзя переопределить как статические;

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


Чем интерфейсы отличаются от абстрактных классов?


Может ли один интерфейс наследоваться от другого?

Да.

От двух других?

И снова да.


Что такое дефолтные методы интерфейсов?

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

Дефолтные методы можно переопределить.

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

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

Метод по умолчанию не может переопределить метод класса java.lang.Object.

Для чего они нужны?

Помогают реализовывать интерфейсы без страха нарушить работу других классов.

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

Дают свободу классам выбрать метод, который нужно переопределить.

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


Как решается проблема ромбовидного наследования при наследовании интерфейсов при наличии default методов?

Через переопределение.

Порядок вызова конструкторов и блоков инициализации

Сначала вызываются все статические блоки в очередности от первого статического блока корневого предка и выше по цепочке иерархии до статических блоков самого класса.

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

Parent static block(s) → Child static block(s) → Grandchild static block(s) →
Parent nonstatic block(s) → Parent constructor →
Child nonstatic block(s) → Child constructor →
Grandchild nonstatic block(s) → Grandchild constructor


Зачем нужны и какие бывают блоки инициализации?

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

Существуют статические и нестатические блоки инициализации.

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

Несколько блоков инициализации выполняются в порядке следования в коде класса.

Блок инициализации способен генерировать исключения, если их объявления перечислены в throws всех конструкторов класса.

Блок инициализации возможно создать и в анонимном классе.

class Dog {

private String name;

private String poroda;

private int age;

{

name = "Шарик";

poroda = "овчарка";

age = 2;

}

public Dog(String x, String y, int z){

name = x;

poroda = y;

age = z;

}

}


Для чего в Java используются статические блоки инициализации?

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

Такой блок (в отличие от нестатических, принадлежащих конкретному объекту класса) принадлежит только самому классу (объекту метакласса Class).


Что произойдет, если в блоке инициализации возникнет исключительная ситуация?

Если блок статический – ExceptionInInitializerError,
Если нестатический – вылетит само исключение.

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

Для статического блока выбрасывание исключения в явном виде, приводит к ошибке компиляции.

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


Какое исключение выбрасывается при возникновении ошибки в блоке инициализации класса?

Если возникшее исключение - наследник RuntimeException:

Если возникшее исключение - наследник Error, то в обоих случаях будет выброшено java.lang.Error. Исключение: java.lang.ThreadDeath - смерть потока. В этом случае никакое исключение выброшено не будет.

OBJECT

Object это базовый класс для всех остальных объектов в Java.

Любой класс наследуется от Object и, соответственно, наследуют его методы.

Методы класса Object

Метод Описание
equals()

Служит для сравнения объектов по значению; == по ссылке

public boolean equals(Object obj)

hashCode()

Возвращает hash код для объекта

int hashCode()

toString()

Возвращает строковое представление объекта

String toString()

getClass()

Возвращает класс объекта во время выполнения

Class getClass()

clone()

Создает и возвращает копию объекта

protected Object clone()

notify()

Возобновляет поток, ожидающий монитор

void notify()

notifyAll()

Возобновляет все потоки, ожидающие монитор

void notifyAll()

wait()

Остановка вызвавшего метод потока до момента пока другой поток не вызовет метод notify() или notifyAll() для этого объекта

void wait()

wait()

Остановка вызвавшего метод потока на определённое время или пока другой поток не вызовет метод notify() или notifyAll() для этого объекта

void wait(long timeout)

wait()

Остановка вызвавшего метод потока на определённое время или пока другой поток не вызовет метод notify() или notifyAll() для этого объекта

void wait(long timeout, int nanos)

finalize()

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

protected void finalize()


Equals и HashCode

Какой контракт между hashCode() и equals()?

HashCode - Общий контракт:

Equals - Основные принципы:

  1. Рефлексивность

    x.equals(x) = true

  2. Симметричность

    x.equals(y) = true

    y.equals(x) = true

  3. Транзитивность

    x.equals(y) = true

    y.equals(z) = true

    x.equals(z) = true

  4. Постоянство или Непротиворечивость

    Результат одно и то же число пока объект не изменится

    Для любых ссылок на значения х и у, если несколько раз вызвать х.equals(y), постоянно будет возвращаться значение true либо постоянно будет возвращаться значение false при условии, что никакая информация, используемая при сравнении объектов, не поменялась.

  5. Если объекта нет - ложь

    x.equals(null) = false

    Для любой ненулевой ссылки на значение х выражение х.equals(null) должно возвращать false.

Метод equals() - определяет отношение эквивалентности объектов.

При сравнение объектов с помощью == сравнение происходит лишь между ссылками. При сравнении по переопределённому разработчиком equals() - по внутреннему состоянию объектов.

Для чего нужен метод hashCode()?

Метод hashCode() необходим для вычисления хэш кода переданного в качестве входного параметра объекта.

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

Почему нельзя реализовать hashcode() который будет гарантированно уникальным для каждого объекта?

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

Из-за этого, вполне возможна ситуация, что хэш коды разных объектов могут совпасть:

Есть ли какие-либо рекомендации о том, какие поля следует использовать при подсчете hashCode()?

Общий совет: выбирать поля, которые с большой долью вероятности будут различаться. Для этого необходимо использовать уникальные, лучше всего примитивные поля, например такие как id, uuid. При этом нужно следовать правилу, если поля задействованы при вычислении hashCode(), то они должны быть задействованы и при выполнении equals().


Каким образом реализованы методы hashCode() и equals() в классе Object?

public boolean equals(Object obj) { return (this == obj);}

public native int hashCode();

native означает, что реализация данного метода выполнена на другом языке (здесь на C++) и обычно возвращает адрес объекта в памяти.


Зачем нужен equals(). Чем он отличается от операции ==?

Метод equals() - определяет отношение эквивалентности объектов.

При сравнении объектов с помощью == сравнение происходит лишь между ссылками. При сравнении по переопределённому разработчиком equals() - по внутреннему состоянию объектов


Правила переопределения equals()

@Override

public boolean equals(Object obj) {

if (this == obj) // равен сам себе

return true;

if (obj == null) // не равен нулю

return false;

if (getClass() != obj.getClass()) // объекты одинакового класса

return false;

BlackBox other = (BlackBox) obj; // приведение

if (varA != other.varA) // для конкретных переменных класса

return false;

if (varB != other.varB)

return false;

return true;

}


Что будет, если переопределить equals() не переопределяя hashCode()? Какие могут возникнуть проблемы?

Классы и методы, которые используют правила этого контракта могут работать некорректно.

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

В HashSet при добавлении объект сначала сравнивается хэш добавляемого и существующие (быстрая проверка очень экономит время), если хэш разный – то дальше сравнивается по equals.


Есть класс Point{int x, y;}. Почему хэш-код в виде 31 * x + y предпочтительнее чем x + y

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


Чем a.getClass().equals(A.class) отличается от a instanceOf A.class

Для equals всегда нужно сравнивать типы объектов через getClass.

instanceof не соблюдает правило симметрии в наследниках.

my.equals(child) == true

child.equals(my) == false // должно быть тру

Оператор instanceof нужен, чтобы проверить, был ли объект, на который ссылается переменная X, создан на основе какого-либо класса Y. Оператор instanceof проверяет именно происхождение объекта, а не переменной.