Классы JAVA

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

Вопросы:

Из чего состоит класс.
Какие виды классов есть в JAVA?

КЛАСС

Класс в Java состоит из следующих элементов:

  1. Имя класса (Class Name):
    Имя, по которому класс можно идентифицировать.
    Имя класса должно быть уникальным.
  2. Поля (Fields):
    Это переменные, которые представляют данные или состояние объекта.

    Они могут быть разных типов, таких как int, String, custom types и так далее.

  3. Конструкторы (Constructors):
    Это специальные методы, которые вызываются при создании объекта класса.
    Имя конструктора должно совпадать с именем класса.
    Они используются для инициализации полей класса.
  4. Методы (Methods):
    Методы представляют действия, которые может выполнять объект.
    Методы определяют поведение класса.
    Они могут принимать параметры и могут возвращать значения.
  5. Вложенные классы и интерфейсы (Nested classes & interfaces):
    Класс может содержать другие классы и интерфейсы.
  6. Блоки инициализации (Initialization blocks):
    Это блоки кода, которые выполняются при создании объекта класса.
    Они могут быть статическими (выполняются при загрузке класса) или нестатическими (выполняются перед каждым вызовом конструктора).
  7. Модификаторы класса (Class Modifiers):
    Это ключевые слова, которые определяют свойства класса, такие как public,

Виды классов

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

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

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

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

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

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

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

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

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

Static nested class Member inner class Local inner class Anonymous inner class
Из него видны:

Из него (самого класса) видны:

- статические свойства и методы OuterClassа (даже private).

- статические свойства и методы родителя OuterClassа public и protected.
То есть те, которые видны в OuterClassе.

Из его экземпляра видны:

- все (даже private) свойства и методы OuterClassа обычные и статические.

- public и protected свойства и методы родителя OuterClassа обычные и статические.
То есть те, которые видны в OuterClassе.

- все (даже private) свойства и методы OuterClassа обычные и статические.

- public и protected свойства и методы родителя OuterClassа обычные и статические.
То есть те, которые видны в OuterClassе.

- все (даже private) свойства и методы OuterClassа обычные и статические.

- public и protected свойства и методы родителя OuterClassа обычные и статические. То есть те, которые видны в OuterClassе.

- все (даже private) свойства и методы OuterClassа обычные и статические.

- public и protected свойства и методы родителя OuterClassа обычные и статические.
То есть те, которые видны в OuterClassе.

Его видно: - согласно модификатору доступа. - согласно модификатору доступа. - только в том методе где он определён. - только в том методе где он определён.
Может наследовать:

- обычные классы.

- такие же статические внутренние классы в OuterClassе и его предках.

- обычные классы.

- такие же внутренние классы в OuterClassе и его предках.

- обычные классы.

- внутренние классы в OuterClassе и его предках.

- такие же локальные классы определённые в том же методе.

Не может наследовать
Может быть наследован:

- любым классом:

- вложенным

- не вложенным

- статическим

- не статическим!

- таким же внутренним классом в OuterClassе и его наследниках. - таким же локальным классом определённом в том же методе. Не может быть наследован
Может имплементировать интерфейс ДА ДА ДА НЕТ
Может содержать:

- статические свойства и методы.

- не статические свойства и методы.

- только обычные свойства и методы (не статические). - только обычные свойства и методы (не статические). - только обычные свойства и методы (не статические).

Рекомендации к применению:

  • Cтатический: если вложенный класс должен быть виден за пределами одного метода или он слишком длинный для того, чтобы его можно было удобно разместить в границах одного метода и если каждому экземпляру такого класса необходима ссылка на включающий его экземпляр.
  • Нестатический: если ссылка на обрамляющий класс не требуется. Доступ к полям стат. и нестат. класса.
  • Локальный: если класс необходим только внутри какого-то метода и требуется создавать экземпляры этого класса только в этом методе.
  • Анонимный: если к тому же применение класса сводится к использованию лишь в одном месте и уже существует тип, характеризующий этот класс.

Лапша на тему использования:

Вложенные классы используются для различных целей:

  • Логическая группировка классов: Если класс полезен только для одного другого класса, то имеет смысл держать его в этом классе в качестве вложенного. Это помогает в упаковке классов, которые логически связаны вместе, и повышает инкапсуляцию.
  • Повышение инкапсуляции: Вложенные классы могут получить доступ ко всем членам, включая приватные члены, внешних классов. Это помогает внешнему классу скрыть его внутренние детали от внешнего мира.
  • Повышение читаемости и поддерживаемости кода: Вложенные классы держат код более организованным и читаемым.

Статический вложенный класc

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

Статические вложенные классы применяются, когда:

  1. Вы хотите связать два класса вместе, но вложенный класс должен быть независим от внешнего класса: в этом случае он не имеет доступа к нестатическим членам внешнего класса, если не создан экземпляр внешнего класса.
  2. Вам нужен класс, который будет использоваться только в одном месте. В этом случае его логично объявить внутри другого класса, чтобы не засорять пространство имен.
  3. Вам нужен утилитарный класс, который не должен иметь своих экземпляров.

Сколько объектов статического вложенного класса можно создать в программе?

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


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

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

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

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

Можно почитать: Вложенные классы в Java

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

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

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

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

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

  • Видны только в пределах блока, в котором объявлены;
  • Не могут быть объявлены как private/public/protected или static;
  • Не могут иметь внутри себя статических объявлений (полей, методов, классов);
  • Имеют доступ к полям и методам обрамляющего класса;
  • Могут обращаться к локальным переменным и параметрам метода, если они объявлены с модификатором final или effectively final.

Локальные классы обычно используются в следующих случаях:

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

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

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

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

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

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

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


Сколько объектов анонимного класса можно создать в программе?

Если речь об объектах разных классов - сколько позволяет память вашей системы.

Если об экземплярах конкретного класса, то каждое объявление анонимного класса ведет к созданию нового класса во время компиляции. т.е. один.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Можно ли наследовать конструктор?

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

Что такое перегрузка конструктора?

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

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

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

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

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

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

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

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

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

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

Ключевое слово Класс Пакет Наследник Все
private YES NO NO NO
- YES YES NO NO
protected YES YES YES NO
public YES YES YES YES

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

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

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

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

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

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


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

Да

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

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

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

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

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

Что такое абстрактные классы?

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

Могут ли быть конструкторы у абстрактных классов?

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

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

Сколько объектов абстрактного класса можно создать в программе?

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

Чем отличается интерфейс от абстрактного класса?

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

Абстрактные классы в Java - это классы, которые не могут быть инстанцированы.

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

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

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

Для обозначения используется модификатор abstract.

Абстрактные классы могут содержать как абстрактные методы, так и не абстрактные методы.

- Абстрактные методы - это методы, которые объявлены, но не реализованы в абстрактном классе.

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

Пример: abstract void move();

- Не абстрактные методы (или конкретные методы) в абстрактных классах реализуются как обычно.

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

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

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

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

  • Может быть конструктор (для вызовов по цепочке из наследников)
  • Имплементят интерфейсы, но не обязаны реализовывать их методы
  • Не могут быть final
  • Могут содержать static методы
  • Нельзя создать объект
  • Абстрактные методы могут отсутствовать
  • Может содержать метод main()
  • Классы-наследники не обязаны реализовывать все абстрактные методы

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

Да


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

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


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

Абстрактный класс в Java может содержать от нуля и более абстрактных методов.

Однако, даже если абстрактный класс не содержит ни одного абстрактного метода, он все равно не может быть инстанциирован.

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

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

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

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


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

Да, абстрактный класс в Java может содержать обычные методы.
Это одна из особенностей абстрактных классов.

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

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


Сколько объектов абстрактного класса можно создать в программе?

Ни одного. Невозможно создать объект абстрактного класса в Java.

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

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


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

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

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

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

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

Интерфейсы

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

Можно ли создать объект интерфейса?

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

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

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

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

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

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

Проблема ромбовидного наследования

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

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

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

Интерфейс в Java может содержать следующие элементы:

  1. Методы: Интерфейс может объявлять любое количество методов.

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

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

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

    Т.е. по факту это константы.

  3. Вложенные типы: Интерфейс может содержать вложенные интерфейсы, классы и перечисления.

  4. Методы по умолчанию: Начиная с Java 8, интерфейсы могут содержать методы по умолчанию (default), которые предоставляют их реализацию. Методы по умолчанию помогают в расширении интерфейсов без опасения нарушения существующих классов, которые уже реализуют эти интерфейсы.

  5. Статические методы: Также, начиная с Java 8, интерфейс может содержать статические методы.

  6. Частные (приватные) методы: Начиная с Java 9, интерфейсы могут содержать частные (приватные) методы, которые могут быть использованы для повторного использования общего кода между методами.


Можно ли создать объект интерфейса? Если да, то как?

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

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

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

Объявляем переменную, пишем new ИмяИнтрефейса{и тут же реализацию методов}


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

Поля автоматически являются публичными public, статическими static и неизменяемыми final.

Все методы неявно объявляются как public.


Что такое 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()

