Зачем нужны вложенные классы java
Перейти к содержимому

Зачем нужны вложенные классы java

  • автор:

Nested Classes

The Java programming language allows you to define a class within another class. Such a class is called a nested class and is illustrated here:

A nested class is a member of its enclosing class. Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private. Static nested classes do not have access to other members of the enclosing class. As a member of the OuterClass , a nested class can be declared private , public , protected , or package private. (Recall that outer classes can only be declared public or package private.)

Why Use Nested Classes?

Compelling reasons for using nested classes include the following:

It is a way of logically grouping classes that are only used in one place: If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.

It increases encapsulation: Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private . By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.

It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.

Inner Classes

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.

Objects that are instances of an inner class exist within an instance of the outer class. Consider the following classes:

An instance of InnerClass can exist only within an instance of OuterClass and has direct access to the methods and fields of its enclosing instance.

To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax:

There are two special kinds of inner classes: local classes and anonymous classes.

Static Nested Classes

As with class methods and variables, a static nested class is associated with its outer class. And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class: it can use them only through an object reference. Inner Class and Nested Static Class Example demonstrates this.

You instantiate a static nested class the same way as a top-level class:

Inner Class and Nested Static Class Example

The following example, OuterClass , along with TopLevelClass , demonstrates which class members of OuterClass an inner class ( InnerClass ), a nested static class ( StaticNestedClass ), and a top-level class ( TopLevelClass ) can access:

OuterClass.java

TopLevelClass.java

This example prints the following output:

Note that a static nested class interacts with the instance members of its outer class just like any other top-level class. The static nested class StaticNestedClass can’t directly access outerField because it’s an instance variable of the enclosing class, OuterClass . The Java compiler generates an error at the highlighted statement:

To fix this error, access outerField through an object reference:

Similarly, the top-level class TopLevelClass can’t directly access outerField either.

Note: For more information about the taxonomy of the different kinds of classes in the Java programming language (which can be tricky to describe concisely, clearly, and correctly), see Joseph Darcy's blog: Nested, Inner, Member, and Top-Level Classes.

Shadowing

If a declaration of a type (such as a member variable or a parameter name) in a particular scope (such as an inner class or a method definition) has the same name as another declaration in the enclosing scope, then the declaration shadows the declaration of the enclosing scope. You cannot refer to a shadowed declaration by its name alone. The following example, ShadowTest , demonstrates this:

The following is the output of this example:

This example defines three variables named x : the member variable of the class ShadowTest , the member variable of the inner class FirstLevel , and the parameter in the method methodInFirstLevel . The variable x defined as a parameter of the method methodInFirstLevel shadows the variable of the inner class FirstLevel . Consequently, when you use the variable x in the method methodInFirstLevel , it refers to the method parameter. To refer to the member variable of the inner class FirstLevel , use the keyword this to represent the enclosing scope:

Serialization of inner classes, including local and anonymous classes, is strongly discouraged. When the Java compiler compiles certain constructs, such as inner classes, it creates synthetic constructs; these are classes, methods, fields, and other constructs that do not have a corresponding construct in the source code. Synthetic constructs enable Java compilers to implement new Java language features without changes to the JVM. However, synthetic constructs can vary among different Java compiler implementations, which means that .class files can vary among different implementations as well. Consequently, you may have compatibility issues if you serialize an inner class and then deserialize it with a different JRE implementation. See the section Implicit and Synthetic Parameters in the section Obtaining Names of Method Parameters for more information about the synthetic constructs generated when an inner class is compiled.

Для чего в java нужны вложенные классы?

Нестатические вложенные классы (non static nested classes)

Они же — внутренние классы.

Где применяется?

Там где вы описываете структуру будущего объекта, который имеет не только состояние и поведение, но и имеет какие-то дополнительные (составные) части. Без которых сам объект не существовал бы.

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

Как применяется?

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

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

Внутренний класс так же можно определять внутри методов и циклов.

Статические вложенные классы (static nested classes)

Первое, что нужно понимать, что вложенные классы в отличии от внутренних помечаются ключевым словом static . И чтобы во вложенном классе можно было работать с методами и полями внешнего класса, они тоже должны быть помечены static .

Где применяется?

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

Как применяется?

Все обращения к полям и методам внешнего класса и наоборот к полям и методам вложенного класса происходит через класс.

Dmitrii's user avatar

Постараюсь объяснить максимально просто:

В основном в java внутренние классы служат нам для того, чтобы привязывать одну сущность к другой. Например: сущность МАШИНА , у него есть руль , коробка передач , педали . И зачем создавать публичные классы со всем этим? Просто кинул как внутренний в класс Машина и вот лафа.

Так же удобно для применения всяких патернов типа singleton и builder .

insolor's user avatar

Обычно внутренний класс наследует от класса или реализует интерфейс, а код внутрен­него класса манипулирует объектом внешнего класса, в котором он был создан. Значит, можно сказать, что внутренний класс —это нечто вроде «окна» во внешний класс.

Возникает естественный вопрос: «Если мне нужна ссылка на интерфейс, почему бы внешнему классу не реализовать этот интерфейс?» Ответ здесь таков: «Если это все, что вам нужно, —значит, так и следует поступить». Но что же отличает внутренний класс, реализующий интерфейс, от внешнего класса, реализующего тот же интерфейс? Далеко не всегда удается использовать удобство интерфейсов —иногда приходится работать и с реализацией. Поэтому наиболее веская причина для использования вну­тренних классов формулируется так:

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

Без способности внутренних классов наследовать (фактически) реализацию более чем одного конкретного или абстрактного класса некоторые задачи планирования и программирования имели бы крайне сложное решение. Поэтому внутренний класс выступает как «довесок» множественного наследования. Интерфейсы берут на себя часть этой задачи, в то время как внутренние классы фактически обеспечивают «мно­жественное наследование реализации». То есть внутренние классы позволяют вам наследовать от нескольких «не-интерфейсов».

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

Конечно, выбор того или иного способа организации кода зависит от конкретной ситуации. Впрочем, сама решаемая вами задача должна подсказать, что для нее пред­ почтительно: один отдельный класс или внутренний класс. Но при отсутствии иных ограничений оба подхода, использованные в рассмотренном примере, ничем не от­ личаются с точки зрения реализации. Оба они работают. Однако, если вместо интерфейсов у вас имеются конкретные или абстрактные классы, придется «звать на помощь» внутренние классы, если новый класс должен как-то за­ действовать функциональность двух других классов:

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

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

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

Место создания объекта внутреннего класса не привязано к месту и времени соз­дания объекта внешнего класса.

Внутренний класс не создает взаимосвязи классов типа «является тем-то», способной вызвать путаницу; он представляет собой отдельную сущность.

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

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

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

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

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

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

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

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

  • Static nested class (Статический вложенный класс);
  • Member inner class (Простой внутренний класс); ; .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Анонимные классы имеют несколько ограничений:
  • Их использование разрешено только в одном месте программы — месте его создания;
  • Применение возможно только в том случае, если после порождения экземпляра нет необходимости на него ссылаться;
  • Реализует лишь методы своего интерфейса или суперкласса, т.е. не может объявлять каких-либо новых методов, так как для доступа к ним нет поименованного типа.
Анонимные классы обычно применяются для:
  • создания объекта функции (function object), например реализация интерфейса Comparator;
  • создания объекта процесса (process object), такого как экземпляры классов Thread, Runnable и подобных;
  • в статическом методе генерации;
  • инициализации открытого статического поля final, которое соответствует сложному перечислению типов, когда для каждого экземпляра в перечислении требуется отдельный.

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

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

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

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

Особенности Enum классов:
  • Конструктор всегда private или default
  • Могут имплементировать интерфейсы
  • Не могут наследовать класс
  • Можем переопределить toString()
  • Нет public конструктора, поэтому нельзя создать экземпляр вне Enum
  • При equals() выполняется ==
  • values() возвращает все элементы
  • ordinal() возвращает порядок элементов
  • Может использоваться в TreeSet и TreeMap т.к. Enum имплементирует Comparable
  • compareTo() имитирует порядок элементов предоставляемый ordinal()
  • Можно использовать в Switch Case
  • Легко создать потокобезопасный синглтон без double check volatile переменных.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Класс не может иметь наследников;
  • Метод не может быть переопределен в классах наследниках;
  • Поле не может изменить свое значение после инициализации;
  • Параметры методов не могут изменять своё значение внутри метода;
  • Локальные переменные не могут быть изменены после присвоения им значения.

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

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

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

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

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

Да, могут.

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

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

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

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

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

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

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

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

Да

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Интерфейсы

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

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

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

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

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

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

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

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

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

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

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

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

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

    Интерфейс описывает только поведение. У него нет состояния. А у абстрактного класса состояние есть: он описывает и то, и другое.

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

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

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

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

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

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

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

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

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

Метод по умолчанию не может переопределить метод класса 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 всех конструкторов класса.

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

private String name;

private String poroda;

private int age;

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

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

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

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

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

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

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

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

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

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

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

  • для статических блоков инициализации будет выброшено java.lang.ExceptionInInitializerError;
  • для нестатических будет проброшено исключение-источник.

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