Документация по OBJECT (Java17)

Equals и HashCode

Базовый контракт между hashCode() и equals()

Equals

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

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

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


Базовая реализация:

equals в классе Object проверяет только равенство ссылок:

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

По умолчанию каждый экземпляр равен только самому себе.
Это не всегда хорошо, поэтому для новых классов обычно переопределяют метод 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():

  • Использование оператора == для проверки, является ли аргумент ссылкой на указанный объект. Если является, возвращается true. Если сравниваемый объект == null, должно вернуться false.
  • Использование оператор instanceof и вызова метода getClass() для проверки, имеет ли аргумент правильный тип. Если не имеет, возвращается false.
  • Приведение аргумента к правильному типу. Поскольку эта операция следует за проверкой instanceof она гарантированно будет выполнена.
  • Обход всех значимых полей класса и проверка того, что значение поля в текущем объекте и значение того же поля в проверяемом на эквивалентность аргументе соответствуют друг другу.

    Если проверки для всех полей прошли успешно, возвращается результат true, в противном случае - false.

  • По окончанию переопределения метода 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()

  1. Для перечислений (enum)
  2. Когда важны сами экземпляры, а не данные внутри них

    Пример - классы Thread, RecursiveTask

  3. Сравнение объектов вообще не предполагается

    Пример - класс Pattern

  4. В базовом классе уже есть equals и нам подходит эта реализация
  5. Класс имеет модификатор доступа private или по умолчанию и мы уверены, что метод equals не будет вызван

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

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

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

my.equals(child) == true

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

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


hashcode

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

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

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

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

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

  • если хэш коды разные, то и объекты гарантированно разные;
  • если хэш коды равны, то объекты могут необязательно равны.
  • для одного и того-же объекта, хеш-код всегда будет одинаковым;
  • если объекты одинаковые, то и хеш-коды одинаковые (но не наоборот).

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

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

public native int hashCode();

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

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

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

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


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

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

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

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


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

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


Lombok аннотация @EqualsAndHashCode

Можно использовать если:

  • Если классы связаны отношением родитель-наследник, сравниваются объекты только одинаковых типов

    ВАЖНО: у всех классов в иерархии должна быть аннотация @EqualsAndHashCode.
    Если у одного класса будет аннотация, а у другого явно определён equals, то сравнения могут быть некорректны

  • Сравниваются все поля

    Изменить список полей можно с помощью аннотаций @EqualsAndHashCode.Include и @EqualsAndHashCode.Exclude.

    Или явно задать его в @EqualsAndHashCode(of = {"x"})

  • Все поля проверяются на null

this и super


Для чего используется ключевое слово this?

В Java, ключевое слово this используется в качестве ссылки на текущий объект, внутри которого оно используется.

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


Как вызвать метод из родительского класса?

В Java вы можете вызвать метод родительского класса с помощью ключевого слова super.

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

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

Методы. Переопределение и перегрузка

Что такое статический метод?

Что такое не статический метод?

Что такое переопределение метода?

Что такое перегрузка метода?

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

Что такое сокрытие метода?

Затенение и сокрытие метода. Чем отличаются?

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

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

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

Что такое виртуальная функция и используются ли они в Java?

Что такое статический метод?

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

Статические методы объявляются с помощью ключевого слова static.

Основные характеристики статических методов:

  1. Статический метод может быть вызван прямо из класса и не требует создания экземпляра класса.
  2. Статический метод может прямо обращаться только к статическим переменным и статическим методам. Он не может прямо обращаться к нестатическим членам (переменным и методам).
  3. Не могут быть переопределены (в прямом смысле это слова, см. сокрытие), но могут быть перегружены.

Что такое не статический метод?

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

Основные характеристики нестатических методов:

  1. Нестатический метод требует создания экземпляра класса для его вызова.
  2. Нестатический метод может прямо обращаться к любому другому члену класса (как статическому, так и нестатическому).
  3. Нестатические методы могут быть переопределены, если они не объявлены как final.

Что такое переопределение метода?

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

Это важный аспект объектно-ориентированного программирования, так как он поддерживает принцип полиморфизма.

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

Аннотация @Override не является обязательной, но это хорошая практика использовать её при переопределении методов, так как это помогает быстрее выявить ошибки во время компиляции, если метод родительского класса не существует или имеет другую сигнатуру.


Что такое перегрузка метода?

Перегрузка метода (Method Overloading) - это возможность в языке программирования Java, которая позволяет классу иметь два или более метода с одинаковым именем, но разными параметрами.

Важно помнить:

  1. Методы должны иметь одинаковое имя, но разные параметры (как по типу, так и по количеству).
  2. Перегруженные методы могут иметь разные модификаторы доступа (private, public, protected).
  3. Перегруженный метод может выбросить любое количество исключений, независимо от того, выбрасывают ли исключения оригинальные методы.
  4. Перегруженный метод может иметь разные возвращаемые типы.*

*Можно ли изменить тип возвращаемых данных при перегрузке метода?

Изменение только типа возвращаемых данных не достаточно для перегрузки метода в Java.

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

Однако, если вы измените список параметров, то сможете успешно перегрузить метод.


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

Нет, однако...

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

Однако, статический метод может сокрыт в подклассе.


Что такое сокрытие метода?

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

Сокрытие метода в Java применяется только к статическим методам. Нестатические методы поддерживают переопределение, но не сокрытие.


Затенение и сокрытие метода. Чем отличаются?

Затенение метода (method overriding) и сокрытие метода (method hiding) - это разные понятия в Java.

Затенение метода (Method Overriding): Это механизм, который позволяет подклассу предоставить реализацию метода, уже предоставленного в его суперклассе.

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

При затенении метода используется ключевое слово super.

Сокрытие метода (Method Hiding): Если суперкласс и подкласс имеют статические методы с одинаковым именем и параметрами, то метод в подклассе скрывает метод в суперклассе.

Это известно как сокрытие метода.

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

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


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

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

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


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

Да, могут.


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

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

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

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

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

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

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


Что такое виртуальная функция и используются ли они в Java?

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

В C++, например, виртуальные функции позволяют достичь "позднего связывания" или "динамического связывания", что является ключевой составляющей полиморфизма.

Однако в Java все не-статические и не-финальные методы по умолчанию являются "виртуальными" в том смысле, что они могут быть переопределены в подклассах.

Таким образом, в Java нет необходимости (и даже возможности) явно обозначать методы как "виртуальные" - это поведение является стандартным.

Effectively final

В Java термин effectively final введен в Java 8 и относится к переменным, которые не модифицируются после инициализации.
Другими словами, переменная "effectively final", если она не изменяется после того, как ей впервые присваивается значение.

Это понятие используется преимущественно в контексте лямбда-выражений и анонимных классов. В Java 8 и более поздних версиях такие сущности могут обращаться к локальным переменным, которые являются final или "effectively final".

Технически, вам не обязательно явно объявлять переменную как final, чтобы она была "effectively final". Если Java видит, что локальная переменная не изменяется после инициализации, она считается "effectively final", и вы можете безопасно ссылаться на нее в лямбда-выражении или анонимном классе.

Что такое record? Когда можно его применять?

Подробненько тут

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

Record — это новый тип объявления (type of declaration) для определения неизменяемых (immutable) классов данных.

Он автоматически предоставляет реализации конструктора и методов equals(), hashCode(), toString(), а также геттеров для каждого объявленного поля (с именами полей, а не с префиксом get).

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

Помимо этого, Record не может наследоваться от другого класса (кроме java.lang.Record) и не может быть суперклассом.

VARARGS

Что такое varargs?
В каких случаях стоит его применять?

Varargs (Variable-Length Arguments) - это возможность в Java, которая позволяет методу принимать нуль или более аргументов одного типа.
Это очень удобно, когда вы не знаете, сколько аргументов нужно передать методу.

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

Например:

public void printNumbers(int... numbers) {
    for (int number : numbers) {
        System.out.println(number);
    }
}

Теперь вы можете вызвать этот метод с любым количеством аргументов типа int (включая ноль аргументов):

printNumbers();        // No arguments
printNumbers(1);       // One argument
printNumbers(1, 2, 3); // Three arguments

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

Varargs должен быть последним в списке аргументов метода.


В каких случаях может возникнуть неоднозначность при работе с varargs?

Неоднозначность при работе с varargs в Java может возникнуть в следующих случаях:

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

    Например, если у вас есть методы foo(int a, int b) и foo(int... a), вызов foo(1, 2) может быть неоднозначным.

  2. Перегрузка методов с разными типами varargs.
    Если у вас есть два метода, оба с varargs, но с разными типами, и вы пытаетесь вызвать метод с аргументами, которые могут быть приведены к обоим типам, это может привести к неоднозначности.

    Например, есть методы foo(Integer... a) и foo(String... a),, вызов foo(null) может вызвать неоднозначность.

  3. Неоднозначность с передачей null.
    Если метод принимает varargs и вы передаете null, это может привести к исключению NullPointerException, если метод пытается обработать varargs как массив (что он и есть), так как null не может быть обработан как массив.

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