OBJECT

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

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

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

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

public boolean equals(Object obj)

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

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

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

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

protected Object clone()

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

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

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

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

void wait(long timeout)

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

void wait(long timeout, int nanos)

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

protected void finalize()

Equals и HashCode

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

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

  • одно и то же число каждый раз когда объект не меняется;
  • если два объекта равны через вызов equals(), то вызов у них hashCode() должен приводить к одному результату;
  • если hashCode() у объектов разные, то объекты разные;

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

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

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

Для любых ссылок на значения х и у, если несколько раз вызвать х.equals(y), постоянно будет возвращаться значение true либо постоянно будет возвращаться значение 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)

public native int hashCode();

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

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

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

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

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

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

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

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

public boolean equals(Object obj) <

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

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

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

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

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

if (varB != other.varB)

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

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

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

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

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

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

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

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

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

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

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

Вложенные классы и лямбда-выражения в Java

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

Сергей Соловых

Согласно документации Java, внутренние (nested) классы бывают двух типов:

  1. статические (static nested classes)
  2. нестатические (inner classes)

Нестатические имеют два подвида: локальные (local classes) и анонимные (anonymous classes). Они схожи и мы рассмотрим их во второй части статьи.

Объявляем класс для примера

Класс в Java позволяет объединять в себе определённые атрибуты и поведение, свойственные объекту. Например, в контексте издательства можно рассмотреть книгу как класс «Book» со следующими полями:

  • Название книги
  • Имя автора
  • Адрес типографии
  • Объём тиража
  • Фамилия редактора
  • Дата издания
  • Текст произведения

А в качестве поведения задать методы, позволяющие:

  • задавать параметры, указанные выше
  • получать содержимое конкретной страницы или нескольких страниц
  • корректировать написанный текст
  • указывать размер тиража
  • отправлять в печать

Здесь в качестве вложенного класса можно описать страницу книги, указать её размер в количестве печатных знаков, задать порядковый номер и вложить само произведение (текст). Также в отдельный класс вынесем обложку книги:

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

Классы обложки и страницы находятся внутри класса книги, но они немного отличаются: «Cover» объявлен как внутренний класс, а класс «Page» — вложенный статический. Это примерная канва процесса создания книги, чтобы объяснить подход к формированию архитектуры приложения, поэтому детали остаются за скобками. Мы не берёмся разработать конкурента InDesign или написать свой WoodWing.

Почему мы объявляем классы именно так? Допустим, работа над книгой начинается, когда автор приносит текст. Сначала нужно определить, в каком он состоянии, требуется ли ему доработка, какого объёма текст. Значит, для работы понадобятся составляющие будущей книги — страницы, каждая из которых будет содержать свой отрывок произведения. Объекты страниц передаются на проверку орфографии, сверку данных, затем попадают на правки к главному редактору. У страниц может измениться размер, могут быть дописаны или сокращены целые главы. Всё это повлияет на конечный вид будущей книги. Таким образом, вполне закономерно, что объекты класса «Page» могут существовать до того, как будет сформирован объект книги.

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

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

Статический класс «Page» содержит текст страницы книги и её геометрические размеры:

Раз нам нужно работать с текстом сразу, то воспользуемся свойством статических классов — экземпляр такого класса может существовать самостоятельно, для его создания не нужен объект внешнего класса. И для создания объекта «Page» нужно обратиться к конструктору через имя внешнего класса и точку:

Именно эта особенность позволяет создать объекты-страницы до того, как они будут объединены в один общий объект-книгу. При этом она диктует и ограничение: из вложенного класса нельзя напрямую обращаться к полям и методам внешнего класса. Ведь объект page существует отдельно и не знает, создан ли какой-либо объект класса «Book», где находится на него ссылка. Но из класса «Page» доступно обращение к статическим полям и методам внешнего класса, в том числе и отмеченным модификатором private.

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

Также статический класс не имеет ограничений по реализации интерфейсов или наследованию. Он может наследовать или сам выступать в роли родительского класса.

Простые внутренние классы

Класс «Cover» — внутренний или нестатический вложенный класс в Java. Его объекты создаются только через объект внешнего класса и не могут существовать без последнего. Выделить в такой класс данные обложки вполне логично, так как она принадлежит только конкретным книге и изданию:

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

Доступность переменных внешнего класса объясняется просто — объект создаётся на основе существующего объекта внешнего класса. Это гарантирует, что его переменные уже созданы и инициализированы, а значит к ним можно обратиться за данными. Более того, объект внутреннего класса хранит ссылку на объект внешнего, благодаря которому он был создан. Чтобы получить её, достаточно добавить в код следующий метод:

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *