0% нашли этот документ полезным (0 голосов)
74 просмотров189 страниц

Java

Документ содержит вопросы и ответы по объектно-ориентированному программированию (ООП) и основам Java, включая принципы ООП, ключевые слова, модификаторы доступа, исключения, сборку мусора и работу с коллекциями. Также рассматриваются концепции, такие как ассоциация, агрегация и композиция, а также различия между классами и интерфейсами. В документе представлены основные понятия и технологии, используемые в Java, включая JRE, JDK, JVM и JIT-компиляцию.

Загружено:

aeglena
Авторское право
© © All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате DOCX, PDF, TXT или читать онлайн в Scribd
0% нашли этот документ полезным (0 голосов)
74 просмотров189 страниц

Java

Документ содержит вопросы и ответы по объектно-ориентированному программированию (ООП) и основам Java, включая принципы ООП, ключевые слова, модификаторы доступа, исключения, сборку мусора и работу с коллекциями. Также рассматриваются концепции, такие как ассоциация, агрегация и композиция, а также различия между классами и интерфейсами. В документе представлены основные понятия и технологии, используемые в Java, включая JRE, JDK, JVM и JIT-компиляцию.

Загружено:

aeglena
Авторское право
© © All Rights Reserved
Мы серьезно относимся к защите прав на контент. Если вы подозреваете, что это ваш контент, заявите об этом здесь.
Доступные форматы
Скачать в формате DOCX, PDF, TXT или читать онлайн в Scribd
Вы находитесь на странице: 1/ 189

CORE

вопросы-ответы Хилькевич Игорь 15.04.2020

Вопросы из “all question”


ООП
1. Что такое ООП, принципы ООП (наследование, инкапсуляция, полиморфизм,
абстракция).
2. Класс, объект, интерфейс.
3. Ассоциация, агрегация, композиция.
4. Является - "is a", имеет - "has a".
5. Статическое и динамическое связывание.
Core
6. JRE, JDK, JVM, JIT...
7. Ключевые слова: abstract, assert,
break, case, catch, class, const*, continue, default, do, else, enum, extends, final, finally, for,
goto*, if, implements, import, instanceof, interface, native, new, package, return, static, strictfp,
super, switch, synchronized, this, throw, throws, transient, try, void, volatile, while. * -
зарезервированное слово, не используется.
8. Модификаторы доступа: private, default, protected, public.
9. Типы данных: примитивные (boolean, char, byte, short, int, long, float, double) и
ссылочные.
10. Логические операторы.
11. Абстрактный класс vs. интерфейс (+ маркерные интерфейсы, функциональные
интерфейсы).
12. Типы классов (вложенные, вложенные статические, анонимные, локальные).
13. Блоки инициализации.
14. Порядок вызова конструкторов и блоков инициализации с учётом иерархии классов.
15. Перегрузка и переопределение статических и нестатических методов.
16. Тип возвращаемого значения метода при перегрузке и переопределении +
модификаторы доступа.
17. Исключения (иерархия, обработка, finally, try с ресурсами, Closeable vs. Autocloseable,
проверяемые vs. непроверяемые, supressed).
18. Garbage collector (виды в HotSpot, типы ссылок, finalize()).
19. Heap vs. stack.
20. Передача переменных в методы.
21. Приведение типов (понижение, повышение типа, ClassCastException).
22. Класс Object (нативные методы, JNA, equals(), hashcode(), toString(), getClass(), clone(),
notify(), notifyAll(), 3 вида wait(), finalize()).
23. String (immutable, string pool, intern(), StringBuilder, StringBuffer, String in switch).
24. Клонирование объектов (поверхностная, глубокая копия, Cloneable, конструктор
копирования).
25. Сериализация объектов (transient, Serializable vs. Externalizable, serialVersionUID,
проблема сериализации синглтона).
26. Классы загрузчики и динамическая загрузка классов.
27. Рефлексия.
28. Контракты equals() и hashcode(). Преимущество использования числа 31 в генерации
хэшкода.
29. Дженерики (инвариантность, ковариантность и контрвариантность, raw types,
wildcards, PECS, множественные ограничения, стирание типов).
Потоки ввода/вывода
30. [Link] vs. [Link] & File vs Path.
31. [Link]
32. Channels.
33. Виды (классы) потоков ввода/вывода (байтовые ([Link],
[Link]) и символьные ([Link], [Link])).
34. Классы для работы с файловой системой.
35. Абсолютный и относительный путь.
36. Символьная ссылка.
Java 8
37. Методы интерфейсов по умолчанию.
38. Лямбда-выражения и ссылка на метод или конструктор.
39. Stream API (как создать стрим, промежуточные и терминальные операции, вернуть
пустой стрим и зачем).
40. Работа с датами и временем.
41. Функциональные интерфейсы.
42. Проверяемые аннотации и аннотации на типы данных (понятие аннотации).
Java Collection Framework (JCF)
43. Понятие коллекции.
44. Иерархия коллекций.
45. List vs. Set.
46. Map не в Collection.
47. Collection vs. Collections.
48. ArrayList vs. LinkedList.
49. Предназначение метода remove() у Iterator. Fail-Fast vs. Fail-Safe.
50. Iterable & Iterator. Enumerated vs. Iterator.
51. Comparator vs. Comparable.
52. Iterator vs. ListIterator.
53. ArrayList vs. Vector.
54. Queue vs. Deque.
55. [Link]
56. HashMap vs. HashTable.
57. Устройство HashMap.
58. TreeSet vs. HashSet vs. LinkedHashSet.
59. EnumSet.
60. TreeMap & LinkedHashMap.
61. NavigableSet
Дополнительно
62. SOLID с реальными примерами.
63. Класс Optional.
64. Ромбовидное наследование.

Ответы

ООП
1. Что такое ООП, принципы ООП (наследование, инкапсуляция,
полиморфизм, абстракция).
Объектно-ориентированное программирование (ООП) — методология
программирования, основанная на представлении программы в виде совокупности объектов,
каждый из которых является экземпляром определенного класса, а классы образуют
иерархию наследования.
 объектно-ориентированное программирование использует в качестве основных
логических
 конструктивных элементов объекты, а не алгоритмы;
 каждый объект является экземпляром определенного класса
 классы образуют иерархии.
Программа считается объектно-ориентированной, только если выполнены все три
указанных требования. В частности, программирование, не использующее наследование,
называется не объектно-ориентированным, а программированием с помощью абстрактных
типов данных.
Согласно парадигме ООП программа состоит из объектов, обменивающихся
сообщениями. Объекты могут обладать состоянием, единственный способ изменить
состояние объекта - послать ему сообщение, в ответ на которое, объект может изменить
собственное состояние.
Инкапсуляция – это свойство системы, позволяющее объединить данные и методы,
работающие с ними, в классе и скрыть детали реализации от пользователя, открыв только то,
что необходимо при последующем использовании.
Цель инкапсуляции — уйти от зависимости внешнего интерфейса класса (то, что могут
использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло
за собой изменение внешнего поведения класса.
Наследование – это свойство системы, позволяющее описать новый класс на основе
уже существующего с частично или полностью заимствующейся функциональностью.
Класс, от которого производится наследование, называется предком, базовым или
родительским. Новый класс – потомком, наследником или производным классом.
Полиморфизм – это свойство системы использовать объекты с одинаковым
интерфейсом без информации о типе и внутренней структуре объекта.
Преимуществом полиморфизма является то, что он помогает снижать сложность
программ, разрешая использование одного и того же интерфейса для задания единого
набора действий. Выбор же конкретного действия, в зависимости от ситуации, возлагается на
компилятор языка программирования. Отсюда следует ключевая особенность полиморфизма
- использование объекта производного класса, вместо объекта базового (потомки могут
изменять родительское поведение, даже если обращение к ним будет производиться по
ссылке родительского типа).
Абстрагирование – это способ выделить набор общих характеристик объекта,
исключая из рассмотрения частные и незначимые. Соответственно, абстракция – это набор
всех таких характеристик.

2. Класс, объект, интерфейс.


Класс – это способ описания сущности, определяющий состояние и поведение,
зависящее от этого состояния, а также правила для взаимодействия с данной сущностью
(контракт).
С точки зрения программирования класс можно рассматривать как набор данных
(полей, атрибутов, членов класса) и функций для работы с ними (методов).
С точки зрения структуры программы, класс является сложным типом данных.
Объект (экземпляр) – это отдельный представитель класса, имеющий конкретное
состояние и поведение, полностью определяемое классом. Каждый объект имеет конкретные
значения атрибутов и методы, работающие с этими значениями на основе правил, заданных в
классе.
Интерфейс – это набор методов класса, доступных для использования. Интерфейсом
класса будет являться набор всех его публичных методов в совокупности с набором
публичных атрибутов. По сути, интерфейс специфицирует класс, чётко определяя все
возможные действия над ним.

3. Ассоциация, агрегация, композиция.


Ассоциация обозначает связь между объектами. Композиция и агрегация — частные
случаи ассоциации «часть-целое».
Агрегация предполагает, что объекты связаны взаимоотношением «part-of» (часть).
Композиция более строгий вариант агрегации. Дополнительно к требованию «part-of»
накладывается условие, что экземпляр «части» может входить только в одно целое (или
никуда не входить), в то время как в случае агрегации экземпляр «части» может входить в
несколько целых.

4. Является - "is a", имеет - "has a".


«является» подразумевает наследование. «имеет» подразумевает ассоциацию
(агрегацию или композицию).
5. Статическое и динамическое связывание.
Присоединение вызова метода к телу метода называется связыванием. Если
связывание проводится компилятором (компоновщиком) перед запуском программы, то оно
называется статическим или ранним связыванием (early binding).
В свою очередь, позднее связывание (late binding) это связывание, проводимое
непосредственно во время выполнения программы, в зависимости от типа объекта. Позднее
связывание также называют динамическим (dynamic) или связыванием на стадии выполнения
(runtime binding). В языках, реализующих позднее связывание, должен существовать
механизм определения фактического типа объекта во время работы программы, для вызова
подходящего метода. Иначе говоря, компилятор не знает тип объекта, но механизм вызова
методов определяет его и вызывает соответствующее тело метода. Механизм позднего
связывания зависит от конкретного языка, но нетрудно предположить, что для его реализации
в объекты должна включаться какая-то дополнительная информация.
Для всех методов Java используется механизм позднего (динамического) связывания,
если только метод не был объявлен как final (приватные методы являются final по
умолчанию).

Core
6. JRE, JDK, JVM, JIT…
JVM, Java Virtual Machine (Виртуальная машина Java) — основная часть среды
времени исполнения Java (JRE). Виртуальная машина Java исполняет байт-код Java,
предварительно созданный из исходного текста Java-программы компилятором Java. JVM
может также использоваться для выполнения программ, написанных на других языках
программирования.
JRE, Java Runtime Environment (Среда времени выполнения Java) - минимально-
необходимая реализация виртуальной машины для исполнения Java-приложений. Состоит из
JVM и стандартного набора библиотек классов Java.
JDK, Java Development Kit (Комплект разработки на Java) - JRE и набор инструментов
разработчика приложений на языке Java, включающий в себя компилятор Java, стандартные
библиотеки классов Java, примеры, документацию, различные утилиты.
Коротко: JDK - среда для разработки программ на Java, включающая в себя JRE -
среду для обеспечения запуска Java программ, которая в свою очередь содержит JVM -
интерпретатор кода Java программ.
JIT-компиляция (англ. Just-in-time compilation, компиляция «на лету»), динамическая
компиляция (англ. dynamic translation) — технология увеличения производительности
программных систем, использующих байт-код, путём компиляции байт-кода в машинный код
или в другой формат непосредственно во время работы программы.

7. Ключевые слова: abstract, assert, break, case, catch, class, const*,


continue, default, do, else, enum, extends, final, finally, for, goto*, if,
implements, import, instanceof, interface, native, new, package, return,
static, strictfp, super, switch, synchronized, this, throw, throws, transient,
try, void, volatile, while. * - зарезервированное слово, не используется.
Для чего используется оператор assert?
Assert (Утверждение) — это специальная конструкция, позволяющая проверять
предположения о значениях произвольных данных в произвольном месте программы.
Утверждение может автоматически сигнализировать об обнаружении некорректных данных,
что обычно приводит к аварийному завершению программы с указанием места обнаружения
некорректных данных.
Утверждения существенно упрощают локализацию ошибок в коде. Даже проверка
результатов выполнения очевидного кода может оказаться полезной при последующем
рефакторинге, после которого код может стать не настолько очевидным и в него может
закрасться ошибка.
Обычно утверждения оставляют включенными во время разработки и тестирования
программ, но отключают в релиз-версиях программ.
Т.к. утверждения могут быть удалены на этапе компиляции либо во время исполнения
программы, они не должны менять поведение программы. Если в результате удаления
утверждения поведение программы может измениться, то это явный признак неправильного
использования assert. Таким образом, внутри assert нельзя вызывать методы, изменяющие
состояние программы, либо внешнего окружения программы.
В Java проверка утверждений реализована с помощью оператора assert, который
имеет форму:
assert [Выражение типа boolean]; или assert [Выражение типа boolean] : [Выражение
любого типа, кроме void];
Во время выполнения программы в том случае, если проверка утверждений включена,
вычисляется значение булевского выражения, и если его результат false, то генерируется
исключение [Link]. В случае использования второй формы оператора assert
выражение после двоеточия задаёт детальное сообщение о произошедшей ошибке
(вычисленное выражение будет преобразовано в строку и передано конструктору
AssertionError).

О чем говорит ключевое слово final?


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

Где и для чего используется модификатор abstract?


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

НА ЗАМЕТКУ!!!
Особенности абстрактных классов:
1. Может быть конструктор (для вызовов по цепочке из наследников)
2. Имплементят интерфейсы, но не обязаны реализовывать их методы
3. Не могут быть final
4. Могут содержать static методы
5. Нельзя создать объект
6. Абстрактные методы могут отсутствовать
7. Может содержать метод main()

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


Нет. В таком случае компилятор выдаст ошибку: "Illegal combination of modifiers:
‘abstract’ and ‘static’". Модификатор abstract говорит, что метод будет реализован в другом
классе, а static наоборот указывает, что этот метод будет доступен по имени класса.

Дайте определение понятию «интерфейс». Какие модификаторы по умолчанию имеют


поля и методы интерфейсов?
Ключевое слово interface используется для создания полностью абстрактных классов.
Основное предназначение интерфейса - определять каким образом мы можем использовать
класс, который его реализует. Создатель интерфейса определяет имена методов, списки
аргументов и типы возвращаемых значений, но не реализует их поведение. Все методы
неявно объявляются как public.
Начиная с Java 8 в интерфейсах разрешается размещать реализацию методов по
умолчанию default и статических static методов.
Интерфейс также может содержать и поля. В этом случае они автоматически являются
публичными public, статическими static и неизменяемыми final.

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


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

Как вызывать static метод интерфейса?


Используя имя интерфейса:
[Link]();

К каким конструкциям Java применим модификатор static?


 полям;
 методам;
 вложенным классам;
 членам секции import.
 блоки инициализации

В чем разница между членом экземпляра класса и статическим членом класса?


 Модификатор static говорит о том, что данный метод или поле принадлежат
самому классу и доступ к ним возможен даже без создания экземпляра класса.
Поля помеченные static инициализируются при инициализации класса. На
методы, объявленные как static, накладывается ряд ограничений:
 Они могут вызывать только другие статические методы.
 Они должны осуществлять доступ только к статическим переменным.
 Они не могут ссылаться на члены типа this или super.
 В отличии от статических, поля экземпляра класса принадлежат конкретному
объекту и могут иметь разные значения для каждого. Вызов метода экземпляра
возможен только после предварительного создания объекта класса.

Где разрешена инициализация статических/нестатических полей?


Статические поля можно инициализировать при объявлении, в статическом или
нестатическом блоке инициализации.
Нестатические поля можно инициализировать при объявлении, в нестатическом блоке
инициализации или в конструкторе.

НА ЗАМЕТКУ!!!
Особенности Enum классов:
1. Конструктор всегда private или default
2. Могут имплементировать интерфейсы
3. Не могут наследовать класс
4. Можем переопределить toString()
5. Нет public конструктора, поэтому нельзя создать экземпляр вне Enum
6. При equals() выполняется ==
7. ordinal() возвращает порядковый номер элемента содержащийся в
Enum.порядок элементов
8. Может использоваться в TreeSet и TreeMap т.к. Enum имплементирует
Comparable
9. compareTo() имитирует порядок элементов предоставляемый ordinal()
10. Можно использовать в Switch Case
11. values() возвращает массив всех констант
12. Легко создать потокобезопасный синглтон без double check volatile переменных.
instanceof
Оператор instanceof сравнивает объект и указанный тип. Имеет ли этот объект связь IS A c
указанным типом. Его можно использовать для проверки является ли данный объект
экземпляром некоторого класса, либо экземпляром его дочернего класса, либо экземпляром
класса, который реализует указанный интерфейс.
[Link]() == [Link]() проверяет два класса на идентичность, поэтому для
корректной реализации контракта метода equals() необходимо использовать точное
сравнение с помощью метода getClass().

8. Модификаторы доступа: private, default, protected, public.


private (приватный): члены класса доступны только внутри класса. Для обозначения
используется служебное слово private.
default, package-private, package level (доступ на уровне пакета): видимость
класса/членов класса только внутри пакета. Является модификатором доступа по умолчанию
- специальное обозначение не требуется.
protected (защищённый): члены класса доступны внутри пакета и в наследниках. Для
обозначения используется служебное слово protected.
public (публичный): класс/члены класса доступны всем. Для обозначения
используется служебное слово public.
Последовательность модификаторов по возрастанию уровня закрытости: public,
protected, default, private.
Во время наследования возможно изменения модификаторов доступа в сторону
большей видимости (для поддержания соответствия принципу подстановки Барбары Лисков).

Может ли объект получить доступ к члену класса объявленному как private? Если да, то
каким образом?
 Внутри класса доступ к приватной переменной открыт без ограничений;
 Вложенный класс имеет полный доступ ко всем (в том числе и приватным)
членам содержащего его класса;
 Доступ к приватным переменным извне может быть организован через
отличные от приватных методы, которые предоставлены разработчиком
класса. Например: getX() и setX().
 Через механизм рефлексии (Reflection API).

9. Типы данных: примитивные (boolean, char, byte, short, int, long, float,
double) и ссылочные.
Числа инициализируются 0 или 0.0;
char — \u0000;
boolean — false;
Объекты (в том числе String) — null.
10. Логические операторы.
&: Логическое AND (И);
&&: Сокращённое AND;
|: Логическое OR (ИЛИ);
||: Сокращённое OR;
^: Логическое XOR (исключающее OR (ИЛИ));
!: Логическое унарное NOT (НЕ);
&=: AND с присваиванием;
|=: OR с присваиванием;
^=: XOR с присваиванием;
==: Равно;
!=: Не равно;
?:: Тернарный (троичный) условный оператор.

Тернарный условный оператор ?: - оператор, которым можно заменить некоторые


конструкции операторов if-then-else.
Выражение записывается в следующей форме:
условие ? выражение1 : выражение2
Если условие выполняется, то вычисляется выражение1 и его результат становится
результатом выполнения всего оператора. Если же условие равно false, то вычисляется
выражение2 и его значение становится результатом работы оператора. Оба операнда
выражение1 и выражение2 должны возвращать значение одинакового (или совместимого)
типа.

Какие побитовые операции вы знаете?


~: Побитовый унарный оператор NOT;
&: Побитовый AND;
&=: Побитовый AND с присваиванием;
|: Побитовый OR;
|=: Побитовый OR с присваиванием;
^: Побитовый исключающее XOR;
^=: Побитовый исключающее XOR с присваиванием;
>>: Сдвиг вправо (деление на 2 в степени сдвига);
>>=: Сдвиг вправо с присваиванием;
>>>: Сдвиг вправо без учета знака;
>>>=: Сдвиг вправо без учета знака с присваиванием;
<<: Сдвиг влево (умножение на 2 в степени сдвига);
<<=: Сдвиг влево с присваиванием.
к оглавлению

11. Абстрактный класс vs. интерфейс (+ маркерные интерфейсы,


функциональные интерфейсы).
В Java класс может одновременно реализовать несколько интерфейсов, но
наследоваться только от одного класса.
Абстрактные классы используются только тогда, когда присутствует тип отношений «is
a» (является). Интерфейсы могут реализоваться классами, которые не связаны друг с другом.
Абстрактный класс - средство, позволяющее избежать написания повторяющегося
кода, инструмент для частичной реализации поведения. Интерфейс - это средство выражения
семантики класса, контракт, описывающий возможности. Все методы интерфейса неявно
объявляются как public abstract или (начиная с Java 8) default - методами с реализацией по-
умолчанию, а поля - public static final.
Интерфейсы позволяют создавать структуры типов без иерархии.
Наследуясь от абстрактного, класс «растворяет» собственную индивидуальность.
Реализуя интерфейс, он расширяет собственную функциональность.
Абстрактные классы содержат частичную реализацию, которая дополняется или
расширяется в подклассах. При этом все подклассы схожи между собой в части реализации,
унаследованной от абстрактного класса и отличаются лишь в части собственной реализации
абстрактных методов родителя. Поэтому абстрактные классы применяются в случае
построения иерархии однотипных, очень похожих друг на друга классов. В этом случае
наследование от абстрактного класса, реализующего поведение объекта по умолчанию
может быть полезно, так как позволяет избежать написания повторяющегося кода. Во всех
остальных случаях лучше использовать интерфейсы.

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


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

Почему нельзя объявить метод интерфейса с модификатором final?


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

Что имеет более высокий уровень абстракции - класс, абстрактный класс или
интерфейс?
Интерфейс.

12. Типы классов (вложенные, вложенные статические, анонимные,


локальные).
Какие типы классов бывают в java?
Top level class (Обычный класс):
-Abstract class (Абстрактный класс);
-Final class (Финализированный класс).
Interfaces (Интерфейс).
Enum (Перечисление).
Nested class (Вложенный класс):
-Static nested class (Статический вложенный класс);
-Member inner class (Простой внутренний класс);
-Local inner class (Локальный класс);
-Anonymous inner class (Анонимный класс).

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


Класс называется вложенным (Nested class), если он определен внутри другого
класса. Вложенный класс должен создаваться только для того, чтобы обслуживать
обрамляющий его класс. Если вложенный класс оказывается полезен в каком-либо ином
контексте, он должен стать классом верхнего уровня. Вложенные классы имеют доступ ко
всем (в том числе приватным) полям и методам внешнего класса, но не наоборот. Из-за этого
разрешения использование вложенных классов приводит к некоторому нарушению
инкапсуляции.
Существуют четыре категории вложенных классов: + Static nested class (Статический
вложенный класс); + Member inner class (Простой внутренний класс); + Local inner class
(Локальный класс); + Anonymous inner class (Анонимный класс).
Такие категории классов, за исключением первого, также называют внутренними (Inner
class). Внутренние классы ассоциируются не с внешним классом, а с экземпляром внешнего.

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

 Не статический: если вложенный класс должен быть виден за пределами


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

Что такое «статический класс»?


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

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


внутренних? В чем заключается разница между ними?
Вложенные классы могут обращаться ко всем членам обрамляющего класса, в том
числе и приватным.Для этого необходимо сделать hide этой переменной во внутреннем
классе. Пример :
double d = [Link].d ; (имя класса, this, имя переменной)
и уже на объектеобьекте внутреннего вызвать это hide поле.
Для создания объекта статического вложенного класса объект внешнего класса не
требуется.
Из объекта статического вложенного класса нельзя обращаться к не статическим
членам обрамляющего класса напрямую, а только через ссылку на экземпляр внешнего
класса.
Обычные вложенные классы не могут содержать статических методов, блоков
инициализации и классов. Статические вложенные классы - могут.
В объекте обычного вложенного класса хранится ссылка на объект внешнего класса.
Внутри статического такой ссылки нет. Доступ к экземпляру обрамляющего класса
осуществляется через указание .this после его имени. Например: [Link].

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


Local inner class (Локальный класс) - это вложенный класс, который может быть
декларирован в любом блоке, в котором разрешается декларировать переменные. Как и
простые внутренние классы (Member inner class) локальные классы имеют имена и могут
использоваться многократно. Как и анонимные классы, они имеют окружающий их экземпляр
только тогда, когда применяются в нестатическом контексте.
Локальные классы имеют следующие особенности:
 Видны только в пределах блока, в котором объявлены;
 Не могут быть объявлены как private/public/protected или static;
 Не могут иметь внутри себя статических объявлений (полей, методов, классов);
 Имеют доступ к полям и методам обрамляющего класса;
 Могут обращаться к локальным переменным и параметрам метода, если они
объявлены с модификатором final.

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


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

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


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

13. Блоки инициализации.


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

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


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

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


Для нестатических блоков инициализации, если выбрасывание исключения прописано
явным образом, например после некоего условия -
{
int x = 5;
if (x == 5){
throw new Exception();
}
требуется, чтобы объявления этих исключений были перечислены в throws всех
конструкторов класса. Иначе будет ошибка компиляции. Для статического блока
выбрасывание исключения в явном виде, приводит к ошибке компиляции.
В остальных случаях, взаимодействие с исключениями будет проходить так же как и в
любом другом месте. Класс не будет инициализирован, если ошибка происходит в
статическом блоке и объект класса не будет создан, если ошибка возникает в нестатическом
блоке.

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


класса?
Если возникшее исключение - наследник RuntimeException:
 для статических блоков инициализации будет выброшено
[Link];
 для нестатических будет проброшено исключение-источник.
Если возникшее исключение - наследник Error, то в обоих случаях будет выброшено
[Link]. Исключение: [Link] - смерть потока. В этом случае никакое
исключение выброшено не будет.

14. Порядок вызова конструкторов и блоков инициализации с учётом


иерархии классов.
Сначала вызываются все статические блоки в очередности от первого статического
блока корневого предка и выше по цепочке иерархии до статических блоков самого класса.
Затем вызываются нестатические блоки инициализации корневого предка, конструктор
корневого предка и так далее вплоть до нестатических блоков и конструктора самого класса.
Parent static block(s) → Child static block(s) → Grandchild static block(s)
→ Parent non-static block(s) → Parent constructor →
→ Child non-static block(s) → Child constructor →
→ Grandchild non-static block(s) → Grandchild constructor

15. Перегрузка и переопределение статических и нестатических


методов.
Может ли статический метод быть переопределен или перегружен?
Перегружен - да. Всё работает точно так же как и с обычными методами - 2
статических метода могут иметь одинаковое имя, если количество их параметров или типов
различается.
Переопределен - нет. Выбор вызываемого статического метода происходит при
раннем связывании (на этапе компиляции, а не выполнения) и выполняться всегда будет
родительский метод, хотя синтаксически переопределение статического метода это вполне
корректная языковая конструкция.
В целом, к статическим полям и методам рекомендуется обращаться через имя
класса, а не объект.

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


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

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


С помощью ключевого слова super мы можем обратиться к любому члену
родительского класса - методу или полю, если они не определены с модификатором private.
[Link]();
16. Тип возвращаемого значения метода при перегрузке и
переопределении + модификаторы доступа.
Возможно ли при переопределении метода изменить: модификатор доступа,
возвращаемый тип, тип аргумента или их количество, имена аргументов или их
порядок; убирать, добавлять, изменять порядок следования элементов секции throws?
При переопределении метода сужать модификатор доступа не разрешается, т.к. это
приведет к нарушению принципа подстановки Барбары Лисков. Расширение уровня доступа
возможно.
Можно изменять все, что не мешает компилятору понять какой метод родительского
класса имеется в виду:
Изменять тип возвращаемого значения при переопределении метода разрешено
только в сторону сужения типа (вместо родительского класса - наследника).
При изменении типа, количества, порядка следования аргументов вместо
переопределения будет происходить overloading (перегрузка) метода.
Секцию throws метода можно не указывать, но стоит помнить, что она остаётся
действительной, если уже определена у метода родительского класса. Также, возможно
добавлять новые исключения, являющиеся наследниками от уже объявленных или
исключения RuntimeException. Порядок следования таких элементов при переопределении
значения не имеет.

17. Исключения (иерархия, обработка, finally, try с ресурсами,


Closeable vs. Autocloseable, проверяемые vs. непроверяемые,
supressed).
Опишите иерархию исключений.
Исключения делятся на несколько классов, но все они имеют общего предка — класс
Throwable, потомками которого являются классы Exception и Error.
Ошибки (Errors) представляют собой более серьезные проблемы, которые, согласно
спецификации Java, не следует обрабатывать в собственной программе, поскольку они
связаны с проблемами уровня JVM. Например, исключения такого рода возникают, если
закончилась память доступная виртуальной машине.
Исключения (Exceptions) являются результатом проблем в программе, которые в
принципе решаемы, предсказуемы и последствия которых возможно устранить внутри
программы. Например, произошло деление целого числа на ноль.

Какие виды исключений в Java вы знаете, чем они отличаются? Что такое checked и
unchecked exception?
В Java все исключения делятся на два типа:
 checked (контролируемые/проверяемые исключения) должны обрабатываться блоком
catch или описываться в сигнатуре метода (например throws IOException). Наличие
такого обработчика/модификатора сигнатуры проверяются на этапе компиляции;
 unchecked (неконтролируемые/непроверяемые исключения), к которым относятся
ошибки Error (например OutOfMemoryError), обрабатывать которые не рекомендуется и
исключения времени выполнения, представленные классом RuntimeException и его
наследниками (например NullPointerException), которые могут не обрабатываться
блоком catch и не быть описанными в сигнатуре метода.

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


Это оператор throw:
throw new Exception();

О чем говорит ключевое слово throws?


Модификатор throws прописывается в сигнатуре метода и указывает на то, что метод
потенциально может выбросить исключение с указанным типом.
Как написать собственное («пользовательское») исключение?
Необходимо унаследоваться от базового класса требуемого типа исключений
(например от Exception или RuntimeException).

Какие существуют unchecked exception?


Наиболее часто встречающиеся: ArithmeticException, ClassCastException,
ConcurrentModificationException, IllegalArgumentException, IllegalStateException,
IndexOutOfBoundsException, NoSuchElementException, NullPointerException,
UnsupportedOperationException.

Что представляет из себя ошибки класса Error?


Ошибки класса Error представляют собой наиболее серьёзные проблемы уровня JVM.
Например, исключения такого рода возникают, если закончилась память доступная
виртуальной машине. Обрабатывать такие ошибки не запрещается, но делать этого не
рекомендуется.

Что вы знаете о OutOfMemoryError?


OutOfMemoryError выбрасывается, когда виртуальная машина Java не может создать
(разместить) объект из-за нехватки памяти, а сборщик мусора не может высвободить
достаточное её количество.
Область памяти, занимаемая java процессом, состоит из нескольких частей. Тип
OutOfMemoryError зависит от того, в какой из них не хватило места:
 [Link]: Java heap space: Не хватает места в куче, а
именно, в области памяти в которую помещаются объекты, создаваемые в
приложении программно. Обычно проблема кроется в утечке памяти. Размер
задается параметрами -Xms и -Xmx.
 [Link]: PermGen space: (до версии Java 8) Данная ошибка
возникает при нехватке места в Permanent области, размер которой задается
параметрами -XX:PermSize и -XX:MaxPermSize.
 [Link]: GC overhead limit exceeded: Данная ошибка может
возникнуть как при переполнении первой, так и второй областей. Связана она с
тем, что памяти осталось мало и сборщик мусора постоянно работает, пытаясь
высвободить немного места. Данную ошибку можно отключить с помощью
параметра -XX:-UseGCOverheadLimit.
 [Link]: unable to create new native thread: Выбрасывается,
когда нет возможности создавать новые потоки.

Опишите работу блока try-catch-finally.


try — данное ключевое слово используется для отметки начала блока кода, который
потенциально может привести к ошибке. catch — ключевое слово для отметки начала блока
кода, предназначенного для перехвата и обработки исключений в случае их возникновения.
finally — ключевое слово для отметки начала блока кода, который является дополнительным.
Этот блок помещается после последнего блока catch. Управление передаётся в блок finally в
любом случае, было выброшено исключение или нет.
Общий вид конструкции для обработки исключительной ситуации выглядит
следующим образом:
try {
//код, который потенциально может привести к исключительной ситуации
}
catch(SomeException e ) { //в скобках указывается класс конкретной ожидаемой
ошибки
//код обработки исключительной ситуации
}
finally {
//необязательный блок, код которого выполняется в любом случае
}
Что такое механизм try-with-resources?
Данная конструкция, которая появилась в Java 7, позволяет использовать блок try-
catch не заботясь о закрытии ресурсов, используемых в данном сегменте кода. Ресурсы
объявляются в скобках сразу после try, а компилятор уже сам неявно создает секцию finally, в
которой и происходит освобождение занятых в блоке ресурсов. Под ресурсами
подразумеваются сущности, реализующие интерфейс [Link].
Общий вид конструкции:
try(/*объявление ресурсов*/) {
//...
} catch(Exception ex) {
//...
} finally {
//...
}
Стоит заметить, что блоки catch и явный finally выполняются уже после того, как
закрываются ресурсы в неявном finally.

Возможно ли использование блока try-finally (без catch)?


Такая запись допустима, но смысла в такой записи не так много, всё же лучше иметь
блок catch, в котором будет обрабатываться необходимое исключение.
к оглавлению
Может ли один блок catch отлавливать сразу несколько исключений?
В Java 7 стала доступна новая языковая конструкция, с помощью которой можно
перехватывать несколько исключений одним блоком catch:
try {
//...
} catch(IOException | SQLException ex) {
//...
}

Всегда ли исполняется блок finally?


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

Существуют ли ситуации, когда блок finally не будет выполнен?


Например, когда JVM «умирает» - в такой ситуации finally недостижим и не будет
выполнен, так как происходит принудительный системный выход из программы:
try {
[Link](0);
} catch(Exception e) {
[Link]();
} finally { }

Либо, в других случаях:


 Если вы вызовете [Link]().halt(exitStatus)
 Если JVM выйдет из строя первым
 Если JVM достигает бесконечного цикла (или какого-либо другого не прерываемого, не
завершающего оператора) в блоке try или catch
 Если OS принудительно завершает процесс JVM; например, kill -9 <pid> на UNIX
 Если хост-система умирает; например, сбой питания, аппаратная ошибка, паника OS и
так далее
 Если блок finally будет выполняться потоком демона, а все остальные потоки, не
являющиеся демонами, выйдут до вызова finally
Может ли метод main() выбросить исключение во вне и если да, то где будет
происходить обработка данного исключения?
Может и оно будет передано в виртуальную машину Java (JVM).

Предположим, есть метод, который может выбросить IOException и


FileNotFoundException в какой последовательности должны идти блоки catch? Сколько
блоков catch будет выполнено?
Общее правило: обрабатывать исключения нужно от «младшего» к старшему. Т.е.
нельзя поставить в первый блок catch(Exception ex) {}, иначе все дальнейшие блоки catch()
уже ничего не смогут обработать, т.к. любое исключение будет соответствовать обработчику
catch(Exception ex).
Таким образом, исходя из факта, что FileNotFoundException extends IOException
сначала нужно обработать FileNotFoundException, а затем уже IOException:
void method() {
try {
//...
} catch (FileNotFoundException ex) {
//...
} catch (IOException ex) {
//...
}
}

18. Garbage collector (виды в HotSpot, типы ссылок, finalize()).


В Java существует 4 типа ссылок: сильные (strong reference), мягкие (SoftReference),
слабые (WeakReference) и фантомные (PhantomReference). Особенности каждого типа
ссылок связаны с работой Garbage Collector. Если объект можно достичь только с помощью
цепочки WeakReference (то есть на него отсутствуют сильные и мягкие ссылки), то данный
объект будет помечен на удаление.
Отличия между слабыми, мягкими, фантомными и обычными ссылками в Java
«Слабые» ссылки и «мягкие» ссылки (WeakReference, SoftReference) были добавлены
в Java API давно, но не каждый программист знаком с ними. Это свидетельствует о пробеле в
понимании где и как их использовать. Ссылочные классы особенно важны в контексте сборки
мусора . Как все мы знаем сборщик мусора сам освобождает память занимаемую объектами,
но не все программисты знают что решение об освобождении памяти он принимает исходя из
типа имеющихся на объект ссылок.
Главное отличие SoftReference от WeakReference в том как сборщик с ними будет
работать. Он может удалить объект в любой момент если на него указывают только weak
ссылки, с другой стороны объекты с soft ссылкой будут собраны только когда JVM очень
нужна память. Благодаря таким особенностям ссылочных классов каждый из них имеет свое
применение. SoftReference можно использовать для реализации кэшей и когда JVM
понадобится память она освободит ее за счет удаления таких объектов. А WeakReference
отлично подойдут для хранения метаданных, например для хранения ссылки на ClassLoader.
Если нет классов для загрузки то нет смысла хранить ссылку на ClassLoader, слабая ссылка
делает ClassLoader доступным для удаления как только мы назначим ее вместо крепкой
ссылки (Strong reference). В этой статье мы рассмотрим отличия типов ссылок в том числе
Strong reference и Phantom reference (фантомная ссылка).
WeakReference vs SoftReference в Java
Для тех кто не знает есть 4 вида ссылок:
 Strong reference
 Weak Reference
 Soft Reference
 Phantom Reference
Strong ссылка самая простая, так как мы используем ее в программировании изо дня в
день, например в коде вида String s = “abc” переменная s это и есть strong ссылка.
Любой объект что имеет strong ссылку запрещен для удаления сборщиком мусора.
Разумеется что это объекты которые нужны Java программе. Слабые ссылки представлены
классом [Link], вы можете определить слабую ссылку так:

Counter counter = new Counter(); // strong reference


WeakReference weakCounter = new WeakReference(counter); //weak reference
counter = null; // now Counter object is eligible for garbage collection

Теперь, как только вы присвоили strong ссылке counter значение null (counter = null),
тот объект что создан в первой строке становится доступным для удаления сборщиком
мусора, потому что он больше не имеет strong ссылки. Cозданная Weak ссылка weakCounter
не может предотвратить удаление сборщиком объекта Counter. С другой стороны если бы это
была Soft ссылка, объект типа Counter не был бы удален до тех пор пока JVM не нуждалась
бы в памяти особенно сильно. Soft ссылки в Java представлены классом
[Link]. Пример создания SoftReference в Java

Counter prime = new Counter(); // prime holds a strong reference


SoftReference soft = new SoftReference(prime) ; //soft reference variable
has SoftReference to Counter Object
prime = null; // now Counter object is eligible for garbage collection
but only be collected when JVM absolutely needs memory

После обнуления strong ссылки (в 3-ей строке) на объект Counter останется только 1
мягкая ссылка которая не сможет предотвратить удаление этого объекта сборщиком мусора,
но в отличие от weak ссылки сможет отложить этот процесс до тех пор пока не появится
острая нехватка памяти. Учитывая это отличие soft ссылки от weak, первая больше подходит
для кэшей, а weak для метаданных. Хорошим примером служит класс WeakHashMap который
является наследником интерфейса Map как и классы HashMap или TreeMap, но с одной
отличительной особенностью. WeakHashMap оборачивает ключи как weak ссылки, что
означает что как только не осталось strong ссылок на объект, weak ссылки которые
расположены внутри WeakHashMap не спасут от сборщика мусора.

Фантомные ссылки - третий тип ссылок, доступных в пакете [Link]. Phantom


ссылки представлены классом [Link]. Объект на который указывают
только phantom ссылки может быть удален сборщиком в любой момент. Phantom ссылка
создается точно так же как weak или soft.

DigitalCounter digit = new DigitalCounter(); // digit reference variable


has strong reference
PhantomReference phantom = new PhantomReference(digit); // phantom
reference
digit = null;

Как только вы обнулите strong ссылки на объект DigitalCounter, сборщик мусора удалит
его в любой момент, так как теперь на него ведут только phantom ссылки.

Кроме классов WeakReference, SoftReference, PhantomReference, WeakHashMap,


полезно знать о классе ReferenceQueue. Вы можете воспользоваться этим классом при
создании объекта класса WeakReference, SoftReference или PhantomReference:

ReferenceQueue refQueue = new ReferenceQueue(); //reference will be


stored in this queue for cleanup
DigitalCounter digit = new DigitalCounter();
PhantomReference phantom = new PhantomReference(digit, refQueue);

Ссылка на объект будет добавлена в ReferenceQueue и вы сможете контролировать


состояние ссылок путем опроса ReferenceQueue. Жизненный цикл Object хорошо представлен
на этой диаграмме:

Вот и все отличия между weak и soft ссылками в Java. Так же мы познакомились с phantom
ссылками, классом WeakHashMap и ReferenceQueue. Правильное использование ссылок
поможет при сборке мусора и в результате мы получим более гибкое управление памятью в
Java.
[Link]
obihchnihmi-ssihlkami-v-java

Для чего нужен сборщик мусора?


Сборщик мусора (Garbage Collector) должен делать всего две вещи:
Находить мусор - неиспользуемые объекты. (Объект считается неиспользуемым, если
ни одна из сущностей в коде, выполняемом в данный момент, не содержит ссылок на него,
либо цепочка ссылок, которая могла бы связать объект с некоторой сущностью приложения,
обрывается);
Освобождать память от мусора.
Существует два подхода к обнаружению мусора:
 Reference counting;
 Tracing
Reference counting (подсчет ссылок). Суть этого подхода состоит в том, что каждый
объект имеет счетчик. Счетчик хранит информацию о том, сколько ссылок указывает на
объект. Когда ссылка уничтожается, счетчик уменьшается. Если значение счетчика равно
нулю, - объект можно считать мусором. Главным минусом такого подхода является сложность
обеспечения точности счетчика. Также при таком подходе сложно выявлять циклические
зависимости (когда два объекта указывают друг на друга, но ни один живой объект на них не
ссылается), что приводит к утечкам памяти.
Главная идея подхода Tracing (трассировка) состоит в утверждении, что живыми могут
считаться только те объекты, до которых мы можем добраться из корневых точек (GC Root) и
те объекты, которые доступны с живого объекта. Всё остальное - мусор.
Существует 4 типа корневых точки:
 Локальные переменные и параметры методов;
 Потоки;
 Статические переменные;
 Ссылки из JNI.
Самое простое java приложение будет иметь корневые точки:
 Локальные переменные внутри main() метода и параметры main() метода;
 Поток который выполняет main();
 Статические переменные класса, внутри которого находится main() метод.
Таким образом, если мы представим все объекты и ссылки между ними как дерево, то
нам нужно будет пройти с корневых узлов (точек) по всем ребрам. При этом узлы, до которых
мы сможем добраться - не мусор, все остальные - мусор. При таком подходе циклические
зависимости легко выявляются. HotSpot VM использует именно такой подход.
Для очистки памяти от мусора существуют два основных метода:
 Copying collectors
 Mark-and-sweep
При copying collectors подходе память делится на две части «from-space» и «to-space»,
при этом сам принцип работы такой:
Объекты создаются в «from-space»;
Когда «from-space» заполняется, приложение приостанавливается;
Запускается сборщик мусора. Находятся живые объекты в «from-space» и копируются
в «to-space»;
Когда все объекты скопированы «from-space» полностью очищается;
«to-space» и «from-space» меняются местами.
Главный плюс такого подхода в том, что объекты плотно забивают память. Минусы
подхода:
Приложение должно быть остановлено на время, необходимое для полного
прохождения цикла сборки мусора;
В худшем случае (когда все объекты живые) «frorm-space» и «to-space» будут обязаны
быть одинакового размера.
Алгоритм работы mark-and-sweep можно описать так:
 Объекты создаются в памяти;
 В момент, когда нужно запустить сборщик мусора приложение
приостанавливается;
 Сборщик проходится по дереву объектов, помечая живые объекты;
 Сборщик проходится по всей памяти, находя все не отмеченные куски памяти и
сохраняя их в «free list»;
 Когда новые объекты начинают создаваться они создаются в памяти доступной
во «free list».
Минусы этого способа:
 Приложение не работает пока происходит сборка мусора;
 Время остановки напрямую зависит от размеров памяти и количества
объектов;
 Если не использовать «compacting» память будет использоваться не
эффективно.
Сборщики мусора HotSpot VM используют комбинированный подход Generational
Garbage Collection, который позволяет использовать разные алгоритмы для разных этапов
сборки мусора. Этот подход опирается на том, что:
 большинство создаваемых объектов быстро становятся мусором;
 существует мало связей между объектами, которые были созданы в прошлом и
только что созданными объектами.

Как работает сборщик мусора?


Механизм сборки мусора - это процесс освобождения места в куче, для возможности
добавления новых объектов.
Объекты создаются посредством оператора new, тем самым присваивая объекту
ссылку. Для окончания работы с объектом достаточно просто перестать на него ссылаться,
например присвоив переменной ссылку на другой объект или значение null; прекратить
выполнение метода, чтобы его локальные переменные завершили свое существование
естественным образом. Объекты, ссылки на которые отсутствуют, принято называть мусором
(garbage), который будет удален.
Виртуальная машина Java, применяя механизм сборки мусора, гарантирует, что любой
объект, обладающий ссылками, остается в памяти — все объекты, которые недостижимы из
исполняемого кода, ввиду отсутствия ссылок на них, удаляются с высвобождением
отведенной для них памяти. Точнее говоря, объект не попадает в сферу действия процесса
сборки мусора, если он достижим посредством цепочки ссылок, начиная с корневой (GC Root)
ссылки, т.е. ссылки, непосредственно существующей в выполняемом коде.
Память освобождается сборщиком мусора по его собственному «усмотрению».
Программа может успешно завершить работу, не исчерпав ресурсов свободной памяти или
даже не приблизившись к этой черте и поэтому ей так и не потребуются «услуги» сборщика
мусора.
Мусор собирается системой автоматически, без вмешательства пользователя или
программиста, но это не значит, что этот процесс не требует внимания вовсе. Необходимость
создания и удаления большого количества объектов существенным образом сказывается на
производительности приложений и если быстродействие программы является важным
фактором, следует тщательно обдумывать решения, связанные с созданием объектов, — это,
в свою очередь, уменьшит и объем мусора, подлежащего утилизации.

Какие разновидности сборщиков мусора реализованы в виртуальной машине HotSpot?


Java HotSpot VM предоставляет разработчикам на выбор четыре различных сборщика
мусора:
 Serial (последовательный) — самый простой вариант для приложений с
небольшим объемом данных и не требовательных к задержкам. На данный
момент используется сравнительно редко, но на слабых компьютерах может
быть выбран виртуальной машиной в качестве сборщика по умолчанию.
Использованiие Serial GC включается опцией -XX:+UseSerialGC.
 Parallel (параллельный) — наследует подходы к сборке от последовательного
сборщика, но добавляет параллелизм в некоторые операции, а также
возможности по автоматической подстройке под требуемые параметры
производительности. Параллельный сборщик включается опцией -XX:
+UseParallelGC.
 Concurrent Mark Sweep (CMS) — нацелен на снижение максимальных задержек
путем выполнения части работ по сборке мусора параллельно с основными
потоками приложения. Подходит для работы с относительно большими
объемами данных в памяти. Использование CMS GC включается опцией -XX:
+UseConcMarkSweepGC.
 Garbage-First (G1) — создан для замены CMS, особенно в серверных
приложениях, работающих на многопроцессорных серверах и оперирующих
большими объемами данных. G1 включается опцией Java -XX:+UseG1GC.

Опишите алгоритм работы какого-нибудь сборщика мусора реализованного в


виртуальной машине HotSpot.
Serial Garbage Collector (Последовательный сборщик мусора) был одним из первых
сборщиков мусора в HotSpot VM. Во время работы этого сборщика приложения
приостанавливается и продолжает работать только после прекращение сборки мусора.
Память приложения делится на три пространства:
Young generation. Объекты создаются именно в этом участке памяти.
Old generation. В этот участок памяти перемещаются объекты, которые переживают
«minor garbage collection».
Permanent generation. Тут хранятся метаданные об объектах, Class data sharing (CDS),
пул строк (String pool). Permanent область делится на две: только для чтения и для чтения-
записи. Очевидно, что в этом случае область только для чтения не чистится сборщиком
мусора никогда.
Область памяти Young generation состоит из трёх областей: Eden и двух меньших по
размеру Survivor spaces - To space и From space. Большинство объектов создаются в области
Eden, за исключением очень больших объектов, которые не могут быть размещены в ней и
поэтому сразу размещаются в Old generation. В Survivor spaces перемещаются объекты,
которые пережили по крайней мере одну сборку мусора, но ещё не достигли порога
«старости» (tenuring threshold), чтобы быть перемещенными в Old generation.
Когда Young generation заполняется, то в этой области запускается процесс лёгкой
сборки (minor collection), в отличие от процесса сборки, проводимого над всей кучей (full
collection). Он происходит следующим образом: в начале работы одно из Survivor spaces - To
space, является пустым, а другое - From space, содержит объекты, пережившие предыдущие
сборки. Сборщик мусора ищет живые объекты в Eden и копирует их в To space, а затем
копирует туда же и живые «молодые» (то есть не пережившие еще заданное число сборок
мусора) объекты из From space. Старые объекты из From space перемещаются в Old
generation. После лёгкой сборки From space и To space меняются ролями, область Eden
становится пустой, а число объектов в Old generation увеличивается.
Если в процессе копирования живых объектов To space переполняется, то оставшиеся
живые объекты из Eden и From space, которым не хватило места в To space, будут
перемещены в Old generation, независимо от того, сколько сборок мусора они пережили.
Поскольку при использовании этого алгоритма сборщик мусора просто копирует все
живые объекты из одной области памяти в другую, то такой сборщик мусора называется
copying (копирующий). Очевидно, что для работы копирующего сборщика мусора у
приложения всегда должна быть свободная область памяти, в которую будут копироваться
живые объекты, и такой алгоритм может применяться для областей памяти сравнительно
небольших по отношению к общему размеру памяти приложения. Young generation как раз
удовлетворяет этому условию (по умолчанию на машинах клиентского типа эта область
занимает около 10% кучи (значение может варьироваться в зависимости от платформы)).
Однако, для сборки мусора в Old generation, занимающем большую часть всей памяти,
используется другой алгоритм.
В Old generation сборка мусора происходит с использованием алгоритма mark-sweep-
compact, который состоит из трёх фаз. В фазе Mark (пометка) сборщик мусора помечает все
живые объекты, затем, в фазе Sweep (очистка) все не помеченные объекты удаляются, а в
фазе Сompact (уплотнение) все живые объекты перемещаются в начало Old generation, в
результате чего свободная память после очистки представляет собой непрерывную область.
Фаза уплотнения выполняется для того, чтобы избежать фрагментации и упростить процесс
выделения памяти в Old generation.
Когда свободная память представляет собой непрерывную область, то для выделения
памяти под создаваемый объект можно использовать очень быстрый (около десятка
машинных инструкций) алгоритм bump-the-pointer: адрес начала свободной памяти хранится в
специальном указателе, и когда поступает запрос на создание нового объекта, код проверяет,
что для нового объекта достаточно места, и, если это так, то просто увеличивает указатель на
размер объекта.
Последовательный сборщик мусора отлично подходит для большинства приложений,
использующих до 200 мегабайт кучи, работающих на машинах клиентского типа и не
предъявляющих жёстких требований к величине пауз, затрачиваемых на сборку мусора. В то
же время модель «stop-the-world» может вызвать длительные паузы в работе приложения при
использовании больших объёмов памяти. Кроме того, последовательный алгоритм работы не
позволяет оптимально использовать вычислительные ресурсы компьютера и
последовательный сборщик мусора может стать узким местом при работе приложения на
многопроцессорных машинах.

Что такое finalize()? Зачем он нужен?


Через вызов метода finalize() JVM реализуется функциональность аналогичная
функциональности деструкторов в С++, используемых для очистки памяти перед
возвращением управления операционной системе. Данный метод вызывается при
уничтожении объекта сборщиком мусора (garbage collector) и переопределяя finalize() можно
запрограммировать действия необходимые для корректного удаления экземпляра класса -
например, закрытие сетевых соединений, соединений с базой данных, снятие блокировок на
файлы и т.д.
После выполнения этого метода объект должен быть повторно собран сборщиком
мусора (и это считается серьезной проблемой метода finalize() т.к. он мешает сборщику
мусора освобождать память). Вызов этого метода не гарантируется, т.к. приложение может
быть завершено до того, как будет запущена сборка мусора.
Объект не обязательно будет доступен для сборки сразу же - метод finalize() может
сохранить куда-нибудь ссылку на объект. Подобная ситуация называется «возрождением»
объекта и считается антипаттерном. Главная проблема такого трюка - в том, что «возродить»
объект можно только 1 раз.

Что произойдет со сборщиком мусора, если выполнение метода finalize() требует


ощутимо много времени, или в процессе выполнения будет выброшено исключение?
Непосредственно вызов finalize() происходит в отдельном потоке Finalizer
([Link]), который создаётся при запуске виртуальной машины (в
статической секции при загрузке класса Finalizer). Методы finalize() вызываются
последовательно в том порядке, в котором были добавлены в список сборщиком мусора.
Соответственно, если какой-то finalize() зависнет, он подвесит поток Finalizer, но не сборщик
мусора. Это в частности означает, что объекты, не имеющие метода finalize(), будут исправно
удаляться, а вот имеющие будут добавляться в очередь, пока поток Finalizer не освободится,
не завершится приложение или не кончится память.
То же самое применимо и выброшенным в процессе finalize() исключениям: метод
runFinalizer() у потока Finalizer игнорирует все исключения выброшенные в момент
выполнения finalize(). Таким образом возникновение исключительной ситуации никак не
скажется на работоспособности сборщика мусора.

Чем отличаются final, finally и finalize()?


Модификатор final:
 Класс не может иметь наследников;
 Метод не может быть переопределен в классах наследниках;
 Поле не может изменить свое значение после инициализации;
 Локальные переменные не могут быть изменены после присвоения им
значения;
 Параметры методов не могут изменять своё значение внутри метода.
Оператор finally гарантирует, что определенный в нём участок кода будет выполнен
независимо от того, какие исключения были возбуждены и перехвачены в блоке try-catch.
Метод finalize() вызывается перед тем как сборщик мусора будет проводить удаление
объекта.

19. Heap vs. stack.


Что такое Heap и Stack память в Java? Какая разница между ними?
Heap (куча) используется Java Runtime для выделения памяти под объекты и классы.
Создание нового объекта также происходит в куче. Это же является областью работы
сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут
ссылаться из любой части приложения.
Stack (стек) это область хранения данных также находящееся в общей оперативной
памяти (RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок,
который содержит примитивы и ссылки на другие объекты в методе. Как только метод
заканчивает работу, блок также перестает использоваться, тем самым предоставляя доступ
для следующего метода. Размер стековой памяти намного меньше объема памяти в куче.
Стек в Java работает по схеме LIFO (Последний-зашел-Первый-вышел)
Различия между Heap и Stack памятью:
 Куча используется всеми частями приложения в то время как стек используется
только одним потоком исполнения программы.
 Всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека
содержится лишь ссылка на него. Память стека содержит только локальные
переменные примитивных типов и ссылки на объекты в куче.
 Объекты в куче доступны с любой точке программы, в то время как стековая
память не может быть доступна для других потоков.
 Стековая память существует лишь какое-то время работы программы, а память
в куче живет с самого начала до конца работы программы.
 Если память стека поiлностью занята, то Java Runtime бросает исключение
[Link]. Если заполнена память кучи, то бросается
исключение [Link]: Java Heap Space.
 Размер памяти стека намного меньше памяти в куче.
 Из-за простоты распределения памяти, стековая память работает намного
быстрее кучи.
Для определения начального и максимального размера памяти в куче используются -
Xms и -Xmx опции JVM. Для стека определить размер памяти можно с помощью опции -Xss.

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


экземпляры ссылочных типов данных в куче?
Не совсем. Примитивное поле экземпляра класса хранится не в стеке, а в куче. Любой
объект (всё, что явно или неявно создаётся при помощи оператора new) хранится в куче.

20. Передача переменных в методы.


Каким образом передаются переменные в методы, по значению или по ссылке?
В Java параметры всегда передаются только по значению, что определяется как
«скопировать значение и передать копию». С примитивами это будет копия содержимого. Со
ссылками - тоже копия содержимого, т.е. копия ссылки. При этом внутренние члены
ссылочных типов через такую копию изменить возможно, а вот саму ссылку, указывающую на
экземпляр - нет.

21. Приведение типов (понижение, повышение типа,


ClassCastException).
Расскажите про приведение типов. Что такое понижение и повышение типа?
Java является строго типизированным языком программирования, а это означает, то
что каждое выражение и каждая переменная имеет строго определенный тип уже на момент
компиляции. Однако определен механизм приведения типов (casting) - способ
преобразования значения переменной одного типа в значение другого типа.
В Java существуют несколько разновидностей приведения:
Тождественное (identity). Преобразование выражения любого типа к точно такому же
типу всегда допустимо и происходит автоматически.
Расширение (повышение, upcasting) примитивного типа (widening primitive). Означает,
что осуществляется переход от менее емкого типа к более ёмкому. Например, от типа byte
(длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что
новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом
типе и таким образом не происходит потери данных. Этот тип приведения всегда допустим и
происходит автоматически.
Сужение (понижение, downcasting) примитивного типа (narrowing primitive). Означает,
что переход осуществляется от более емкого типа к менее емкому. При таком
преобразовании есть риск потерять данные. Например, если число типа int было больше 127,
то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое
преобразование должно совершаться явным образом, при этом все старшие биты, не
умещающиеся в новом типе, просто отбрасываются - никакого округления или других
действий для получения более корректного результата не производится.
Расширение объектного типа (widening reference). Означает неявное восходящее
приведение типов или переход от более конкретного типа к менее конкретному, т.е. переход
от потомка к предку. Разрешено всегда и происходит автоматически.
Сужение объектного типа (narrowing reference). Означает нисходящее приведение, то
есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная
является подтипом приводимого типа. При несоответствии типов в момент выполнения
выбрасывается исключение ClassCastException. Требует явного указания типа.
Преобразование к строке (to String). Любой тип может быть приведен к строке, т.е. к
экземпляру класса String.
Запрещенные преобразования (forbidden). Не все приведения между произвольными
типами допустимы. Например, к запрещенным преобразованиям относятся приведения от
любого ссылочного типа к примитивному и наоборот (кроме преобразования к строке). Кроме
того невозможно привести друг к другу классы находящиеся на разных ветвях дерева
наследования и т.п.
При приведении ссылочных типов с самим объектом ничего не происходит, - меняется
лишь тип ссылки, через которую происходит обращение к объекту.
Для проверки возможности приведения нужно воспользоваться оператором instanceof:
Parent parent = new Child();
if (parent instanceof Child) {
Child child = (Child) parent;
}

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


ClassCastException (потомок RuntimeException) - исключение, которое будет
выброшено при ошибке приведения типа.
Что такое autoboxing («автоупаковка») в Java и каковы правила упаковки примитивных
типов в классы-обертки?
Автоупаковка - это механизм неявной инициализации объектов классов-оберток (Byte,
Short, Integer, Long, Float, Double, Character, Boolean) значениями соответствующих им
исходных примитивных типов (byte, short, int...), без явного использования конструктора
класса.
Автоупаковка происходит при прямом присваивании примитива классу-обертке (с
помощью оператора =), либо при передаче примитива в параметры метода (типа класса-
обертки).
Автоупаковке в классы-обертки могут быть подвергнуты как переменные примитивных
типов, так и константы времени компиляции (литералы и final-примитивы). При этом литералы
должны быть синтаксически корректными для инициализации переменной исходного
примитивного типа.
Автоупаковка переменных примитивных типов требует точного соответствия типа
исходного примитива типу класса-обертки. Например, попытка упаковать переменную типа
byte в Short, без предварительного явного приведения byte в short вызовет ошибку
компиляции.
Автоупаковка констант примитивных типов допускает более широкие границы
соответствия. В этом случае компилятор способен предварительно осуществлять неявное
расширение/сужение типа примитивов:
неявное расширение/сужение исходного типа примитива до типа примитива
соответствующего классу-обертке (для преобразования int в Byte, сначала компилятор
самостоятельно неявно сужает int к byte)
автоупаковку примитива в соответствующий класс-обертку. Однако, в этом случае
существуют два дополнительных ограничения: a) присвоение примитива обертке может
производится только оператором = (нельзя передать такой примитив в параметры метода без
явного приведения типов) b) тип левого операнда не должен быть старше чем Character, тип
правого не должен старше, чем int: допустимо расширение/сужение byte в/из short, byte в/из
char, short в/из char и только сужение byte из int, short из int, char из int. Все остальные
варианты требуют явного приведения типов).
Дополнительной особенностью целочисленных классов-оберток созданных
автоупаковкой констант в диапазоне -128 ... +127 является то, что они кэшируются JVM.
Поэтому такие обертки с одинаковыми значениями будут являться ссылками на один объект.

22. Класс Object (нативные методы, JNA, equals(), hashcode(),


toString(), getClass(), clone(), notify(), notifyAll(), 3 вида wait(), finalize()).
Что такое класс Object? Какие в нем есть методы?
Object это базовый класс для всех остальных объектов в Java. Любой класс
наследуется от Object и, соответственно, наследуют его методы:
 public boolean equals(Object obj) – служит для сравнения объектов по значению;
 int hashCode() – возвращает hash код для объекта;
 String toString() – возвращает строковое представление объекта;
 Class getClass() – возвращает класс объекта во время выполнения;
 protected Object clone() – создает и возвращает копию объекта;
 void notify() – возобновляет поток, ожидающий монитор;
 void notifyAll() – возобновляет все потоки, ожидающие монитор;
 void wait() – остановка вызвавшего метод потока до момента пока другой поток
не вызовет метод notify() или notifyAll() для этого объекта;
 void wait(long timeout) – остановка вызвавшего метод потока на определённое
время или пока другой поток не вызовет метод notify() или notifyAll() для этого
объекта;
 void wait(long timeout, int nanos) – остановка вызвавшего метод потока на
определённое время или пока другой поток не вызовет метод notify() или
notifyAll() для этого объекта;
 protected void finalize() – может вызываться сборщиком мусора в момент
удаления объекта при сборке мусора.
23. String (immutable, string pool, intern(), StringBuilder, StringBuffer,
String in switch).
Что такое «пул строк»?
Пул строк – это набор строк хранящийся в Heap.
Пул строк возможен благодаря неизменяемости строк в Java и реализации идеи
интернирования строк;
Пул строк помогает экономить память, но по этой же причине создание строки
занимает больше времени;
Когда для создания строки используются ", то сначала ищется строка в пуле с таким
же значением, если находится, то просто возвращается ссылка, иначе создается новая строка
в пуле, а затем возвращается ссылка на неё;
При использовании оператора new создается новый объект String. Затем при помощи
метода intern() эту строку можно поместить в пул или же получить из пула ссылку на другой
объект String с таким же значением;
Пул строк является примером паттерна «Приспособленец» (Flyweight).

Какие есть особенности класса String?


 Это неизменяемый (immutable) и финализированный тип данных;
 Все объекты класса String JVM хранит в пуле строк;
 Объект класса String можно получить используя двойные кавычки;
 Можно использовать оператор + для конкатенации строк;
 Начиная с Java 7 строки можно использовать в конструкции switch.

Почему String неизменяемый и финализированный класс?


Есть несколько преимуществ в неизменности строк:
Пул строк возможен только потому, что строка неизменяемая, таким образом
виртуальная машина сохраняет больше свободного места в Heap, поскольку разные
строковые переменные указывают на одну и ту же переменную в пуле. Если бы строка была
изменяемой, то интернирование строк не было бы возможным, потому что изменение
значения одной переменной отразилось бы также и на остальных переменных, ссылающихся
на эту строку.
Если строка будет изменяемой, тогда это станет серьезной угрозой безопасности
приложения. Например, имя пользователя базы данных и пароль передаются строкой для
получения соединения с базой данных и в программировании сокетов реквизиты хоста и
порта передаются строкой. Так как строка неизменяемая, её значение не может быть
изменено, в противном случае злоумышленник может изменить значение ссылки и вызвать
проблемы в безопасности приложения.
Неизменяемость позволяет избежать синхронизации: строки безопасны для
многопоточности и один экземпляр строки может быть совместно использован различными
потоками.
Строки используются classloader и неизменность обеспечивает правильность загрузки
класса.
Поскольку строка неизменяемая, её hashCode() кэшируется в момент создания и нет
необходимости рассчитывать его снова. Это делает строку отличным кандидатом для ключа в
HashMap т.к. его обработка происходит быстрее.

Почему char[] предпочтительнее String для хранения пароля?


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

Почему строка является популярным ключом в HashMap в Java?


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

Что делает метод intern() в классе String?


Метод intern() используется для сохранения строки в пуле строк или получения ссылки,
если такая строка уже находится в пуле.

Можно ли использовать строки в конструкции switch?


Да, начиная с Java 7 в операторе switch можно использовать строки, ранние версии
Java не поддерживают этого. При этом:
участвующие строки чувствительны к регистру;
используется метод equals() для сравнения полученного значения со значениями case,
поэтому во избежание NullPointerException стоит предусмотреть проверку на null.
согласно документации Java 7 для строк в switch, компилятор Java формирует более
эффективный байткод для строк в конструкции switch, чем для сцепленных условий if-else.

Какая основная разница между String, StringBuffer, StringBuilder?


Класс String является неизменяемым (immutable) - модифицировать объект такого
класса нельзя, можно лишь заменить его созданием нового экземпляра.
Класс StringBuffer изменяемый - использовать StringBuffer следует тогда, когда
необходимо часто модифицировать содержимое.
Класс StringBuilder был добавлен в Java 5 и он во всем идентичен классу StringBuffer
за исключением того, что он не синхронизирован и поэтому его методы выполняются
значительно быстрей.

Что такое StringJoiner?


Класс StringJoiner используется, чтобы создать последовательность строк,
разделенных разделителем с возможностью присоединить к полученной строке префикс и
суффикс:
StringJoiner joiner = new StringJoiner(".", "prefix-", "-suffix");
for (String s : "Hello the brave world".split(" ")) {
[Link](s);
}
[Link](joiner); //[Link]-suffix
к оглавлению

24. Клонирование объектов (поверхностная, глубокая копия,


Cloneable, конструктор копирования).
Расскажите про клонирование объектов.
Использование оператора присваивания не создает нового объекта, а лишь копирует
ссылку на объект. Таким образом, две ссылки указывают на одну и ту же область XSпамяти,
на один и тот же объект. Для создания нового объекта с таким же состоянием используется
клонирование объекта.
Класс Object содержит protected метод clone(), осуществляющий побитовое
копирование объекта производного класса. Однако сначала необходимо переопределить
метод clone() как public для обеспечения возможности его вызова. В переопределенном
методе следует вызвать базовую версию метода [Link](), которая и выполняет
собственно клонирование.
Чтобы окончательно сделать объект клонируемым, класс должен реализовать
интерфейс Cloneable. Интерфейс Cloneable не содержит методов, относится к маркерным
интерфейсам, а его реализация гарантирует, что метод clone() класса Object возвратит
точную копию вызвавшего его объекта с воспроизведением значений всех его полей. В
противном случае метод генерирует исключение CloneNotSupportedException. Следует
отметить, что при использовании этого механизма объект создается без вызова конструктора.
Это решение эффективно только в случае, если поля клонируемого объекта
представляют собой значения базовых типов и их обёрток или неизменяемых (immutable)
объектных типов. Если же поле клонируемого типа является изменяемым ссылочным типом,
то для корректного клонирования требуется другой подход. Причина заключается в том, что
при создании копии, поля оригинал и копия, представляют собой ссылку на один и тот же
объект. В этой ситуации следует также клонировать и сам объект поля класса.
Такое клонирование возможно только в случае, если тип атрибута класса также
реализует интерфейс Cloneable и переопределяет метод clone(). Так как, если это будет
иначе вызов метода невозможен из-за его недоступности. Отсюда следует, что если класс
имеет суперкласс, то для реализации механизма клонирования текущего класса-потомка
необходимо наличие корректной реализации такого механизма в суперклассе. При этом
следует отказаться от использования объявлений final для полей объектных типов по причине
невозможности изменения их значений при реализации клонирования.
Помимо встроенного механизма клонирования в Java для клонирования объекта
можно использовать:
Специализированный конструктор копирования - в классе описывается конструктор,
который принимает объект этого же класса и инициализирует поля создаваемого объекта
значениями полей переданного.
Фабричный метод - (Factory method), который представляет собой статический метод,
возвращающий экземпляр своего класса.
Механизм сериализации - сохранение и последующее восстановление объекта в/из
потока байтов.

В чем отличие между поверхностным и глубоким клонированием?


Поверхностное копирование копирует настолько малую часть информации об объекте,
насколько это возможно. По умолчанию, клонирование в Java является поверхностным, т.е.
класс Object не знает о структуре класса, которого он копирует. Клонирование такого типа
осуществляется JVM по следующим правилам:
Если класс имеет только члены примитивных типов, то будет создана совершенно
новая копия объекта и возвращена ссылка на этот объект.
Если класс помимо членов примитивных типов содержит члены ссылочных типов, то
тогда копируются ссылки на объекты этих классов. Следовательно, оба объекта будут иметь
одинаковые ссылки.
Глубокое копирование дублирует абсолютно всю информацию объекта:
Нет необходимости копировать отдельно примитивные данные;
Все члены ссылочного типа в оригинальном классе должны поддерживать
клонирование. Для каждого такого члена при переопределении метода clone() должен
вызываться [Link]();
Если какой-либо член класса не поддерживает клонирование, то в методе
клонирования необходимо создать новый экземпляр этого класса и скопировать каждый его
член со всеми атрибутами в новый объект класса, по одному.

Какой способ клонирования предпочтительней?


Наиболее безопасным и следовательно предпочтительным способом клонирования
является использование специализированного конструктора копирования:
Отсутствие ошибок наследования (не нужно беспокоиться, что у наследников появятся
новые поля, которые не будут склонированы через метод clone());
Поля для клонирования указываются явно;
Возможность клонировать даже final поля.

Почему метод clone() объявлен в классе Object, а не в интерфейсе Cloneable?


Метод clone() объявлен в классе Object с указанием модификатора native, чтобы
обеспечить доступ к стандартному механизму поверхностного копирования объектов.
Одновременно он объявлен и как protected, чтобы нельзя было вызвать этот метод у не
переопределивших его объектов. Непосредственно интерфейс Cloneable является
маркерным (не содержит объявлений методов) и нужен только для обозначения самого
факта, что данный объект готов к тому, чтобы быть клонированным. Вызов
переопределённого метода clone() у не Cloneable объекта вызовет выбрасывание
CloneNotSupportedException.

Дайте определение понятию «конструктор».


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

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


Если у какого-либо класса не определить конструктор, то компилятор генерирует
конструктор без аргументов - так называемый «конструктор по умолчанию».
public class ClassName() {}
Если у класса уже определен какой-либо конструктор, то конструктор по умолчанию
создан не будет и, если он необходим, его нужно описывать явно.

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


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

Где и как вы можете использовать приватный конструктор?


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

25. Сериализация объектов (transient, Serializable vs. Externalizable,


serialVersionUID, проблема сериализации синглтона).
Что такое «сериализация»?
Сериализация (Serialization) - процесс преобразования структуры данных в линейную
последовательность байтов для дальнейшей передачи или сохранения. Сериализованные
объекты можно затем восстановить (десериализовать).
В Java, согласно спецификации Java Object Serialization существует два стандартных
способа сериализации: стандартная сериализация, через использование интерфейса
[Link] и «расширенная» сериализация - [Link].
Сериализация позволяет в определенных пределах изменять класс. Вот наиболее
важные изменения, с которыми спецификация Java Object Serialization может справляться
автоматически:
 добавление в класс новых полей;
 изменение полей из статических в нестатические;
 изменение полей из транзитных в нетранзитные.
Обратные изменения (из нестатических полей в статические и из нетранзитных в
транзитные) или удаление полей требуют определенной дополнительной обработки в
зависимости от того, какая степень обратной совместимости необходима.

Опишите процесс сериализации/десериализации с использованием Serializable.


При использовании Serializable применяется алгоритм сериализации, который с
помощью рефлексии (Reflection API) выполняет:
запись в поток метаданных о классе, ассоциированном с объектом (имя класса,
идентификатор SerialVersionUID, идентификаторы полей класса);
рекурсивную запись в поток описания суперклассов до класса [Link] (не
включительно);
запись примитивных значений полей сериализуемого экземпляра, начиная с полей
самого верхнего суперкласса;
рекурсивную запись объектов, которые являются полями сериализуемого объекта.
При этом ранее сериализованные объекты повторно не сериализуются, что позволяет
алгоритму корректно работать с циклическими ссылками.
Для выполнения десериализации под объект выделяется память, после чего его поля
заполняются значениями из потока. Конструктор объекта при этом не вызывается. Однако при
десериализации будет вызван конструктор без параметров родительского несериализуемого
класса, а его отсутствие повлечет ошибку десериализации.

Как изменить стандартное поведение сериализации/десериализации?


Реализовать интерфейс [Link], который позволяет применение
пользовательской логики сериализации. Способ сериализации и десериализации
описывается в методах writeExternal() и readExternal(). Во время десериализации вызывается
конструктор без параметров, а потом уже на созданном объекте вызывается метод
readExternal.
Если у сериализуемого объекта реализован один из следующих методов, то механизм
сериализации будет использовать его, а не метод по умолчанию :
writeObject() - запись объекта в поток;
readObject() - чтение объекта из потока;
writeReplace() - позволяет заменить себя экземпляром другого класса перед записью;
readResolve() - позволяет заменить на себя другой объект после чтения.

Как исключить поля из сериализации?


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

Что обозначает ключевое слово transient?


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

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


При стандартной сериализации поля, имеющие модификатор static, не сериализуются.
Соответственно, после десериализации это поле значения не меняет. При использовании
реализации Externalizable сериализовать и десериализовать статическое поле можно, но не
рекомендуется этого делать, т.к. это может сопровождаться трудноуловимыми ошибками.
Поля с модификатором final сериализуются как и обычные. За одним исключением –
их невозможно десериализовать при использовании Externalizable, поскольку final поля
должны быть инициализированы в конструкторе, а после этого в readExternal() изменить
значение этого поля будет невозможно. Соответственно, если необходимо сериализовать
объект с final полем необходимо использовать только стандартную сериализацию.

Как не допустить сериализацию?


Чтобы не допустить автоматическую сериализацию можно переопределить private
методы для создания исключительной ситуации NotSerializableException.
private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException();
}

private void readObject(ObjectInputStream in) throws IOException {


throw new NotSerializableException();
}
Любая попытка записать или прочитать этот объект теперь приведет к возникновению
исключительной ситуации.

Как создать собственный протокол сериализации?


Для создания собственного протокола сериализации достаточно реализовать
интерфейс Externalizable, который содержит два метода:
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

Какая роль поля serialVersionUID в сериализации?


serialVersionUID используется для указании версии сериализованных данных.
Когда мы не объявляем serialVersionUID в нашем классе явно, среда выполнения Java
делает это за нас, но этот процесс чувствителен ко многим метаданным класса включая
количество полей, тип полей, модификаторы доступа полей, интерфейсов, которые
реализованы в классе и пр.
Рекомендуется явно объявлять serialVersionUID т.к. при добавлении, удалении
атрибутов класса динамически сгенерированное значение может измениться и в момент
выполнения будет выброшено исключение InvalidClassException.
private static final long serialVersionUID = 20161013L;

Когда стоит изменять значение поля serialVersionUID?


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

В чем проблема сериализации Singleton?


Проблема в том что после десериализации мы получим другой объект. Таким образом,
сериализация дает возможность создать Singleton еще раз, что недопустимо. Существует два
способа избежать этого:
явный запрет сериализации.
определение метода с сигнатурой (default/public/private/protected/) Object readResolve()
throws ObjectStreamException, назначением которого станет возврат замещающего объекта
вместо объекта, на котором он вызван.

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


Если есть необходимость выполнения контроля за значениями десериализованного
объекта, то можно использовать интерфейс ObjectInputValidation с переопределением метода
validateObject().
// Если вызвать метод validateObject() после десериализации объекта, то будет
вызвано исключение InvalidObjectException при значении возраста за пределами 39...60.
public class Person implements [Link],
[Link] {
...
@Override
public void validateObject() throws InvalidObjectException {
if ((age < 39) || (age > 60))
throw new InvalidObjectException("Invalid age");
}
}
Так же существуют способы подписывания и шифрования, позволяющие убедиться,
что данные не были изменены:
с помощью описания логики в writeObject() и readObject().
поместить в оберточный класс [Link] и/или
[Link]. Данные классы являются сериализуемыми, поэтому при
оборачивании объекта в SealedObject создается подобие «подарочной упаковки» вокруг
исходного объекта. Для шифрования необходимо создать симметричный ключ, управление
которым должно осуществляться отдельно. Аналогично, для проверки данных можно
использовать класс SignedObject, для работы с которым также нужен симметричный ключ,
управляемый отдельно.

26. Классы загрузчики и динамическая загрузка классов.


Расскажите про классы-загрузчики и про динамическую загрузку классов.
Основа работы с классами в Java — классы-загрузчики, обычные Java-объекты,
предоставляющие интерфейс для поиска и создания объекта класса по его имени во время
работы приложения.
В начале работы программы создается 3 основных загрузчика классов:
 базовый загрузчик (bootstrap/primordial). Загружает основные системные и
внутренние классы JDK (Core API - пакеты java.* ([Link] и [Link]) . Важно
заметить, что базовый загрузчик является «Изначальным» или «Корневым» и
частью JVM, вследствие чего его нельзя создать внутри кода программы.
 загрузчик расширений (extention). Загружает различные пакеты расширений,
которые располагаются в директории <JAVA_HOME>/lib/ext или другой
директории, описанной в системном параметре [Link]. Это позволяет
обновлять и добавлять новые расширения без необходимости
модифицировать настройки используемых приложений. Загрузчик расширений
реализован классом [Link]$ExtClassLoader.
 системный загрузчик (system/application). Загружает классы, пути к которым
указаны в переменной окружения CLASSPATH или пути, которые указаны в
командной строке запуска JVM после ключей -classpath или -cp. Системный
загрузчик реализован классом [Link]$AppClassLoader.
Загрузчики классов являются иерархическими: каждый из них (кроме базового) имеет
родительский загрузчик и в большинстве случаев, перед тем как попробовать загрузить класс
самостоятельно, он посылает вначале запрос родительскому загрузчику загрузить указанный
класс. Такое делегирование позволяет загружать классы тем загрузчиком, который находится
ближе всего к базовому в иерархии делегирования. Как следствие поиск классов будет
происходить в источниках в порядке их доверия: сначала в библиотеке Core API, потом в
папке расширений, потом в локальных файлах CLASSPATH.
Процесс загрузки класса состоит из трех частей:
 Loading – на этой фазе происходит поиск и физическая загрузка файла класса
в определенном источнике (в зависимости от загрузчика). Этот процесс
определяет базовое представление класса в памяти. На этом этапе такие
понятия как «методы», «поля» и т.д. пока не известны.
 Linking – процесс, который может быть разбит на 3 части:
Bytecode verification – проверка байт-кода на соответствие требованиям определенным
в спецификации JVM.
Class preparation – создание и инициализация необходимых структур, используемых
для представления полей, методов, реализованных интерфейсов и т.п., определенных в
загружаемом классе.
Resolving – загрузка набора классов, на которые ссылается загружаемый класс.
 Initialization – вызов статически х блоков инициализации и присваивание полям
класса значений по умолчанию.
Динамическая загрузка классов в Java имеет ряд особенностей:
 отложенная (lazy) загрузка и связывание классов. Загрузка классов
производится только при необходимости, что позволяет экономить ресурсы и
распределять нагрузку.
 проверка корректности загружаемого кода (type safeness). Все действия
связанные с контролем использования типов производятся только во время
загрузки класса, позволяя избежать дополнительной нагрузки во время
выполнения кода.
 программируемая загрузка. Пользовательский загрузчик полностью
контролирует процесс получения запрошенного класса — самому ли искать
байт-код и создавать класс или делегировать создание другому загрузчику.
Дополнительно существует возможность выставлять различные атрибуты
безопасности для загружаемых классов, позволяя таким образом работать с
кодом из ненадежных источников.
 множественные пространства имен. Каждый загрузчик имеет своё
пространство имён для создаваемых классов. Соответственно, классы,
загруженные двумя различными загрузчиками на основе общего байт-кода, в
системе будут различаться.
Существует несколько способов инициировать загрузку требуемого класса:
 явный: вызов [Link]() или [Link]() (по умолчанию
используется загрузчик, создавший текущий класс, но есть возможность и
явного указания загрузчика);
 неявный: когда для дальнейшей работы приложения требуется ранее не
использованный класс, JVM инициирует его загрузку.

27. Рефлексия.
Что такое Reflection?
Рефлексия (Reflection) - это механизм получения данных о программе во время ее
выполнения (runtime). В Java Reflection осуществляется с помощью Java Reflection API,
состоящего из классов пакетов [Link] и [Link].
Возможности Java Reflection API:
 Определение класса объекта;
 Получение информации о модификаторах класса, полях, методах,
конструкторах и суперклассах;
 Определение интерфейсов, реализуемых классом;
 Создание экземпляра класса;
 Получение и установка значений полей объекта;
 Вызов методов объекта;
 Создание нового массива.

28. Контракты equals() и hashcode(). Преимущество использования


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

Какими свойствами обладает порождаемое equals() отношение эквивалентности?


 Рефлексивность: для любой ссылки на значение x, [Link](x) вернет true;
 Симметричность: для любых ссылок на значения x и y, [Link](y) должно
вернуть true, тогда и только тогда, когда [Link](x) возвращает true.
 Транзитивность: для любых ссылок на значения x, y и z, если [Link](y) и
[Link](z) возвращают true, тогда и [Link](z) вернёт true;
 Непротиворечивость: для любых ссылок на значения х и у, если несколько раз
вызвать х.equals(y), постоянно будет возвращаться значение true либо
постоянно будет возвращаться значение false при условии, что никакая
информация, используемая при сравнении объектов, не поменялась.
Для любой ненулевой ссылки на значение х выражение х.equals(null) должно
возвращать false.

Правила переопределения метода [Link]().


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

Если equals() переопределен, есть ли какие-либо другие методы, которые следует


переопределить?
Равные объекты должны возвращать одинаковые хэш коды. При переопределении
equals() нужно обязательно переопределять и метод hashCode().

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


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

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


Реализация метода [Link]() сводится к проверке на равенство двух ссылок:
public boolean equals(Object obj) {
return (this == obj);
}
Реализация метода [Link]() описана как native, т.е. определенной не с
помощью Java кода и обычно возвращает адрес объекта в памяти:
public native int hashCode();

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


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

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


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

Могут ли у разных объектов быть одинаковые hashCode()?


Да, могут. Метод hashCode() не гарантирует уникальность возвращаемого значения.
Ситуация, когда у разных объектов одинаковые хэш коды называется коллизией. Вероятность
возникновения коллизии зависит от используемого алгоритма генерации хэш кода.
Почему хэш код в виде 31 * x + y предпочтительнее чем x + y?
Множитель создает зависимость значения хэш кода от очередности обработки полей,
что в итоге порождает лучшую хэш функцию.

29. Дженерики (инвариантность, ковариантность и контрвариантность,


raw types, wildcards, PECS, множественные ограничения, стирание
типов).
Что такое generics?
Generics - это технический термин, обозначающий набор свойств языка позволяющих
определять и использовать обобщенные типы и методы. Обобщенные типы или методы
отличаются от обычных тем, что имеют типизированные параметры.
Примером использования обобщенных типов может служить Java Collection
Framework. Так, класс LinkedList<E> - типичный обобщенный тип. Он содержит параметр E,
который представляет тип элементов, которые будут храниться в коллекции. Создание
объектов обобщенных типов происходит посредством замены параметризированных типов
реальными типами данных. Вместо того, чтобы просто использовать LinkedList, ничего не
говоря о типе элемента в списке, предлагается использовать точное указание типа
LinkedList<String>, LinkedList<Integer> и т.п.

Raw type - это имя интерфейса без указания параметризованного типа:


List list = new ArrayList(); // raw type
List<Integer> listIntgrs = new ArrayList<>(); // parameterized type

Стирание типов - суть заключается в том, что внутри класса не хранится никакой
информации о типе-параметре. Эта информация доступна только на этапе компиляции и
стирается (становится недоступной) в runtime.

Потоки ввода/вывода
30. [Link] vs. [Link] & File vs Path.
В чём заключается разница между IO и NIO?
Java IO (input-output) является потокоориентированным, а Java NIO (new/non-blocking
io) – буфер-ориентированным. Потокоориентированный ввод/вывод подразумевает
чтение/запись из потока/в поток одного или нескольких байт в единицу времени поочередно.
Данная информация нигде не кэшируются. Таким образом, невозможно произвольно
двигаться по потоку данных вперед или назад. В Java NIO данные сначала считываются в
буфер, что дает больше гибкости при обработке данных.
Потоки ввода/вывода в Java IO являются блокирующими. Это значит, что когда в
потоке выполнения вызывается read() или write() метод любого класса из пакета [Link].*,
происходит блокировка до тех пор, пока данные не будут считаны или записаны. Поток
выполнения в данный момент не может делать ничего другого. Неблокирующий режим Java
NIO позволяет запрашивать считанные данные из канала (channel) и получать только то, что
доступно на данный момент, или вообще ничего, если доступных данных пока нет. Вместо
того, чтобы оставаться заблокированным пока данные не станут доступными для считывания,
поток выполнения может заняться чем-то другим. Тоже самое справедливо и для
неблокирующего вывода. Поток выполнения может запросить запись в канал некоторых
данных, но не дожидаться при этом пока они не будут полностью записаны.
В Java NIO имеются селекторы, которые позволяют одному потоку выполнения
мониторить несколько каналов ввода. Т.е. существует возможность зарегистрировать
несколько каналов с селектором, а потом использовать один поток выполнения для
обслуживания каналов, имеющих доступные для обработки данные, или для выбора каналов,
готовых для записи.
31. [Link]
Какие классы поддерживают чтение и запись потоков в компрессированном формате?
 DeflaterOutputStream - компрессия данных в формате deflate.
 Deflater - компрессия данных в формат ZLIB
 ZipOutputStream - потомок DeflaterOutputStream для компрессии данных в
формат Zip.
 GZIPOutputStream - потомок DeflaterOutputStream для компрессии данных в
формат GZIP.
 InflaterInputStream - декомпрессия данных в формате deflate.
 Inflater - декомпрессия данных в формате ZLIB
 ZipInputStream - потомок InflaterInputStream для декомпрессии данных в
формате Zip.
 GZIPInputStream - потомок InflaterInputStream для декомпрессии данных в
формате GZIP.

32. Channels.
Что такое «каналы»?
Каналы (channels) – это логические (не физические) порталы, абстракции объектов
более низкого уровня файловой системы (например, отображенные в памяти файлы и
блокировки файлов), через которые осуществляется ввод/вывод данных, а буферы являются
источниками или приемниками этих переданных данных. При организации вывода, данные,
которые необходимо отправить, помещаются в буфер, который затем передается в канал.
При вводе, данные из канала помещаются в заранее предоставленный буфер.
Каналы напоминают трубопроводы, по которым эффективно транспортируются
данные между буферами байтов и сущностями по ту сторону каналов. Каналы – это шлюзы,
которые позволяют получить доступ к сервисам ввода/вывода операционной системы с
минимальными накладными расходами, а буферы – внутренние конечные точки этих шлюзов,
используемые для передачи и приема данных.

33. Виды (классы) потоков ввода/вывода (байтовые


([Link], [Link]) и символьные
([Link], [Link])).
Назовите основные классы потоков ввода/вывода?
Разделяют два вида потоков ввода/вывода:
 байтовые - [Link], [Link];
 символьные - [Link], [Link].

В каких пакетах расположены классы потоков ввода/вывода?


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

Какие подклассы класса InputStream вы знаете, для чего они предназначены?


 InputStream - абстрактный класс, описывающий поток ввода;
 BufferedInputStream - буферизованный входной поток;
 ByteArrayInputStream позволяет использовать буфер в памяти (массив байтов)
в качестве источника данных для входного потока;
 DataInputStream - входной поток для байтовых данных, включающий методы
для чтения стандартных типов данных Java;
 FileInputStream - входной поток для чтения информации из файла;
 FilterInputStream - абстрактный класс, предоставляющий интерфейс для
классов-надстроек, которые добавляют к существующим потокам полезные
свойства;
 ObjectInputStream - входной поток для объектов;
 StringBufferInputStream превращает строку (String) во входной поток данных
InputStream;
 PipedInputStream реализует понятие входного канала;
 PrintStream - выходной поток, включающий методы print() и println();
 PushbackInputStream - разновидность буферизации, обеспечивающая чтение
байта с последующим его возвратом в поток, позволяет «заглянуть» во
входной поток и увидеть, что оттуда поступит в следующий момент, не
извлекая информации.
 SequenceInputStream используется для слияния двух или более потоков
InputStream в единый.

Для чего используется PushbackInputStream?


Разновидность буферизации, обеспечивающая чтение байта с последующим его
возвратом в поток. Класс PushbackInputStream представляет механизм «заглянуть» во
входной поток и увидеть, что оттуда поступит в следующий момент, не извлекая информации.
У класса есть дополнительный метод unread().

Для чего используется SequenceInputStream?


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

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


примитивных типов данных?
Класс DataInputStream представляет поток ввода и предназначен для записи данных
примитивных типов, таких, как int, double и т.д. Для каждого примитивного типа определен
свой метод для считывания:
 boolean readBoolean(): считывает из потока булевое однобайтовое значение
 byte readByte(): считывает из потока 1 байт
 char readChar(): считывает из потока значение char
 double readDouble(): считывает из потока 8-байтовое значение double
 float readFloat(): считывает из потока 4-байтовое значение float
 int readInt(): считывает из потока целочисленное значение int
 long readLong(): считывает из потока значение long
 short readShort(): считывает значение short
 String readUTF(): считывает из потока строку в кодировке UTF-8

Какие подклассы класса OutputStream вы знаете, для чего они предназначены?


 OutputStream - это абстрактный класс, определяющий потоковый байтовый
вывод;
 BufferedOutputStream - буферизированный выходной поток;
 ByteArrayOutputStream - все данные, посылаемые в этот поток, размещаются в
предварительно созданном буфере;
 DataOutputStream - выходной поток байт, включающий методы для записи
стандартных типов данных Java;
 FileOutputStream - запись данных в файл на физическом носителе;
 FilterOutputStream - абстрактный класс, предоставляющий интерфейс для
классов-надстроек, которые добавляют к существующим потокам полезные
свойства;
 ObjectOutputStream - выходной поток для записи объектов;
 PipedOutputStream реализует понятие выходного канала.
Какие подклассы класса Reader вы знаете, для чего они предназначены?
 Reader - абстрактный класс, описывающий символьный ввод;
 BufferedReader - буферизованный входной символьный поток;
 CharArrayReader - входной поток, который читает из символьного массива;
 FileReader - входной поток, читающий файл;
 FilterReader - абстрактный класс, предоставляющий интерфейс для классов-
надстроек;
 InputStreamReader- входной поток, транслирующий байты в символы;
 LineNumberReader - входной поток, подсчитывающий строки;
 PipedReader - входной канал;
 PushbackReader - входной поток, позволяющий возвращать символы обратно в
поток;
 StringReader - входной поток, читающий из строки.

Какие подклассы класса Writer вы знаете, для чего они предназначены?


 Writer - абстрактный класс, описывающий символьный вывод;
 BufferedWriter - буферизованный выходной символьный поток;
 CharArrayWriter - выходной поток, который пишет в символьный массив;
 FileWriter - выходной поток, пишущий в файл;
 FilterWriter - абстрактный класс, предоставляющий интерфейс для классов-
надстроек;
 OutputStreamWriter - выходной поток, транслирующий байты в символы;
 PipedWriter - выходной канал;
 PrintWriter - выходной поток символов, включающий методы print() и println();
 StringWriter - выходной поток, пишущий в строку;

В чем отличие класса PrintWriter от PrintStream?


Прежде всего, в классе PrintWriter применен усовершенствованный способ работы с
символами Unicode и другой механизм буферизации вывода: в классе PrintStream буфер
вывода сбрасывался всякий раз, когда вызывался метод print() или println(), а при
использовании класса PrintWriter существует возможность отказаться от автоматического
сброса буферов, выполняя его явным образом при помощи метода flush().
Кроме того, методы класса PrintWriter никогда не создают исключений. Для проверки
ошибок необходимо явно вызвать метод checkError().

Чем отличаются и что общего у InputStream, OutputStream, Reader, Writer?


 InputStream и его наследники - совокупность для получения байтовых данных
из различных источников;
 OutputStream и его наследники - набор классов определяющих потоковый
байтовый вывод;
 Reader и его наследники определяют потоковый ввод символов Unicode;
 Writer и его наследники определяют потоковый вывод символов Unicode.

Какие классы позволяют преобразовать байтовые потоки в символьные и обратно?


 OutputStreamWriter — «мост» между классом OutputStream и классом Writer.
Символы, записанные в поток, преобразовываются в байты.
 InputStreamReader — аналог для чтения. При помощи методов класса Reader
читаются байты из потока InputStream и далее преобразуются в символы.

Какие классы позволяют ускорить чтение/запись за счет использования буфера?


 BufferedInputStream(InputStream in)/BufferedInputStream(InputStream in, int size),
 BufferedOutputStream(OutputStream out)/BufferedOutputStream(OutputStream out,
int size),
 BufferedReader(Reader r)/BufferedReader(Reader in, int sz),
 BufferedWriter(Writer out)/BufferedWriter(Writer out, int sz)

Существует ли возможность перенаправить потоки стандартного ввода/вывода?


Класс System позволяет вам перенаправлять стандартный ввод, вывод и поток вывода
ошибок, используя простой вызов статического метода:
 setIn(InputStream) - для ввода;
 setOut(PrintStream) - для вывода;
 setErr(PrintStream) - для вывода ошибок.

34. Классы для работы с файловой системой.


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

Какие методы класса File вы знаете?


Наиболее используемые методы класса File:
 boolean createNewFile(): делает попытку создать новый файл;
 boolean delete(): делает попытку удалить каталог или файл;
 boolean mkdir(): делает попытку создать новый каталог;
 boolean renameTo(File dest): делает попытку переименовать файл или каталог;
 boolean exists(): проверяет, существует ли файл или каталог;
 String getAbsolutePath(): возвращает абсолютный путь для пути, переданного в
конструктор объекта;
 String getName(): возвращает краткое имя файла или каталога;
 String getParent(): возвращает имя родительского каталога;
 boolean isDirectory(): возвращает значение true, если по указанному пути
располагается каталог;
 boolean isFile(): возвращает значение true, если по указанному пути находится
файл;
 boolean isHidden(): возвращает значение true, если каталог или файл являются
скрытыми;
 long length(): возвращает размер файла в байтах;
 long lastModified(): возвращает время последнего изменения файла или
каталога;
 String[] list(): возвращает массив файлов и подкаталогов, которые находятся в
определенном каталоге;
 File[] listFiles(): возвращает массив файлов и подкаталогов, которые находятся
в определенном каталоге.

Что вы знаете об интерфейсе FileFilter?


Интерфейс FileFilter применяется для проверки, попадает ли объект File под некоторое
условие. Этот интерфейс содержит единственный метод boolean accept(File pathName). Этот
метод необходимо переопределить и реализовать. Например:
public boolean accept(final File file) {
return [Link]() && [Link]();
}

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


определенным расширением)?
Метод [Link]() возвращает массив объектов File, содержащихся в каталоге.
Метод может принимать в качестве параметра объект класса, реализующего FileFilter. Это
позволяет включить в список только те элементы, для которых метод accept возвращает true
(критерием может быть длина имени файла или его расширение).
Что вы знаете о RandomAccessFile?
Класс [Link] обеспечивает чтение и запись данных в произвольном
месте файла. Он не является частью иерархии InputStream или OutputStream. Это полностью
отдельный класс, имеющий свои собственные (в большинстве своем native) методы.
Объяснением этого может быть то, что RandomAccessFile имеет во многом отличающееся
поведение по сравнению с остальными классами ввода/вывода так как позволяет, в пределах
файла, перемещаться вперед и назад.
RandomAccessFile имеет такие специфические методы как:
 getFilePointer() для определения текущего местоположения в файле;
 seek() для перемещения на новую позицию в файле;
 length() для выяснения размера файла;
 setLength() для установки размера файла;
 skipBytes() для того, чтобы попытаться пропустить определенное число байт;
 getChannel() для работы с уникальным файловым каналом, ассоциированным с
заданным файлом;
 методы для выполнения обычного и форматированного вывода из файла
(read(), readInt(), readLine(), readUTF() и т.п.);
 методы для обычной или форматированной записи в файл с прямым доступом
(write(), writeBoolean(), writeByte() и т.п.).
Так же следует отметить, что конструкторы RandomAccessFile требуют второй
аргумент, указывающий необходимый режим доступа к файлу - только чтение ("r"), чтение и
запись ("rw") или иную их разновидность.

Какие режимы доступа к файлу есть у RandomAccessFile?


"r" открывает файл только для чтения. Запуск любых методов записи данных приведет
к выбросу исключения IOException.
"rw" открывает файл для чтения и записи. Если файл еще не создан, то
осуществляется попытка создать его.
"rws" открывает файл для чтения и записи подобно "rw", но требует от системы при
каждом изменении содержимого файла или метаданных синхронно записывать эти изменения
на физический носитель.
"rwd" открывает файл для чтения и записи подобно "rws", но требует от системы
синхронно записывать изменения на физический носитель только при каждом изменении
содержимого файла. Если изменяются метаданные, синхронная запись не требуется.

35. Абсолютный и относительный путь.


Какой символ является разделителем при указании пути в файловой системе?
Для различных операционных систем символ разделителя различается. Для Windows
это \, для Linux - /.
В Java получить разделитель для текущей операционной системы можно через
обращение к статическому полю [Link].

Что такое «абсолютный путь» и «относительный путь»?


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

36. Символьная ссылка.


Что такое «символьная ссылка»?
Символьная (символическая) ссылка (также «симлинк», Symbolic link) — специальный
файл в файловой системе, в котором, вместо пользовательских данных, содержится путь к
файлу, который должен быть открыт при попытке обратиться к данной ссылке (файлу). Целью
ссылки может быть любой объект: например, другая ссылка, файл, каталог или даже
несуществующий файл (в последнем случае, при попытке открыть его, должно выдаваться
сообщение об отсутствии файла).
Символьные ссылки используются для более удобной организации структуры файлов
на компьютере, так как:
позволяют для одного файла или каталога иметь несколько имён и различных
атрибутов;
свободны от некоторых ограничений, присущих жёстким ссылкам (последние
действуют только в пределах одной файловой системы (одного раздела) и не могут
ссылаться на каталоги).

Java 8
37. Методы интерфейсов по умолчанию.
Что такое default методы интерфейса?
Java 8 позволяет добавлять неабстрактные реализации методов в интерфейс,
используя ключевое слово default:
interface Example {
int process(int a);
default void show() {
[Link]("default show()");
}
}
Если класс реализует интерфейс, он может, но не обязан, реализовать методы по-
умолчанию, уже реализованные в интерфейсе. Класс наследует реализацию по умолчанию.
Если некий класс реализует несколько интерфейсов, которые имеют одинаковый
метод по умолчанию, то класс должен реализовать метод с совпадающей сигнатурой
самостоятельно. Ситуация аналогична, если один интерфейс имеет метод по умолчанию, а в
другом этот же метод является абстрактным - никакой реализации по умолчанию классом не
наследуется.
Метод по умолчанию не может переопределить метод класса [Link].
Помогают реализовывать интерфейсы без страха нарушить работу других классов.
Позволяют избежать создания служебных классов, так как все необходимые методы
могут быть представлены в самих интерфейсах.
Дают свободу классам выбрать метод, который нужно переопределить.
Одной из основных причин внедрения методов по умолчанию является возможность
коллекций в Java 8 использовать лямбда-выражения.

Как вызывать default метод интерфейса в реализующем этот интерфейс классе?


Используя ключевое слово super вместе с именем интерфейса:
[Link]();

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


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

Как вызывать static метод интерфейса?


Используя имя интерфейса:
[Link]();
38. Лямбда-выражения и ссылка на метод или конструктор.
Что такое «лямбда»? Какова структура и особенности использования лямбда-
выражения?
Лямбда представляет собой набор инструкций, которые можно выделить в отдельную
переменную и затем многократно вызвать в различных местах программы.
Основу лямбда-выражения составляет лямбда-оператор, который представляет
стрелку ->. Этот оператор разделяет лямбда-выражение на две части: левая часть содержит
список параметров выражения, а правая собственно представляет тело лямбда-выражения,
где выполняются все действия.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода,
определенного в функциональном интерфейсе. При этом важно, что функциональный
интерфейс должен содержать только один единственный метод без реализации.
По факту лямбда-выражения являются в некотором роде сокращенной формой
внутренних анонимных классов, которые ранее применялись в Java.
Отложенное выполнение (deferred execution) лямбда-выражения- определяется один
раз в одном месте программы, вызываются при необходимости, любое количество раз и в
произвольном месте программы.
Параметры лямбда-выражения должны соответствовать по типу параметрам метода
функционального интерфейса:
operation = (int x, int y) -> x + y;
//При написании самого лямбда-выражения тип параметров разрешается не
указывать:
(x, y) -> x + y;
//Если метод не принимает никаких параметров, то пишутся пустые скобки, например:
() -> 30 + 20;
//Если метод принимает только один параметр, то скобки можно опустить:
n -> n * n;
Конечные лямбда-выражения не обязаны возвращать какое-либо значение.
Блочные лямбда-выражения обрамляются фигурными скобками. В блочных лямбда-
выражениях можно использовать внутренние вложенные блоки, циклы, конструкции if, switch,
создавать переменные и т.д. Если блочное лямбда-выражение должно возвращать значение,
то явным образом применяется оператор return:
Operationable operation = (int x, int y) -> {
if (y == 0) {
return 0;
}
else {
return x / y;main
}
};
Передача лямбда-выражения в качестве параметра метода:
interface Condition {
boolean isAppropriate(int n);
}

private static int sum(int[] numbers, Condition condition) {


int result = 0;
for (int i : numbers) {
if ([Link](i)) {
result += i;
}
}
return result;
}

public static void main(String[] args) {


[Link](sum(new int[] {0, 1, 0, 3, 0, 5, 0, 7, 0, 9}, (n) -> n != 0));
}
К каким переменным есть доступ у лямбда-выражений?
Доступ к переменным внешней области действия из лямбда-выражения очень схож к
доступу из анонимных объектов. Можно ссылаться на:
 неизменяемые (effectively final - не обязательно помеченные как final)
локальные переменные;
 поля класса;
 статические переменные.
К методам по умолчанию реализуемого функционального интерфейса обращаться
внутри лямбда-выражения запрещено.

Как отсортировать список строк с помощью лямбда-выражения?


public static List<String> sort(List<String> list){
[Link](list, (a, b) -> [Link](b));
return list;
}

Что такое «ссылка на метод»?


Если существующий в классе метод уже делает все, что необходимо, то можно
воспользоваться механизмом method reference (ссылка на метод) для непосредственной
передачи этого метода. Такая ссылка передается в виде:
 имя_класса::имя_статического_метода для статического метода;
 объект_класса::имя_метода для метода экземпляра;
 название_класса::new для конструктора.
Результат будет в точности таким же, как в случае определения лямбда-выражения,
которое вызывает этот метод.
private interface Measurable {
public int length(String string);
}

public static void main(String[] args) {


Measurable a = String::length;
[Link]([Link]("abc"));
}
Ссылки на методы потенциально более эффективны, чем использование лямбда-
выражений. Кроме того, они предоставляют компилятору более качественную информацию о
типе и при возможности выбора между использованием ссылки на существующий метод и
использованием лямбда-выражения, следует всегда предпочитать использование ссылки на
метод.

Какие виды ссылок на методы вы знаете?


 на статический метод;
 на метод экземпляра;
 на конструктор.

Объясните выражение [Link]::println.


Данное выражение иллюстрирует механизм instance method reference: передачи
ссылки на метод println() статического поля out класса System.

39. Stream API (как создать стрим, промежуточные и терминальные


операции, вернуть пустой стрим и зачем).
Что такое Stream?
Интерфейс [Link] представляет собой последовательность элементов, над
которой можно производить различные операции.
Операции над стримами бывают или промежуточными (intermediate) или конечными
(terminal). Конечные операции возвращают результат определенного типа, а промежуточные
операции возвращают тот же стрим. Таким образом вы можете строить цепочки из несколько
операций над одним и тем же стримом.
У стрима может быть сколько угодно вызовов промежуточных операций и последним
вызов конечной операции. При этом все промежуточные операции выполняются лениво и
пока не будет вызвана конечная операция никаких действий на самом деле не происходит
(похоже на создание объекта Thread или Runnable, без вызова start()).
Стримы создаются на основе источников каких-либо, например классов из
[Link].
Ассоциативные массивы (maps), например HashMap, не поддерживаются.
Операции над стримами могут выполняться как последовательно, так и параллельно.
Потоки не могут быть использованы повторно. Как только была вызвана какая-нибудь
конечная операция, поток закрывается.
Кроме универсальных объектных существуют особые виды стримов для работы с
примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти
примитивные стримы работают так же, как и обычные объектные, но со следующими
отличиями:
используют специализированные лямбда-выражения, например IntFunction или
IntPredicate вместо Function и Predicate;
поддерживают дополнительные конечные операции sum(), average(), mapToObj().

Какие существуют способы создания стрима?


 Из коллекции:
Stream<String> fromCollection = [Link]("x", "y", "z").stream();
 Из набора значений:
Stream<String> fromValues = [Link]("x", "y", "z");
 Из массива:
Stream<String> fromArray = [Link](new String[]{"x", "y", "z"});
 Из файла (каждая строка в файле будет отдельным элементом в стриме):
Stream<String> fromFile = [Link]([Link]("[Link]"));
 Из строки:
IntStream fromString = "0123456789".chars();
 С помощью [Link]():
Stream<String> fromBuilder = [Link]().add("z").add("y").add("z").build();
 С помощью [Link]() (бесконечный):
Stream<Integer> fromIterate = [Link](1, n -> n + 1);
 С помощью [Link]() (бесконечный):

Stream<String> fromGenerate = [Link](() -> "0");

В чем разница между Collection и Stream?


Коллекции позволяют работать с элементами по-отдельности, тогда как стримы так
делать не позволяют, но вместо этого предоставляют возможность выполнять функции над
данными как над одним целым.
Также стоит отметить важность самой концепции сущностей: Collection - это прежде
всего воплощение Структуры Данных. Например Set не просто хранит в себе элементы, он
реализует идею множества с уникальными элементами, тогда как Stream, это прежде всего
абстракция необходимая для реализации конвеера вычислений, собственно поэтому,
результатом работы конвеера являются те или иные Структуры Данных или же результаты
проверок/поиска и т.п.

Для чего нужен метод collect() в стримах?


Метод collect() является конечной операцией, которая используется для
представление результата в виде коллекции или какой-либо другой структуры данных.
collect() принимает на вход Collector<Тип_источника, Тип_аккумулятора,
Тип_результата>, который содержит четыре этапа: supplier - инициализация аккумулятора,
accumulator - обработка каждого элемента, combiner - соединение двух аккумуляторов при
параллельном выполнении, [finisher] - необязательный метод последней обработки
аккумулятора. В Java 8 в классе Collectors реализовано несколько распространённых
коллекторов:
 toList(), toCollection(), toSet() - представляют стрим в виде списка, коллекции или
множества;
 toConcurrentMap(), toMap() - позволяют преобразовать стрим в Map;
 averagingInt(), averagingDouble(), averagingLong() - возвращают среднее
значение;
 summingInt(), summingDouble(), summingLong() - возвращает сумму;
 summarizingInt(), summarizingDouble(), summarizingLong() - возвращают
SummaryStatistics с разными агрегатными значениями;
 partitioningBy() - разделяет коллекцию на две части по соответствию условию и
возвращает их как Map<Boolean, List>;
 groupingBy() - разделяет коллекцию на несколько частей и возвращает Map<N,
List<T>>;
 mapping() - дополнительные преобразования значений для сложных Collector-
ов.
Также существует возможность создания собственного коллектора через [Link]():
Collector<String, List<String>, List<String>> toList = [Link](
ArrayList::new,
List::add,
(l1, l2) -> { [Link](l2); return l1; }
);

Для чего в стримах применяются методы forEach() и forEachOrdered()?


forEach() применяет функцию к каждому объекту стрима, порядок при параллельном
выполнении не гарантируется;
forEachOrdered() применяет функцию к каждому объекту стрима с сохранением
порядка элементов.

Для чего в стримах предназначены методы map() и mapToInt(), mapToDouble(),


mapToLong()?
Метод map() является промежуточной операцией, которая заданным образом
преобразует каждый элемент стрима.
mapToInt(), mapToDouble(), mapToLong() - аналоги map(), возвращающие
соответствующий числовой стрим (то есть стрим из числовых примитивов):
Stream
.of("12", "22", "4", "444", "123")
.mapToInt(Integer::parseInt)
.toArray(); //[12, 22, 4, 444, 123]

Какова цель метода filter() в стримах?


Метод filter() является промежуточной операцией принимающей предикат, который
фильтрует все элементы, возвращая только те, что соответствуют условию.

Для чего в стримах предназначен метод limit()?


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

Для чего в стримах предназначен метод sorted()?


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

Для чего в стримах предназначены методы flatMap(), flatMapToInt(), flatMapToDouble(),


flatMapToLong()?
Метод flatMap() похож на map, но может создавать из одного элемента несколько.
Таким образом, каждый объект будет преобразован в ноль, один или несколько других
объектов, поддерживаемых потоком. Наиболее очевидный способ применения этой операции
— преобразование элементов контейнера при помощи функций, которые возвращают
контейнеры.
Stream
.of("H e l l o", "w o r l d !")
.flatMap((p) -> [Link]([Link](" ")))
.toArray(String[]::new);//["H", "e", "l", "l", "o", "w", "o", "r", "l", "d", "!"]
flatMapToInt(), flatMapToDouble(), flatMapToLong() - это аналоги flatMap(),
возвращающие соответствующий числовой стрим.

Расскажите о параллельной обработке в Java 8.


Стримы могут быть последовательными и параллельными. Операции над
последовательными стримами выполняются в одном потоке процессора, над параллельными
— используя несколько потоков процессора. Параллельные стримы используют общий
ForkJoinPool доступный через статический [Link]() метод. При этом, если
окружение не является многоядерным, то поток будет выполняться как последовательный.
Фактически применение параллельных стримов сводится к тому, что данные в стримах будут
разделены на части, каждая часть обрабатывается на отдельном ядре процессора, и в конце
эти части соединяются, и над ними выполняются конечные операции.
Для создания параллельного потока из коллекции можно также использовать метод
parallelStream() интерфейса Collection.
Чтобы сделать обычный последовательный стрим параллельным, надо вызвать у
объекта Stream метод parallel(). Метод isParallel() позволяет узнать является ли стрим
параллельным.
С помощью, методов parallel() и sequential() можно определять какие операции могут
быть параллельными, а какие только последовательными. Также из любого
последовательного стрима можно сделать параллельный и наоборот:
collection
.stream()
.peek(...) // операция последовательна
.parallel()
.map(...) // операция может выполняться параллельно,
.sequential()
.reduce(...) // операция снова последовательна
Как правило, элементы передаются в стрим в том же порядке, в котором они
определены в источнике данных. При работе с параллельными стримами система сохраняет
порядок следования элементов. Исключение составляет метод forEach(), который может
выводить элементы в произвольном порядке. И чтобы сохранить порядок следования,
необходимо применять метод forEachOrdered().
Критерии, которые могут повлиять на производительность в параллельных стримах:
Размер данных - чем больше данных, тем сложнее сначала разделять данные, а
потом их соединять.
Количество ядер процессора. Теоретически, чем больше ядер в компьютере, тем
быстрее программа будет работать. Если на машине одно ядро, нет смысла применять
параллельные потоки.
Чем проще структура данных, с которой работает поток, тем быстрее будут
происходить операции. Например, данные из ArrayList легко использовать, так как структура
данной коллекции предполагает последовательность несвязанных данных. А вот коллекция
типа LinkedList - не лучший вариант, так как в последовательном списке все элементы
связаны с предыдущими/последующими. И такие данные трудно распараллелить.
Над данными примитивных типов операции будут производиться быстрее, чем над
объектами классов.
Крайне не рекомендуется использовать параллельные стримы для сколько-нибудь
долгих операций (например сетевых соединений), так как все параллельные стримы работают
c одним ForkJoinPool, то такие долгие операции могут остановить работу всех параллельных
стримов в JVM из-за отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит
использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где
счет может идти на секунды и минуты;
Сохранение порядка в параллельных стримах увеличивает издержки при выполнении
и если порядок не важен, то имеется возможность отключить его сохранение и тем самым
увеличить производительность, использовав промежуточную операцию unordered():
[Link]()
.sorted()
.unordered()
.collect([Link]());

Какие конечные методы работы со стримами вы знаете?


 findFirst() возвращает первый элемент;
 findAny() возвращает любой подходящий элемент;
 collect() представление результатов в виде коллекций и других структур
данных;
 count() возвращает количество элементов;
 anyMatch() возвращает true, если условие выполняется хотя бы для одного
элемента;
 noneMatch() возвращает true, если условие не выполняется ни для одного
элемента;
 allMatch() возвращает true, если условие выполняется для всех элементов;
 min() возвращает минимальный элемент, используя в качестве условия
Comparator;
 max() возвращает максимальный элемент, используя в качестве условия
Comparator;
 forEach() применяет функцию к каждому объекту (порядок при параллельном
выполнении не гарантируется);
 forEachOrdered() применяет функцию к каждому объекту с сохранением
порядка элементов;
 toArray() возвращает массив значений;
 reduce()позволяет выполнять агрегатные функции и возвращать один
результат.
 Для числовых стримов дополнительно доступны:
 sum() возвращает сумму всех чисел;
 average() возвращает среднее арифметическое всех чисел.

Какие промежуточные методы работы со стримами вы знаете?


 filter() отфильтровывает записи, возвращая только записи, соответствующие
условию;
 skip() позволяет пропустить определенное количество элементов в начале;
 distinct() возвращает стрим без дубликатов (для метода equals());
 map() преобразует каждый элемент;
 peek() возвращает тот же стрим, применяя к каждому элементу функцию;
 limit() позволяет ограничить выборку определенным количеством первых
элементов;
 sorted() позволяет сортировать значения либо в натуральном порядке, либо
задавая Comparator;
 mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим
числовых примитивов;
 flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но
могут создавать из одного элемента несколько.
Для числовых стримов дополнительно доступен метод mapToObj(), который
преобразует числовой стрим обратно в объектный.

Как вывести на экран 10 случайных чисел, используя forEach()?


(new Random())
.ints()
.limit(10)
.forEach([Link]::println);

Как можно вывести на экран уникальные квадраты чисел используя метод map()?
Stream
.of(1, 2, 3, 2, 1)
.map(s -> s * s)
.distinct()
.forEach([Link]::println);

Как вывести на экран количество пустых строк с помощью метода filter()?


[Link](
Stream
.of("Hello", "", ", ", "world", "!")
.filter(String::isEmpty)
.count());

Как вывести на экран 10 случайных чисел в порядке возрастания?


(new Random())
.ints()
.limit(10)
.sorted()
.forEach([Link]::println);

Как найти максимальное число в наборе?


Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.max()
.getAsInt(); //55

Как найти минимальное число в наборе?


Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.min()
.getAsInt(); //2

Как получить сумму всех чисел в наборе?


Stream
.of(5, 3, 4, 55, 2)
.mapToInt()
.sum(); //69

Как получить среднее значение всех чисел?


Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.average()
.getAsDouble(); //13.8

Какие дополнительные методы для работы с ассоциативными массивами (maps)


появились в Java 8?
 putIfAbsent() добавляет пару «ключ-значение», только если ключ отсутствовал:
 [Link]("a", "Aa");
 forEach() принимает функцию, которая производит операцию над каждым
элементом:
 [Link]((k, v) -> [Link](v));
 compute() создаёт или обновляет текущее значение на полученное в
результате вычисления (возможно использовать ключ и текущее значение):
 [Link]("a", (k, v) -> [Link](k).concat(v)); //["a", "aAa"]
 computeIfPresent() если ключ существует, обновляет текущее значение на
полученное в результате вычисления (возможно использовать ключ и текущее
значение):
 [Link]("a", (k, v) -> [Link](v));
 computeIfAbsent() если ключ отсутствует, создаёт его со значением, которое
вычисляется (возможно использовать ключ):
 [Link]("a", k -> "A".concat(k)); //["a","Aa"]
 getOrDefault() в случае отсутствия ключа, возвращает переданное значение по-
умолчанию:
 [Link]("a", "not found");
 merge() принимает ключ, значение и функцию, которая объединяет
передаваемое и текущее значения. Если под заданным ключем значение
отсутствует, то записывает туда передаваемое значение.
 [Link]("a", "z", (value, newValue) -> [Link](newValue)); //["a","Aaz"]

40. Работа с датами и временем.


Что такое LocalDateTime?
LocalDateTime объединяет вместе LocaleDate и LocalTime, содержит дату и время в
календарной системе ISO-8601 без привязки к часовому поясу. Время хранится с точностью
до наносекунды. Содержит множество удобных методов, таких как plusMinutes, plusHours,
isAfter, toSecondOfDay и т.д.

Что такое ZonedDateTime?


[Link] — аналог [Link], класс с самым полным объемом
информации о временном контексте в календарной системе ISO-8601. Включает временную
зону, поэтому все операции с временными сдвигами этот класс проводит с её учётом.

Как получить текущую дату с использованием Date Time API из Java 8?


[Link]();

Как добавить 1 неделю, 1 месяц, 1 год, 10 лет к текущей дате с использованием Date
Time API?
[Link]().plusWeeks(1);
[Link]().plusMonths(1);
[Link]().plusYears(1);
[Link]().plus(1, [Link]);

Как получить следующий вторник используя Date Time API?


[Link]().with([Link]([Link]));

Как получить вторую субботу текущего месяца используя Date Time API?
LocalDate
.of([Link]().getYear(), [Link]().getMonth(), 1)
.with([Link]([Link]))
.with([Link]([Link]));

Как получить текущее время с точностью до миллисекунд используя Date Time API?
new Date().toInstant();

Как получить текущее время по местному времени с точностью до миллисекунд


используя Date Time API?
[Link](new Date().toInstant(), [Link]());

41. Функциональные интерфейсы.


Что такое «функциональные интерфейсы»?
Функциональный интерфейс - это интерфейс, который определяет только один
абстрактный метод.
Чтобы точно определить интерфейс как функциональный, добавлена аннотация
@FunctionalInterface, работающая по принципу @Override. Она обозначит замысел и не даст
определить второй абстрактный метод в интерфейсе.
Интерфейс может включать сколько угодно default методов и при этом оставаться
функциональным, потому что default методы - не абстрактные.

Для чего нужны функциональные интерфейсы Function<T,R>, DoubleFunction<R>,


IntFunction<R> и LongFunction<R>?
Function<T, R> - интерфейс, с помощью которого реализуется функция, получающая
на вход экземпляр класса T и возвращающая на выходе экземпляр класса R.
Методы по умолчанию могут использоваться для построения цепочек вызовов
(compose, andThen).
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = [Link](String::valueOf);
[Link]("123"); // "123"
DoubleFunction<R> - функция получающая на вход Double и возвращающая на выходе
экземпляр класса R;
IntFunction<R> - функция получающая на вход Integer и возвращающая на выходе
экземпляр класса R;
LongFunction<R> - функция получающая на вход Long и возвращающая на выходе
экземпляр класса R.

Для чего нужны функциональные интерфейсы UnaryOperator<T>, DoubleUnaryOperator,


IntUnaryOperator и LongUnaryOperator?
UnaryOperator<T> (унарный оператор) принимает в качестве параметра объект типа T,
выполняет над ними операции и возвращает результат операций в виде объекта типа T:
UnaryOperator<Integer> operator = x -> x * x;
[Link]([Link](5)); // 25
DoubleUnaryOperator - унарный оператор получающий на вход Double;
IntUnaryOperator - унарный оператор получающий на вход Integer;
LongUnaryOperator - унарный оператор получающий на вход Long.

Для чего нужны функциональные интерфейсы BinaryOperator<T>,


DoubleBinaryOperator, IntBinaryOperator и LongBinaryOperator?
BinaryOperator<T> (бинарный оператор) - интерфейс, с помощью которого реализуется
функция, получающая на вход два экземпляра класса T и возвращающая на выходе
экземпляр класса T.
BinaryOperator<Integer> operator = (a, b) -> a + b;
[Link]([Link](1, 2)); // 3
DoubleBinaryOperator - бинарный оператор получающий на вход Double;
IntBinaryOperator - бинарный оператор получающий на вход Integer;
LongBinaryOperator - бинарный оператор получающий на вход Long.

Для чего нужны функциональные интерфейсы Predicate<T>, DoublePredicate,


IntPredicate и LongPredicate?
Predicate<T> (предикат) - интерфейс, с помощью которого реализуется функция,
получающая на вход экземпляр класса T и возвращающая на выходе значение типа boolean.
Интерфейс содержит различные методы по умолчанию, позволяющие строить
сложные условия (and, or, negate).
Predicate<String> predicate = (s) -> [Link]() > 0;
[Link]("foo"); // true
[Link]().test("foo"); // false
DoublePredicate - предикат получающий на вход Double;
IntPredicate - предикат получающий на вход Integer;
LongPredicate - предикат получающий на вход Long.

Для чего нужны функциональные интерфейсы Consumer<T>, DoubleConsumer,


IntConsumer и LongConsumer?
Consumer<T> (потребитель) - интерфейс, с помощью которого реализуется функция,
которая получает на вход экземпляр класса T, производит с ним некоторое действие и ничего
не возвращает.
Consumer<String> hello = (name) -> [Link]("Hello, " + name);
[Link]("world");
DoubleConsumer - потребитель получающий на вход Double;
IntConsumer - потребитель получающий на вход Integer;
LongConsumer - потребитель получающий на вход Long.

Для чего нужны функциональные интерфейсы Supplier<T>, BooleanSupplier,


DoubleSupplier, IntSupplier и LongSupplier?
Supplier<T> (поставщик) - интерфейс, с помощью которого реализуется функция,
ничего не принимающая на вход, но возвращающая на выход результат класса T;
Supplier<LocalDateTime> now = LocalDateTime::now;
[Link]();
DoubleSupplier - поставщик возвращающий Double;
IntSupplier - поставщик возвращающий Integer;
LongSupplier - поставщик возвращающий Long.

Для чего нужен функциональный интерфейс BiConsumer<T,U>?


BiConsumer<T,U> представляет собой операцию, которая принимает два аргумента
классов T и U производит с ними некоторое действие и ничего не возвращает.

Для чего нужен функциональный интерфейс BiFunction<T,U,R>?


BiFunction<T,U,R> представляет собой операцию, которая принимает два аргумента
классов T и U и возвращающая результат класса R.

Для чего нужен функциональный интерфейс BiPredicate<T,U>?


BiPredicate<T,U> представляет собой операцию, которая принимает два аргумента
классов T и U и возвращающая результат типа boolean.
Для чего нужны функциональные интерфейсы вида _To_Function?
DoubleToIntFunction - операция принимающая аргумент класса Double и
возвращающая результат типа Integer;
DoubleToLongFunction - операция принимающая аргумент класса Double и
возвращающая результат типа Long;
IntToDoubleFunction - операция принимающая аргумент класса Integer и
возвращающая результат типа Double;
IntToLongFunction - операция принимающая аргумент класса Integer и возвращающая
результат типа Long;
LongToDoubleFunction - операция принимающая аргумент класса Long и
возвращающая результат типа Double;
LongToIntFunction - операция принимающая аргумент класса Long и возвращающая
результат типа Integer.

Для чего нужны функциональные интерфейсы ToDoubleBiFunction<T,U>,


ToIntBiFunction<T,U> и ToLongBiFunction<T,U>?
ToDoubleBiFunction<T,U> - операция принимающая два аргумента классов T и U и
возвращающая результат типа Double;
ToLongBiFunction<T,U> - операция принимающая два аргумента классов T и U и
возвращающая результат типа Long;
ToIntBiFunction<T,U> - операция принимающая два аргумента классов T и U и
возвращающая результат типа Integer.

Для чего нужны функциональные интерфейсы ToDoubleFunction<T>, ToIntFunction<T> и


ToLongFunction<T>?
ToDoubleFunction<T> - операция принимающая аргумент класса T и возвращающая
результат типа Double;
ToLongFunction<T> - операция принимающая аргумент класса T и возвращающая
результат типа Long;
ToIntFunction<T> - операция принимающая аргумент класса T и возвращающая
результат типа Integer.

Для чего нужны функциональные интерфейсы ObjDoubleConsumer<T>,


ObjIntConsumer<T> и ObjLongConsumer<T>?
ObjDoubleConsumer<T> - операция, которая принимает два аргумента классов T и
Double, производит с ними некоторое действие и ничего не возвращает;
ObjLongConsumer<T> - операция, которая принимает два аргумента классов T и Long,
производит с ними некоторое действие и ничего не возвращает;
ObjIntConsumer<T> - операция, которая принимает два аргумента классов T и Integer,
производит с ними некоторое действие и ничего не возвращает.

42. Проверяемые аннотации и аннотации на типы данных (понятие


аннотации).
Как определить повторяемую аннотацию?
Чтобы определить повторяемую аннотацию, необходимо создать аннотацию-
контейнер для списка повторяемых аннотаций и обозначить повторяемую мета-аннотацией
@Repeatable:
@interface Schedulers
{
Scheduler[] value();
}

@Repeatable([Link])
@interface Scheduler
{
String birthday() default "Jan 8 1935";
}

Java Collection Framework (JCF)


43. Понятие коллекции.
Что такое «коллекция»?
«Коллекция» - это структура данных, набор каких-либо объектов. Данными (объектами
в наборе) могут быть числа, строки, объекты пользовательских классов и т.п.

44. Иерархия коллекций.


Назовите основные интерфейсы JCF и их реализации.
На вершине иерархии в Java Collection Framework располагаются 2 интерфейса:
Collection и Map. Эти интерфейсы разделяют все коллекции, входящие во фреймворк на две
части по типу хранения данных: простые последовательные наборы элементов и наборы пар
«ключ — значение» соответственно.
Интерфейс Collection расширяют интерфейсы:
 List (список) представляет собой коллекцию, в которой допустимы
дублирующие значения. Элементы такой коллекции пронумерованы, начиная
от нуля, к ним можно обратиться по индексу. Реализации:
-ArrayList - инкапсулирует в себе обычный массив, длина которого
автоматически увеличивается при добавлении новых элементов.
-LinkedList (двунаправленный связный список) - состоит из узлов,
каждый из которых содержит как собственно данные, так и две ссылки на
следующий и предыдущий узел.
-Vector — реализация динамического массива объектов, методы
которой синхронизированы.
-Stack — реализация стека LIFO (last-in-first-out).
 Set (сет) описывает неупорядоченную коллекцию, не содержащую
повторяющихся элементов. Реализации:
-HashSet - использует HashMap для хранения данных. В качестве ключа
и значения используется добавляемый элемент. Из-за особенностей
реализации порядок элементов не гарантируется при добавлении.
-LinkedHashSet — гарантирует, что порядок элементов при обходе
коллекции будет идентичен порядку добавления элементов.
-TreeSet — предоставляет возможность управлять порядком элементов
в коллекции при помощи объекта Comparator, либо сохраняет элементы с
использованием «natural ordering».
 Queue (очередь) предназначена для хранения элементов с предопределенным
способом вставки и извлечения FIFO (first-in-first-out):е
-PriorityQueue — предоставляет возможность управлять порядком
элементов в коллекции при помощи объекта Comparator, либо сохраняет
элементы с использованием «natural ordering».
-ArrayDeque — реализация интерфейса Deque, который расширяет
интерфейс Queue методами, позволяющими реализовать конструкцию вида
LIFO (last-in-first-out).

Расположите в виде иерархии следующие интерфейсы: List, Set, Map, SortedSet,


SortedMap, Collection, Iterable, Iterator, NavigableSet, NavigableMap.
Iterable
Collection
List
Set
SortedSet
NavigableSet
Map
SortedMap
NavigableMap
Iterator

Почему Map — это не Collection, в то время как List и Set являются Collection?
Collection представляет собой совокупность некоторых элементов. Map - это
совокупность пар «ключ-значение».

Stack считается «устаревшим». Чем его рекомендуют заменять? Почему?


Stack был добавлен в Java 1.0 как реализация стека LIFO (last-in-first-out) и является
расширением коллекции Vector, хотя это несколько нарушает понятие стека (например, класс
Vector предоставляет возможность обращаться к любому элементу по индексу). Является
частично синхронизированной коллекцией (кроме метода добавления push()) с вытекающими
отсюда последствиями в виде негативного воздействия на производительность. После
добавления в Java 1.6 интерфейса Deque, рекомендуется использовать реализации именно
этого интерфейса, например ArrayDeque.

`45. List vs. Set.


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

46. Map не в Collection.


Интерфейс Map реализован классами:
-Hashtable — хэш-таблица, методы которой синхронизированы. Не позволяет
использовать null в качестве значения или ключа и не является упорядоченной.
-HashMap — хэш-таблица. Позволяет использовать null в качестве значения или ключа
и не является упорядоченной.
-LinkedHashMap — упорядоченная реализация хэш-таблицы.
-TreeMap — реализация основанная на красно-черных деревьях. Является
упорядоченной и предоставляет возможность управлять порядком элементов в коллекции при
помощи объекта Comparator, либо сохраняет элементы с использованием «natural ordering».
-WeakHashMap — реализация хэш-таблицы, которая организована с использованием
weak references для ключей (сборщик мусора автоматически удалит элемент из коллекции при
следующей сборке мусора, если на ключ этого элемента нет жёстких ссылок).

47. Collection vs. Collections.


В чем разница между классами [Link] и [Link]?
[Link] - набор статических методов для работы с коллекциями.
[Link] - один из основных интерфейсов Java Collections Framework.

48. ArrayList vs. LinkedList.


Чем отличается ArrayList от LinkedList? В каких случаях лучше использовать первый, а
в каких второй?
ArrayList это список, реализованный на основе массива, а LinkedList — это
классический двусвязный список, основанный на объектах с ссылками между ними.
ArrayList:
 доступ к произвольному элементу по индексу за константное время O(1);
 доступ к элементам по значению за линейное время O(N);
 вставка в конец в среднем производится за константное время O(1);
 удаление произвольного элемента из списка занимает значительное время т.к.
при этом все элементы находящиеся «правее» смещаются на одну ячейку
влево (реальный размер массива (capacity) не изменяется);
 вставка элемента в произвольное место списка занимает значительное время
т.к. при этом все элементы находящиеся «правее» смещаются на одну ячейку
вправо;
 минимум накладных расходов при хранении.
LinkedList:
 на получение элемента по индексу или значению потребуется линейное время
O(N);
 на добавление и удаление в начало или конец списка потребуется константное
O(1);
 вставка или удаление в/из произвольного место линейное O(N);
 требует больше памяти для хранения такого же количества элементов, потому
что кроме самого элемента хранятся еще указатели на следующий и
предыдущий элементы списка.
В целом, LinkedList в абсолютных величинах проигрывает ArrayList и по потребляемой
памяти и по скорости выполнения операций. LinkedList предпочтительно применять, когда
нужны частые операции вставки/удаления или в случаях, когда необходимо гарантированное
время добавления элемента в список.

Что работает быстрее ArrayList или LinkedList?


Смотря какие действия будут выполняться над структурой.
см. Чем отличается ArrayList от LinkedList

Какое худшее время работы метода contains() для элемента, который есть в LinkedList?
O(N). Время поиска элемента линейно пропорционально количеству элементов вс
списке.

Какое худшее время работы метода contains() для элемента, который есть в ArrayList?
O(N). Время поиска элемента линейно пропорционально количеству элементов вс
списке.

Какое худшее время работы метода add() для LinkedList?


O(N). Добавление в начало/конец списка осуществляется за время O(1).

Какое худшее время работы метода add() для ArrayList?


O(N). Вставка элемента в конец списка осуществляется за время O(1), но если
вместимость массива недостаточна, то происходит создание нового массива с увеличенным
размером и копирование всех элементов из старого массива в новый.

Необходимо добавить 1 млн. элементов, какую структуру вы используете?


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

Как происходит удаление элементов из ArrayList? Как меняется в этом случае размер
ArrayList?
При удалении произвольного элемента из списка, все элементы находящиеся
«правее» смещаются на одну ячейку влево и реальный размер массива (его емкость, capacity)
не изменяется никак. Механизм автоматического «расширения» массива существует, а вот
автоматического «сжатия» нет, можно только явно выполнить «сжатие» командой
trimToSize().

Предложите эффективный алгоритм удаления нескольких рядом стоящих элементов


из середины списка, реализуемого ArrayList.
Допустим нужно удалить n элементов с позиции m в списке. Вместо выполнения
удаления одного элемента n раз (каждый раз смещая на 1 позицию элементы, стоящие
«правее» в списке), нужно выполнить смещение всех элементов, стоящих «правее» n + m
позиции на n элементов «левее» к началу списка. Таким образом, вместо выполнения n
итераций перемещения элементов списка, все выполняется за 1 проход.

Сколько необходимо дополнительной памяти при вызове [Link]()?


Если в массиве достаточно места для размещения нового элемента, то
дополнительной памяти не требуется. Иначе происходит создание нового массива размером
в 1,5 раза превышающим существующий (это верно для JDK выше 1.7, в более ранних
версиях размер увеличения иной).

Сколько выделяется дополнительно памяти при вызове [Link]()?


Создается один новый экземпляр вложенного класса Node.

Оцените количество памяти на хранение одного примитива типа byte в LinkedList?


Каждый элемент LinkedList хранит ссылку на предыдущий элемент, следующий
элемент и ссылку на данные.
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
//...
}
Для 32-битных систем каждая ссылка занимает 32 бита (4 байта). Сам объект
(заголовок) вложенного класса Node занимает 8 байт. 4 + 4 + 4 + 8 = 20 байт, а т.к. размер
каждого объекта в Java кратен 8, соответственно получаем 24 байта. Примитив типа byte
занимает 1 байт памяти, но в JCF примитивы упаковываются: объект типа Byte занимает в
памяти 16 байт (8 байт на заголовок объекта, 1 байт на поле типа byte и 7 байт для кратности
8). Также напомню, что значения от -128 до 127 кэшируются и для них новые объекты каждый
раз не создаются. Таким образом, в x32 JVM 24 байта тратятся на хранение одного элемента
в списке и 16 байт - на хранение упакованного объекта типа Byte. Итого 40 байт.
Для 64-битной JVM каждая ссылка занимает 64 бита (8 байт), размер заголовка
каждого объекта составляет 16 байт (два машинных слова). Вычисления аналогичны: 8 + 8 + 8
+ 16 = 40 байт и 24 байта. Итого 64 байта.

Оцените количество памяти на хранение одного примитива типа byte в ArrayList?пу


ArrayList основан на массиве, для примитивных типов данных осуществляется
автоматическая упаковка значения, поэтому 16 байт тратится на хранение упакованного
объекта и 4 байта (8 для x64) - на хранение ссылки на этот объект в самой структуре данных.
Таким образом, в x32 JVM 4 байта используются на хранение одного элемента и 16 байт - на
хранение упакованного объекта типа Byte. Для x64 - 8 байт и 24 байта соответственно.

Для ArrayList или для LinkedList операция добавления элемента в середину


([Link]([Link]()/2, newElement)) медленнее?
Для ArrayList:
 проверка массива на вместимость. Если вместимости недостаточно, то
увеличение размера массива и копирование всех элементов в новый массив
(O(N));
 копирование всех элементов, расположенных правее от позиции вставки, на
одну позицию вправо (O(N));
 вставка элемента (O(1)).
Для LinkedList:
 поиск позиции вставки (O(N));
 вставка элемента (O(1)).
В худшем случае вставка в середину списка эффективнее для LinkedList. В остальных
- скорее всего, для ArrayList, поскольку копирование элементов осуществляется за счет
вызова быстрого системного метода [Link]().

В реализации класса ArrayList есть следующие поля: Object[] elementData, int size.
Объясните, зачем хранить отдельно size, если всегда можно взять [Link]?
Размер массива elementData представляет собой вместимость (capacity) ArrayList,
которая всегда больше переменной size - реального количества хранимых элементов. При
необходимости вместимость автоматически возрастает.

Почему LinkedList реализует и List, и Deque?


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

LinkedList — это односвязный, двусвязный или четырехсвязный список?


Двусвязный: каждый элемент LinkedList хранит ссылку на предыдущий и следующий
элементы.

Как перебрать элементы LinkedList в обратном порядке, не используя медленный


get(index)?
Для этого в LinkedList есть обратный итератор, который можно получить вызвав метод
descendingIterator().

49. Предназначение метода remove() у Iterator. Fail-Fast vs. Fail-Safe.


Что такое «fail-fast поведение»?
fail-fast поведение означает, что при возникновении ошибки или состояния, которое
может привести к ошибке, система немедленно прекращает дальнейшую работу и
уведомляет об этом. Использование fail-fast подхода позволяет избежать
недетерминированного поведения программы в течение времени.
В Java Collections API некоторые итераторы ведут себя как fail-fast и выбрасывают
ConcurrentModificationException, если после его создания была произведена модификация
коллекции, т.е. добавлен или удален элемент напрямую из коллекции, а не используя методы
итератора.
Реализация такого поведения осуществляется за счет подсчета количества
модификаций коллекции (modification count):
 при изменении коллекции счетчик модификаций также изменяется;
 при создании итератора ему передается текущее значение счетчика;
 при каждом обращении к итератору сохраненное значение счетчика
сравнивается с текущим, и, если они не совпадают, возникает исключение.

Какая разница между fail-fast и fail-safe?


В противоположность fail-fast, итераторы fail-safe не вызывают никаких исключений при
изменении структуры, потому что они работают с клоном коллекции вместо оригинала.
Приведите примеры итераторов реализующих поведение fail-safe
Итератор коллекции CopyOnWriteArrayList и итератор представления keySet коллекции
ConcurrentHashMap являются примерами итераторов fail-safe.

Как поведёт себя коллекция, если вызвать [Link]()?


Если вызову [Link]() предшествовал вызов [Link](), то [Link]()
удалит элемент коллекции, на который указывает итератор, в противном случае будет
выброшено IllegalStateException().

Как поведёт себя уже инстанциированный итератор для collection, если вызвать
[Link]()?
При следующем вызове методов итератора будет выброшено
ConcurrentModificationException.

Как избежать ConcurrentModificationException во время перебора коллекции?


 Попробовать подобрать другой итератор, работающий по принципу fail-safe. К
примеру, для List можно использовать ListIterator.
 Использовать ConcurrentHashMap и CopyOnWriteArrayList.
 Преобразовать список в массив и перебирать массив.
 Блокировать изменения списка на время перебора с помощью блока
synchronized.
Отрицательная сторона последних двух вариантов - ухудшение производительности.

50. Iterable & Iterator. Enumerated vs. Iterator.


Чем различаются Enumeration и Iterator?
Хотя оба интерфейса и предназначены для обхода коллекций между ними имеются
существенные различия:
 с помощью Enumeration нельзя добавлять/удалять элементы;
 в Iterator исправлены имена методов для повышения читаемости кода
([Link]() соответствует [Link](),
[Link]() соответствует [Link]() и т.д);
 Enumeration присутствуют в устаревших классах, таких как Vector/Stack, тогда
как Iterator есть во всех современных классах-коллекциях.

Что произойдет при вызове [Link]() без предварительного вызова


[Link]()?
Если итератор указывает на последний элемент коллекции, то возникнет исключение
NoSuchElementException, иначе будет возвращен следующий элемент.

Сколько элементов будет пропущено, если [Link]() будет вызван после 10-ти
вызовов [Link]()?
Нисколько - hasNext() осуществляет только проверку наличия следующего элемента.

Как между собой связаны Iterable и Iterator?


Интерфейс Iterable имеет только один метод - iterator(), который возвращает Iterator.

Как между собой связаны Iterable, Iterator и «for-each»?


Классы, реализующие интерфейс Iterable, могут применяться в конструкции for-each,
которая использует Iterator.
51. Comparator vs. Comparable.
Интерфейс Comparable является хорошим выбором, когда он используется для
определения порядка по умолчанию или, другими словами, если это основной способ
сравнения объектов.
Затем мы должны спросить себя, зачем использовать Comparator, если у нас уже есть
Comparable?
Есть несколько причин, почему:
 Иногда мы не можем изменить исходный код класса, чьи объекты мы хотим
отсортировать, что делает невозможным использование Comparable
 Использование компараторов позволяет нам избежать добавления
дополнительного кода в классы нашего домена
 Мы можем определить несколько разных стратегий сравнения, что невозможно
при использовании Comparable

52. Iterator vs. ListIterator.


Сравните Iterator и ListIterator.
 ListIterator расширяет интерфейс Iterator
 ListIterator может быть использован только для перебора элементов коллекции
List;
 Iterator позволяет перебирать элементы только в одном направлении, при
помощи метода next(). Тогда как ListIterator позволяет перебирать список в
обоих направлениях, при помощи методов next() и previous();
 ListIterator не указывает на конкретный элемент: его текущая позиция
располагается между элементами, которые возвращают методы previous() и
next().
 При помощи ListIterator вы можете модифицировать список, добавляя/удаляя
элементы с помощью методов add() и remove(). Iterator не поддерживает
данного функционала.

53. ArrayList vs. Vector.


Зачем добавили ArrayList, если уже был Vector?
 Методы класса Vector синхронизированы, а ArrayList - нет;
 По умолчанию, Vector удваивает свой размер, когда заканчивается выделенная
под элементы память. ArrayList же увеличивает свой размер только на
половину.
 Vector это устаревший класс и его использование не рекомендовано.

54. Queue vs. Deque.


Сравните интерфейсы Queue и Deque. Кто кого расширяет: Queue расширяет Deque, или
Deque расширяет Queue?
Queue - это очередь, которая обычно (но необязательно) строится по принципу FIFO
(First-In-First-Out) - соответственно извлечение элемента осуществляется с начала очереди,
вставка элемента - в конец очереди. Хотя этот принцип нарушает, к примеру PriorityQueue,
использующая «natural ordering» или переданный Comparator при вставке нового элемента.
Deque (Double Ended Queue) расширяет Queue и согласно документации это линейная
коллекция, поддерживающая вставку/извлечение элементов с обоих концов. Помимо этого
реализации интерфейса Deque могут строится по принципу FIFO, либо LIFO.
Реализации и Deque, и Queue обычно не переопределяют методы equals() и
hashCode(), вместо этого используются унаследованные методы класса Object, основанные
на сравнении ссылок.

55. PriorityQueue.
Что позволяет сделать PriorityQueue?
Особенностью PriorityQueue является возможность управления порядком элементов.
По-умолчанию, элементы сортируются с использованием «natural ordering», но это поведение
может быть переопределено при помощи объекта Comparator, который задается при создании
очереди. Данная коллекция не поддерживает null в качестве элементов.
Используя PriorityQueue, можно, например, реализовать алгоритм Дейкстры для
поиска кратчайшего пути от одной вершины графа к другой. Либо для хранения объектов
согласно определённого свойства.

56. HashMap vs. HashTable.


Зачем нужен HashMap, если есть Hashtable?
 Методы класса Hashtable синхронизированы, что приводит к снижению
производительности, а HashMap - нет;
 HashTable не может содержать элементы null, тогда как HashMap может
содержать один ключ null и любое количество значений null;
 Iterator у HashMap, в отличие от Enumeration у HashTable, работает по принципу
«fail-fast» (выдает исключение при любой несогласованности данных).
 Hashtable это устаревший класс и его использование не рекомендовано.

57. Устройство HashMap.


Как устроен HashMap?
HashMap состоит из «корзин» (bucket). С технической точки зрения «корзины» — это
элементы массива, которые хранят ссылки на списки элементов. При добавлении новой пары
«ключ-значение», вычисляет хэш-код ключа, на основании которого вычисляется номер
корзины (номер ячейки массива), в которую попадет новый элемент. Если корзина пустая, то в
нее сохраняется ссылка на вновь добавляемый элемент, если же там уже есть элемент, то
происходит последовательный переход по ссылкам между элементами в цепочке, в поисках
последнего элемента, от которого и ставится ссылка на вновь добавленный элемент. Если в
списке был найден элемент с таким же ключом, то он заменяется.

Согласно Кнуту и Кормену существует две основных реализации хэш-таблицы: на


основе открытой адресации и на основе метода цепочек. Как реализована HashMap?
Почему, по вашему мнению, была выбрана именно эта реализация? В чем плюсы и
минусы каждого подхода?
HashMap реализован с использованием метода цепочек, т.е. каждой ячейке массива
(корзине) соответствует свой связный список и при возникновении коллизии осуществляется
добавление нового элемента в этот список.
Для метода цепочек коэффициент заполнения может быть больше 1 и с увеличением
числа элементов производительность убывает линейно. Такие таблицы удобно использовать,
если заранее неизвестно количество хранимых элементов, либо их может быть достаточно
много, что приводит к большим значениям коэффициента заполнения.
Среди методов открытой реализации различают:
 линейное пробирование;
 квадратичное пробирование;
 двойное хэширование.
Недостатки структур с методом открытой адресации:
 Количество элементов в хэш-таблице не может превышать размера массива.
По мере увеличения числа элементов и повышения коэффициента заполнения
производительность структуры резко падает, поэтому необходимо проводить
перехэширование.
 Сложно организовать удаление элемента.
 Первые два метод а открытой адресации приводят к проблеме первичной и
вторичной группировок.
Преимущества хэш-таблицы с открытой адресацией:
 отсутствие затрат на создание и хранение объектов списка;
 простота организации сериализации/десериализации объекта.

Как работает HashMap при попытке сохранить в него два элемента по ключам с
одинаковым hashCode(), но для которых equals() == false?
По значению hashCode() вычисляется индекс ячейки массива, в список которой этот
элемент будет добавлен. Перед добавлением осуществляется проверка на наличие
элементов в этой ячейке. Если элементы с таким hashCode() уже присутствует, но их equals()
методы не равны, то элемент будет добавлен в конец списка.

Какое начальное количество корзин в HashMap?


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

Какова оценка временной сложности операций над элементами из HashMap?


Гарантирует ли HashMap указанную сложность выборки элемента?
В общем случае операции добавления, поиска и удаления элементов занимают
константное время.
Данная сложность не гарантируется, т.к. если хэш-функция распределяет элементы по
корзинам равномерно, временная сложность станет не хуже Логарифмического времени
O(log(N)), а в случае, когда хэш-функция постоянно возвращает одно и то же значение,
HashMap превратится в связный список со сложностью О(n).

Возможна ли ситуация, когда HashMap выродится в список даже с ключами имеющими


разные hashCode()?
Это возможно в случае, если метод, определяющий номер корзины будет возвращать
одинаковые значения.

В каком случае может быть потерян элемент в HashMap?


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

Почему нельзя использовать byte[] в качестве ключа в HashMap?


Хэш-код массива не зависит от хранимых в нем элементов, а присваивается при
создании массива (метод вычисления хэш-кода массива не переопределен и вычисляется по
стандартному [Link]() на основании адреса массива). Также у массивов не
переопределен equals и выполняется сравнение указателей. Это приводит к тому, что
обратиться к сохраненному с ключом-массивом элементу не получится при использовании
другого массива такого же размера и с такими же элементами, доступ можно осуществить
лишь в одном случае — при использовании той же самой ссылки на массив, что
использовалась для сохранения элемента.

Какова роль equals() и hashCode() в HashMap?


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

Каково максимальное число значений hashCode()?


Число значений следует из сигнатуры int hashCode() и равно диапазону типа int — 2 в
32.

Какое худшее время работы метода get(key) для ключа, который есть в HashMap?
O(N). Худший случай - это поиск ключа в HashMap, вырожденного в список по причине
совпадения ключей по hashCode() и для выяснения хранится ли элемент с определенным
ключом может потребоваться перебор всего списка.

Сколько переходов происходит в момент вызова [Link](key) по ключу, который


есть в таблице?
ключ равен null: 1 - выполняется единственный метод getForNullKey().
любой ключ отличный от null: 4 - вычисление хэш-кода ключа; определение номера
корзины; поиск значения; возврат значения.

Сколько создается новых объектов, когда вы добавляете новый элемент в HashMap?


Один новый объект статического вложенного класса Entry<K,V>.

Как и когда происходит увеличение количества корзин в HashMap?


Помимо capacity у HashMap есть еще поле loadFactor, на основании которого,
вычисляется предельное количество занятых корзин capacity * loadFactor. По умолчанию
loadFactor = 0.75. По достижению предельного значения, число корзин увеличивается в 2 раза
и для всех хранимых элементов вычисляется новое «местоположение» с учетом нового числа
корзин.

Объясните смысл параметров в конструкторе HashMap(int initialCapacity, float


loadFactor).
initialCapacity - исходный размер HashMap, количество корзин в хэш-таблице в момент
её создания.
loadFactor - коэффициент заполнения HashMap, при превышении которого происходит
увеличение количества корзин и автоматическое перехэширование. Равен отношению числа
уже хранимых элементов в таблице к ее размеру.

Будет ли работать HashMap, если все добавляемые ключи будут иметь одинаковый
hashCode()?
Да, будет, но в этом случае HashMap вырождается в связный список и теряет свои
преимущества.

Как перебрать все ключи Map?


Использовать метод keySet(), который возвращает множество Set<K> ключей.

Как перебрать все значения Map?


Использовать метод values(), который возвращает коллекцию Collection<V> значений.

Как перебрать все пары «ключ-значение» в Map?


Использовать метод entrySet(), который возвращает множество Set<[Link]<K, V>
пар «ключ-значение».

В чем разница между HashMap и IdentityHashMap? Для чего нужна IdentityHashMap?


IdentityHashMap - это структура данных, также реализующая интерфейс Map и
использующая при сравнении ключей (значений) сравнение ссылок, а не вызов метода
equals(). Другими словами, в IdentityHashMap два ключа k1 и k2 будут считаться равными,
если они указывают на один объект, т.е. выполняется условие k1 == k2.
IdentityHashMap не использует метод hashCode(), вместо которого применяется метод
[Link](), по этой причине IdentityHashMap по сравнению с HashMap имеет
более высокую производительность, особенно если последний хранит объекты с
дорогостоящими методами equals() и hashCode().
Одним из основных требований к использованию HashMap является неизменяемость
ключа, а, т.к. IdentityHashMap не использует методы equals() и hashCode(), то это правило на
него не распространяется.
IdentityHashMap может применяться для реализации сериализации/клонирования. При
выполнении подобных алгоритмов программе необходимо обслуживать хэш-таблицу со всеми
ссылками на объекты, которые уже были обработаны. Такая структура не должна
рассматривать уникальные объекты как равные, даже если метод equals() возвращает true.

В чем разница между HashMap и WeakHashMap? Для чего используется WeakHashMap?


В Java существует 4 типа ссылок: сильные (strong reference), мягкие (SoftReference),
слабые (WeakReference) и фантомные (PhantomReference). Особенности каждого типа ссылок
связаны с работой Garbage Collector. Если объект можно достичь только с помощью цепочки
WeakReference (то есть на него отсутствуют сильные и мягкие ссылки), то данный объект
будет помечен на удаление.
WeakHashMap - это структура данных, реализующая интерфейс Map и основанная на
использовании WeakReference для хранения ключей. Таким образом, пара «ключ-значение»
будет удалена из WeakHashMap, если на объект-ключ более не имеется сильных ссылок.
В качестве примера использования такой структуры данных можно привести
следующую ситуацию: допустим имеются объекты, которые необходимо расширить
дополнительной информацией, при этом изменение класса этих объектов нежелательно либо
невозможно. В этом случае добавляем каждый объект в WeakHashMap в качестве ключа, а в
качестве значения - нужную информацию. Таким образом, пока на объект имеется сильная
ссылка (либо мягкая), можно проверять хэш-таблицу и извлекать информацию. Как только
объект будет удален, то WeakReference для этого ключа будет помещен в ReferenceQueue и
затем соответствующая запись для этой слабой ссылки будет удалена из WeakHashMap.

В WeakHashMap используются WeakReferences. А почему бы не создать SoftHashMap на


SoftReferences?
SoftHashMap представлена в сторонних библиотеках, например, в Apache Commons.

В WeakHashMap используются WeakReferences. А почему бы не создать


PhantomHashMap на PhantomReferences?
PhantomReference при вызове метода get() возвращает всегда null, поэтому тяжело
представить назначение такой структуры данных.

58. TreeSet vs. HashSet vs. LinkedHashSet.


В чем отличия TreeSet и HashSet?
TreeSet обеспечивает упорядоченно хранение элементов в виде красно-черного
дерева. Сложность выполнения основных операций не хуже O(log(N)) (Логарифмическое
время).
HashSet использует для хранения элементов такой же подход, что и HashMap, за тем
отличием, что в HashSet в качестве ключа и значения выступает сам элемент, кроме того
HashSet не поддерживает упорядоченное хранение элементов и обеспечивает временную
сложность выполнения операций аналогично HashMap.

Что будет, если добавлять элементы в TreeSet по возрастанию?


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

Чем LinkedHashSet отличается от HashSet?


LinkedHashSet отличается от HashSet только тем, что в его основе лежит
LinkedHashMap вместо HashMap. Благодаря этому порядок элементов при обходе коллекции
является идентичным порядку добавления элементов (insertion-order). При добавлении
элемента, который уже присутствует в LinkedHashSet (т.е. с одинаковым ключом), порядок
обхода элементов не изменяется.

59. EnumSet.
Для Enum есть специальный класс [Link]. Зачем? Чем авторов не устраивал
HashSet или TreeSet?
EnumSet - это реализация интерфейса Set для использования с перечислениями
(Enum). В структуре данных хранятся объекты только одного типа Enum, указываемого при
создании. Для хранения значений EnumSet использует массив битов (bit vector), - это
позволяет получить высокую компактность и эффективность. Проход по EnumSet
осуществляется согласно порядку объявления элементов перечисления.
Все основные операции выполняются за O(1) и обычно (но негарантированно) быстрей
аналогов из HashSet, а пакетные операции (bulk operations), такие как containsAll() и retainAll()
выполняются даже гораздо быстрей.
Помимо всего EnumSet предоставляет множество статических методов инициализации
для упрощенного и удобного создания экземпляров.

60. TreeMap & LinkedHashMap.


LinkedHashMap - что в нем от LinkedList, а что от HashMap?
Реализация LinkedHashMap отличается от HashMap поддержкой двухсвязанного
списка, определяющего порядок итерации по элементам структуры данных. По умолчанию
элементы списка упорядочены согласно их порядку добавления в LinkedHashMap (insertion-
order). Однако порядок итерации можно изменить, установив параметр конструктора
accessOrder в значение true. В этом случае доступ осуществляется по порядку последнего
обращения к элементу (access-order). Это означает, что при вызове методов get() или put()
элемент, к которому обращаемся, перемещается в конец списка.
При добавлении элемента, который уже присутствует в LinkedHashMap (т.е. с
одинаковым ключом), порядок итерации по элементам не изменяется.

61. NavigableSet
Интерфейс унаследован от SortedSet и расширяет методы навигации находя
ближайшее совпадение по заданному значению. И сродни родительскому интерфейсу в
NavigableSet не может быть дубликатов.

Дополнительно
62. SOLID с реальными примерами.
Принцип единственной ответственности
Класс должен быть ответственен лишь за что-то одно. Если класс отвечает за
решение нескольких задач, его подсистемы, реализующие решение этих задач, оказываются
связанными друг с другом. Изменения в одной такой подсистеме ведут к изменениям в
другой.
Принцип открытости-закрытости
Программные сущности (классы, модули, функции) должны быть открыты для
расширения, но не для модификации.
Принцип подстановки Барбары Лисков
Необходимо, чтобы подклассы могли бы служить заменой для своих суперклассов.
Цель этого принципа заключаются в том, чтобы классы-наследники могли бы
использоваться вместо родительских классов, от которых они образованы, не нарушая работу
программы. Если оказывается, что в коде проверяется тип класса, значит принцип
подстановки нарушается.
Принцип разделения интерфейса
Создавайте узкоспециализированные интерфейсы, предназначенные для конкретного
клиента. Клиенты не должны зависеть от интерфейсов, которые они не используют.
Этот принцип направлен на устранение недостатков, связанных с реализацией
больших интерфейсов.
Принцип инверсии зависимостей
Объектом зависимости должна быть абстракция, а не что-то конкретное.
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа
модулей должны зависеть от абстракций.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
В процессе разработки программного обеспечения существует момент, когда
функционал приложения перестает помещаться в рамках одного модуля. Когда это
происходит, нам приходится решать проблему зависимостей модулей. В результате,
например, может оказаться так, что высокоуровневые компоненты зависят от низкоуровневых
компонентов.
[Link]

63. Класс Optional.


Что такое Optional?
Опциональное значение Optional — это контейнер для объекта, который может
содержать или не содержать значение null. Такая обёртка является удобным средством
предотвращения NullPointerException, т.к. имеет некоторые функции высшего порядка,
избавляющие от добавления повторяющихся if null/notNull проверок.

64. Ромбовидное наследование.

Многопоточность

вопросы-ответы Хилькевич Игорь 15.04.2020

Вопросы из “all question”


1. Java Memory Model (JMM).
2. Потокобезопасность, конкуренция, параллелизм, корпоративная и
вытесняющая многозадачность.
3. Ordering, as-if-serial semantics, sequental consistency, visibility,
atomicity, happense-before, mutual exclusion, safe publication.
4. Процесс vs. поток. Зеленые потоки.
5. Способы создания потока в java.
6. Thread vs. Runnable (start() vs. run()).
7. Как принудительно запустить поток? (никак)
8. Монитор в java.
9. Понятие синхронизации. Способы синхронизации в java.
10. Состояния потока.
11. private mutex.
12. wait(), notify(), notyfyAll()
13. sleep() vs. yield()
14. [Link]()
15. Race condition, deadlock, livelock, starvation.
16. [Link]()
17. volatile.
18. Atomic переменные.
19. Приоритет потока (от 1 до 10, default 5).
20. Потоки демоны.
21. Runnable vs. Callable.
22. FutureTask.
23. CyclicBarrier vs. CountDownLatch.
24. [Link]() vs interrupt(). interrupted() vs. isInterrupted().
25. Исключение в потоке.
26. Пулл потоков.
27. Executor, Executors.
28. Fork/Join framework.
29. Semaphore.
30. ThreadLocal-переменная.
31. synchronized vs. ReentrantLock.
32. ReedWriteLock.
33. Блокирующий метод.
34. Busy spin.

Ответы
1. Java Memory Model (JMM).
Расскажите о модели памяти Java?
Модель памяти Java (Java Memory Model, JMM) описывает поведение потоков в среде
исполнения Java. Это часть семантики языка Java, набор правил, описывающий выполнение
многопоточных программ и правил, по которым потоки могут взаимодействовать друг с другом
посредством основной памяти.
Program order - правило порядка выполнения программы.(Program order гарантирует, что в
отдельных потоках оптимизация переупорядочения, введенная компилятором, не
может привести к результатам, отличным от того, что произошло бы, если бы
программа выполнялась последовательно.)
Формально модель памяти определяет набор действий межпоточного взаимодействия (эти
действия включают в себя, в частности, чтение и запись переменной, захват и освобождений
монитора, чтение и запись volatile переменной, запуск нового потока), а также модель памяти
определяет отношение между этими действиями -happens-before - абстракции обозначающей,
что если операция AX связана отношением happens-before с операцией BY, то весь код
следуемый за операцией BY, выполняемый в одном потоке, видит все изменения, сделанные
другим потоком, до операции AX.

Существует несколько основных правил для отношения happens-before:


 В рамках одного потока любая операция идущая в program order является - happens-
before по отношению к любой операциией следующей за ней в исходном коде;
 Освобождение монитора (unlock) happens-before захват того же монитора (lock);
 Выход из synchronized блока/метода happens-before вход в synchronized блок/метод на
том же мониторе;
 Запись volatile поля happens-before чтение того же самого volatile поля;
 Завершение метода run() экземпляра класса Thread happens-before выход из метода
join() или возвращение false методом isAlive() экземпляром того же потока;
 Вызов метода start() экземпляра класса Thread happens-before начало метода run()
экземпляра того же потока;
 Завершение конструктора happens-before начало метода finalize() этого класса;
 Вызов метода interrupt() на потоке happens-before обнаружению потоком факта, что
данный метод был вызван либо путем выбрасывания исключения InterruptedException,
либо с помощью методов isInterrupted() или interrupted().
 Связь happens-before транзитивна, т.е. если X happens-before Y, а Y happens-before Z,
то X happens-before Z.
 Освобождение/захват монитора и запись/чтение в volatile переменную связаны
отношением happens-before, только если операции про товодятся над одним и тем же
экземпляром объекта.
 В отношении happens-before участвуют только два потока, о поведении остальных
потоков ничего сказать нельзя, пока в каждом из них не наступит отношение happens-
before с другим потоком.
Можно выделить несколько основных областей, имеющих отношение к модели памяти:
 Видимость (visibility). Один поток может в какой-то момент временно сохранить
значение некоторых полей не в основную память, а в регистры или локальный кэш
процессора, таким образом второй поток, выполняемый на другом процессоре, читая
из основной памяти, может не увидеть последних изменений поля. И наоборот, если
поток на протяжении какого-то времени работает с регистрами и локальными кэшами,
читая данные оттуда, он может сразу не увидеть изменений, сделанных другим
потоком в основную память.
К вопросу видимости имеют отношение следующие ключевые слова языка Java: synchronized,
volatile, final.
С точки зрения Java все переменные (за исключением локальных переменных, объявленных
внутри метода) хранятся в главной памяти, которая доступна всем потокам, кроме этого,
каждый поток имеет локальную—рабочую—память, где он хранит копии переменных, с
которыми он работает, и при выполнении программы поток работает только с этими копиями.
Надо отметить, что это описание не требование к реализации, а всего лишь модель, которая
объясняет поведение программы, так, в качестве локальной памяти не обязательно
выступает кэш память, это могут быть регистры процессора или потоки могут вообще не
иметь локальной памяти.
При входе в synchronized метод или блок поток обновляет содержимое локальной памяти, а
при выходе из synchronized метода или блока поток записывает изменения, сделанные в
локальной памяти, в главную. Такое поведение synchronized методов и блоков следует из
правил для отношения «происходит раньше»: так как все операции с памятью происходят
раньше освобождения монитора и освобождение монитора происходит раньше захвата
монитора, то все операции с памятью, которые были сделаны потоком до выхода из
synchronized блока должны быть видны любому потоку, который входит в synchronized блок
для того же самого монитора. Очень важно, что это правило работает только в том случае,
если потоки синхронизируются, используя один и тот же монитор!
Что касается volatile переменных, то запись таких переменных производится в основную
память, минуя локальную. и чтение volatile переменной производится также из основной
памяти, то есть значение переменной не может сохраняться в регистрах или локальной
памяти потока и операция чтения этой переменной гарантированно вернёт последнее
записанное в неё значение.
Также модель памяти определяет дополнительную семантику ключевого слова final,
имеющую отношение к видимости: после того как объект был корректно создан, любой поток
может видеть значения его final полей без дополнительной синхронизации. «Корректно
создан» означает, что ссылка на создающийся объект не должна использоваться до тех пор,
пока не завершился конструктор объекта. Наличие такой семантики для ключевого слова final
позволяет создание неизменяемых (immutable) объектов, содержащих только final поля, такие
объекты могут свободно передаваться между потоками без обеспечения синхронизации при
передаче.
Есть одна проблема, связанная с final полями: реализация разрешает менять значения таких
полей после создания объекта (это может быть сделано, например, с использованием
механизма reflection). Если значение final поля—константа, чьё значение известно на момент
компиляции, изменения такого поля могут не иметь эффекта, так-как обращения к этой
переменной могли быть заменены компилятором на константу. Также спецификация
разрешает другие оптимизации, связанные с final полями, например, операции чтения final
переменной могут быть переупорядочены с операциями, которые потенциально могут
изменить такую переменную. Так что рекомендуется изменять final поля объекта только
внутри конструктора, в противном случае поведение не специфицировано.
 Reordering (переупорядочивание). Для увеличения производительности
процессор/компилятор могут переставлять местами некоторые инструкции/операции.
Вернее, с точки зрения потока, наблюдающего за выполнением операций в другом
потоке, операции могут быть выполнены не в том порядке, в котором они идут в
исходном коде. Тот же эффект может наблюдаться, когда один поток кладет
результаты первой операции в регистр или локальный кэш, а результат второй
операции попадает непосредственно в основную память. Тогда второй поток,
обращаясь к основной памяти может сначала увидеть результат второй операции, и
только потом первой, когда все регистры или кэши синхронизируются с основной
памятью. Еще одна причина reordering, может заключаться в том, что процессор может
решить поменять порядок выполнения операций, если, например, сочтет что такая
последовательность выполнится быстрее.
Вопрос reordering также регулируется набором правил для отношения «происходит раньше» и
у этих правил есть следствие, касающееся порядка операций, используемое на практике:
операции чтения и записи volatile переменных не могут быть переупорядочены с операциями
чтения и записи других volatile и не-volatile переменных. Это следствие делает возможным
использование volatile переменной как флага, сигнализирующем об окончании какого-либо
действия. В остальном правила, касающиеся порядка выполнения операций, гарантируют
упорядоченность операций для конкретного набора случаев (таких как, например, захват и
освобождение монитора), во всех остальных случаях оставляя компилятору и процессору
полную свободу для оптимизаций.

В чем заключаются различия между cтеком (stack) и кучей (heap) с точки зрения
многопоточности?
Cтек – участок памяти, тесно связанный с потоками. У каждого потока есть свой стек, которые
хранит локальные переменные, параметры методов и стек вызовов. Переменная, хранящаяся
в стеке одного потока, не видна для другого.
Куча – общий участок памяти, который делится между всеми потоками. Объекты, неважно
локальные или любого другого уровня, создаются в куче. Для улучшения производительности,
поток обычно кэширует значения из кучи в свой стек, в этом случае для того, чтобы указать
потоку, что переменную следует читать из кучи используется ключевое слово volatile.

Как поделиться данными между двумя потоками?


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

Какой параметр запуска JVM используется для контроля размера стека потока?
-Xss

Как получить дамп потока?


Среды исполнения Java на основе HotSpot генерируют только дамп в формате HPROF. В
распоряжении разработчика имеется несколько интерактивных методов генерации дампов и
один метод генерации дампов на основе событий.
Интерактивные методы:
 Использование Ctrl+Break: если для исполняющегося приложения установлена опция
командной строки -XX:+HeapDumpOnCtrlBreak, то дамп формата HPROF генерируется
вместе с дампом потока при наступлении события Ctrl+Break или SIGQUIT (обычно
генерируется с помощью kill -3), которое инициируется посредством консоли. Эта
опция может быть недоступна в некоторых версиях. В этом случае можно попытаться
использовать следующую опцию: -Xrunhprof:format=b,file=[Link]
 Использование инструмента jmap: утилита jmap, поставляемая в составе каталога /bin/
комплекта JDK, позволяет запрашивать дамп HPROF из исполняющегося процесса.
 Использование операционной системы: Для создания файла ядра можно
воспользоваться неразрушающей командой gcore или разрушающими командами kill -
6 или kill -11. Затем извлечь дамп кучи из файла ядра с помощью утилиты jmap.
 Использование инструмента JConsole. Операция dumpHeap предоставляется в
JConsole как MBean-компонент HotSpotDiagnostic. Эта операция запрашивает
генерацию дампа в формате HPROF.
Метод на основе событий:
Событие OutOfMemoryError: Если для исполняющегося приложения установлена опция
командной строки -XX:+HeapDumpOnOutOfMemoryError, то при возникновении ошибки
OutOfMemoryError генерируется дамп формата HPROF. Это идеальный метод для
«production» систем, поскольку он практически обязателен для диагностирования проблем
памяти и не сопровождается постоянными накладными расходами с точки зрения
производительности. В старых выпусках сред исполнения Java на базе HotSpot для этого
события не устанавливается предельное количество дампов кучи в пересчете на одну JVM; в
более новых выпусках допускается не более одного дампа кучи для этого события на каждый
запуск JVM.

2. Потокобезопасность, конкуренция, параллелизм, корпоративная и


вытесняющая многозадачность.
Что такое «потокобезопасность»?
Потокобезопасность – свойство объекта или кода, которое гарантирует, что при исполнении
или использовании несколькими потоками, код будет вести себя, как
предполагается. Например потокобезопасный счётчик не пропустит ни один счёт, даже если
один и тот же экземпляр этого счётчика будет использоваться несколькими потоками.

В чём разница между «конкуренцией» и «параллелизмом»?


Конкуренция — это способ одновременного решения множества задач.
Признаки:
 Наличие нескольких потоков управления (например Thread в Java, корутина в Kotlin),
если поток управления один, то конкурентного выполнения быть не может
 Недетерминированный(Независимый друг от друга) результат выполнения. Результат
зависит от случайных событий, реализации и того как была проведена синхронизация.
Даже если каждый поток полностью детерминированный, итоговый результат будет
недетерминированным
Параллелизм — это способ выполнения разных частей одной задачи.
Признаки:
 Необязательно имеет несколько потоков управления
 Может приводить к детерминированному результату, так, например, результат
умножения каждого элемента массива на число, не изменится, если умножать его по
частям параллельно.

Что такое «кооперативная многозадачность»? Какой тип многозадачности использует


Java? Чем обусловлен этот выбор?
Кооперативная многозадачность - это способ деления процессорного времени между
потоками, при котором каждый поток обязан отдавать управление следующему добровольно.
Преимущества такого подхода - простота реализации, меньшие накладные расходы на
переключение контекста.
Недостатки - если один поток завис или ведет себя некорректно, то зависает целиком вся
система и другие потоки никогда не получат управление.
Java использует вытесняющую многозадачность, при которой решение о переключении между
потоками процесса принимает операционная система.
В отличие от кооперативной многозадачности управление операционной системе передается
вне зависимости от состояния работающих приложений, благодаря чему, отдельные
зависшие потоки процесса, как правило, не «подвешивают» всю систему целиком. За счёт
регулярного переключения между задачами также улучшается отзывчивость приложения и
повышается оперативность освобождения ресурсов, которые больше не используются.
В реализации вытесняющая многозадачность отличается от кооперативной, в частности, тем,
что требует обработки системного прерывания от аппаратного таймера.

3. Ordering, as-if-serial semantics, sequental consistency, visibility,


atomicity, happense-before, mutual exclusion, safe publication.
Что такое ordering, as-if-serial semantics, sequential consistency, visibility, atomicity,
happens-before, mutual exclusion, safe publication?
ordering механизм, который определяет, когда один поток может увидеть out-of-order
(неверный) порядок исполнения инструкций другого потока. CPU для для повышения
производительности может переупорядочивать процессорные инструкции и выполнять их в
произвольном порядке до тех пор пока для потока внутри не будет видно никаких отличий.
Гарантия предоставляемая этим механизмом называется as-if-serial semantics.
sequential consistency - то же что и as-if-serial semantics, гарантия того, что в рамках
одного потока побочные эффекты от всех операций будут такие, как будто все операции
выполняются последовательно.
visibility определяет, когда действия в одном потоке становятся видны из другого
потока.
happens-before - логическое ограничение на порядок выполнения инструкций
программы. Если указывается, что запись в переменную и последующее ее чтение связаны
через эту зависимость, то как бы при выполнении не переупорядочивались инструкции, в
момент чтения все связанные с процессом записи результаты уже зафиксированы и видны.
atomicity — атомарность операций. Атомарная операция выглядит единой и
неделимой командой процессора, которая может быть или уже выполненной или ещё
невыполненной.
mutual exclusion (взаимоисключающая блокировка, семафор с одним состоянием) -
механизм, гарантирующий потоку исключительный доступ к ресурсу. Используется для
предотвращения одновременного доступа к общему ресурсу. В каждый момент времени
таким ресурсом может владеть только один поток. Простейший пример: synchronized(obj)
{ … }.
safe publication? - показ объектов другим потокам из текущего, не нарушая ограничений
visibility. Способы такой публикации в Java:
static{} инициализатор;
volatile переменные;
atomic переменные;
сохранение в разделяемой переменной, корректно защищенной с использованием
synchronized(), синхронизаторов или других конструкций, создающих read/write memory barrier;
final переменные в разделяемом объекте, который был корректно
проинициализирован.

4. Процесс vs. поток. Зелёные потоки.


Чем отличается процесс от потока?
Процесс — экземпляр программы во время выполнения, независимый объект, которому
выделены системные ресурсы (например, процессорное время и память). Каждый процесс
выполняется в отдельном адресном пространстве: один процесс не может получить доступ к
переменным и структурам данных другого. Если процесс хочет получить доступ к чужим
ресурсам, необходимо использовать межпроцессное взаимодействие. Это могут быть
конвейеры, файлы, каналы связи между компьютерами и многое другое.
Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к
которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит
только его данные и находится в полном его распоряжении. Операционная система же
отвечает за то, как виртуальное пространство процесса проецируется на физическую память.
Поток(thread) — определенный способ выполнения процесса, определяющий
последовательность исполнения кода в процессе. Потоки всегда создаются в контексте
какого-либо процесса, и вся их жизнь проходит только в его границах. Потоки могут исполнять
один и тот же код и манипулировать одними и теми же данными, а также совместно
использовать описатели объектов ядра, поскольку таблица описателей создается не в
отдельных потоках, а в процессах. Так как потоки расходуют существенно меньше ресурсов,
чем процессы, в процессе выполнения работы выгоднее создавать дополнительные потоки и
избегать создания новых процессов.

Что такое «зеленые потоки» и есть ли они в Java?


Зелёные (легковесные) потоки(green threads) - потоки эмулируемые виртуальной машиной
или средой исполнения. Создание зелёного потока не подразумевает под собой создание
реального потока ОС.
Виртуальная машина Java берёт на себя заботу о переключении между разными green
threads, а сама машина работает как один поток ОС. Это даёт несколько преимуществ. Потоки
ОС относительно дороги в большинстве POSIX-систем. Кроме того, переключение между
native threads гораздо медленнее, чем между green threads.
Это всё означает, что в некоторых ситуациях green threads гораздо выгоднее, чем native
threads. Система может поддерживать гораздо большее количество green threads, чем
потоков OС. Например, гораздо практичнее запускать новый green thread для нового HTTP-
соединения к веб-серверу, вместо создания нового native thread.
Однако есть и недостатки. Самый большой заключается в том, что вы не можете исполнять
два потока одновременно. Поскольку существует только один native thread, только он и
вызывается планировщиком ОС. Даже если у вас несколько процессоров и несколько green
threads, только один процессор может вызывать green thread. И всё потому, что с точки зрения
планировщика заданий ОС всё это выглядит одним потоком.
Начиная с версии 1.2 Java поддерживает native threads, и с тех пор они используются по
умолчанию.

5. Способы создания потока в java.


Каким образом можно создать поток?
 Создать потомка класса Thread и переопределить его метод run();
 Создать объект класса Thread, передав ему в конструкторе экземпляр класса,
реализующего интерфейс Runnable. Эти интерфейс содержит метод run(), который
будет выполняться в новом потоке. Поток закончит выполнение, когда завершится его
метод run().
 Вызвать метод submit() у экземпляра класса реализующего интерфейс
ExecutorService, передав ему в качестве параметра экземпляр класса реализующего
интерфейс Runnable или Callable (содержит метод call(), в котором описывается логика
выполнения).

6. Thread vs. Runnable (start() vs. run()).


Чем различаются Thread и Runnable?
Thread - это класс, некоторая надстройка над физическим потоком.
Runnable - это интерфейс, представляющий абстракцию над выполняемой задачей.
Помимо того, что Runnable помогает разрешить проблему множественного наследования,
несомненный плюс от его использования состоит в том, что он позволяет логически отделить
логику выполнения задачи от непосредственного управления потоком.

В чём заключается разница между методами start() и run()?


Несмотря на то, что start() вызывает метод run() внутри себя, это не то же самое, что просто
вызов run(). Если run() вызывается как обычный метод, то он вызывается в том же потоке и
никакой новый поток не запускается, как это происходит, в случае, когда вы вызываете метод
start().
7. Как принудительно запустить поток? (никак)
Как принудительно запустить поток?
Никак. В Java не существует абсолютно никакого способа принудительного запуска
потока. Это контролируется JVM и Java не предоставляет никакогоникакго API для
управления этим процессом.

8. Монитор в java.
Что такое «монитор» в Java?
Монитор, мьютекс (mutex) – это средство обеспечения контроля за доступом к ресурсу.
У монитора может быть максимум один владелец в каждый текущий момент времени.
Следовательно, если кто-то использует ресурс и захватил монитор для обеспечения
единоличного доступа, то другой, желающий использовать тот же ресурс, должен подождать
освобождения монитора, захватить его и только потом начать использовать ресурс.
Удобно представлять монитор как id захватившего его объекта. Если этот id равен 0 –
ресурс свободен. Если не 0 – ресурс занят. Можно встать в очередь и ждать ешго
освобождения.

В Java у каждого экземпляра объекта есть монитор, который контролируется


непосредственно виртуальной машиной. Используется он так: любой нестатический
synchronized-метод при своем вызове прежде всего пытается захватить монитор того объекта,
у которого он вызван (на который он может сослаться как на this). Если это удалось – метод
исполняется. Если нет – поток останавливается и ждет, пока монитор будет отпущен.

9. Понятие синхронизации. Способы синхронизации в java.


Дайте определение понятию «синхронизация».
Синхронизация это процесс, который позволяет выполнять потоки параллельно.
В Java все объекты имеют одну блокировку, благодаря которой только один поток
одновременно может получить доступ к критическому коду в объекте. Такая синхронизация
помогает предотвратить повреждение состояния объекта. Если поток получил блокировку, ни
один другой поток не может войти в синхронизированный код, пока блокировка не будет
снята. Когда поток, владеющий блокировкой, выходит из синхронизированного кода,
блокировка снимается. Теперь другой поток может получить блокировку объекта и выполнить
синхронизированный код. Если поток пытается получить блокировку объекта, когда другой
поток владеет блокировкой, поток переходит в состояние Блокировки до тех пор, пока
блокировка не снимется.

Какие существуют способы синхронизации в Java?


Системная синхронизация с использованием wait()/notify(). Поток, который ждет
выполнения каких-либо условий, вызывает у этого объекта метод wait(), предварительно
захватив его монитор. На этом его работа приостанавливается. Другой поток может вызвать
на этом же самом объекте метод notify() (опять же, предварительно захватив монитор
объекта), в результате чего, ждущий на объекте поток «просыпается» и продолжает свое
выполнение. В обоих случаях монитор надо захватывать в явном виде, через synchronized-
блок, потому как методы wait()/notify() не синхронизированы!
Системная синхронизация с использованием join(). Метод join(), вызванный у
экземпляра класса Thread, позволяет текущему потоку остановиться до того момента, как
поток, связанный с этим экземпляром, закончит работу.
Использование классов из пакета [Link], который предоставляет набор
классов для организации межпоточного взаимодействия. Примеры таких классов - Lock,
Semaphore и пр.. Концепция данного подхода заключается в использовании атомарных
операций и переменных.

10. Состояния потока.


В каких состояниях может находиться поток?
Потоки могут находиться в одном из следующих состояний:
 Новый (New). После создания экземпляра потока, он находится в состоянии Новый до
тех пор, пока не вызван метод start(). В этом состоянии поток не считается живым.
 Работоспособный (Runnable). Поток переходит в состояние Работоспособный, когда
вызывается метод start(). Поток может перейти в это состояние также из состояния
Работающий или из состояния Блокирован. Когда поток находится в этом состоянии,
он считается живым.
 Работающий (Running). Поток переходит из состояния Работоспособный в состояние
Работающий, когда Планировщик потоков выбирает его как работающий в данный
момент.
 Живой, но не работоспособный (Alive, but not runnable). Поток может быть живым, но
не работоспособным по нескольким причинам:
 Ожидание (Waiting). Поток переходит в состояние Ожидания, вызывая метод wait().
Вызов notify() или notifyAll() может перевести поток из состояния Ожидания в состояние
Работоспособный.
 Сон (Sleeping). Метод sleep() переводит поток в состояние Сна на заданный
промежуток времени в миллисекундах.
 Блокировка (Blocked). Поток может перейти в это состояние, в ожидании ресурса,
такого как ввод/вывод или из-за блокировки другого объекта. В этом случае поток
переходит в состояние Работоспособный, когда ресурс становится доступен.
 Мёртвый (Dead). Поток считается мертвым, когда его метод run() полностью выполнен.
Мертвый поток не может перейти ни в какое другое состояние, даже если для него
вызван метод start().

11. private mutex.


Можно ли создавать новые экземпляры класса, пока выполняется static synchronized
метод?
Да, можно создавать новые экземпляры класса, так как статические поля не принадлежат к
экземплярам класса.

Зачем может быть нужен private мьютекс?


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

На каком объекте происходит синхронизация при вызове static synchronized метода?


У синхронизированного статического метода нет доступа к this, но есть доступ к объекту
класса Class, он присутствует в единственном экземпляре и именно он выступает в качестве
монитора для синхронизации статических методов. Таким образом, следующая конструкция:
public class SomeClass {

public static synchronized void someMethod() {


//code
}
}
эквивалентна такой:
public class SomeClass {

public static void someMethod(){


synchronized([Link]){
//code
}
}
}

12. wait(), notify(), notyfyAll()


Как работают методы wait() и notify()/notifyAll()?
Эти методы поопределены у класса Object и предназначены для взаимодействия потоков
между собой при межпоточной синхронизации.
 wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до
тех пор, пока другой поток не вызовет метод notify()/notifyAll();
 notify(): продолжает работу потока, у которого ранее был вызван метод wait();
 notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait().
Когда вызван метод wait(), поток освобождает блокировку на объекте и переходит из
состояния Работающий (Running) в состояние Ожидания (Waiting). Метод notify() подаёт
сигнал одному из потоков, ожидающих на объекте, чтобы перейти в состояние
Работоспособный (Runnable). При этом невозможно определить, какой из ожидающих потоков
должен стать работоспособным. Метод notifyAll() заставляет все ожидающие потоки для
объекта вернуться в состояние Работоспособный (Runnable). Если ни один поток не
находится в ожидании на методе wait(), то при вызове notify() или notifyAll() ничего не
происходит.
Поток может вызвать методы wait() или notify() для определённого объекта, только если он в
данный момент имеет блокировку на этот объект. wait(), notify() и notifyAll() должны
вызываться только из синхронизированного кода.

В чем разница между notify() и notifyAll()?


Дело в том, что «висеть» на методе wait() одного монитора могут сразу несколько потоков.
При вызове notify() только один из них выходит из wait() и пытается захватить монитор, а
затем продолжает работу со следующего после wait() оператора. Какой из них выйдет -
заранее неизвестно. А при вызове notifyAll(), все висящие на wait() потоки выходят из wait(), и
все они пытаются захватить монитор. Понятно, что в любой момент времени монитор может
быть захвачен только одним потоком, а остальные ждут своей очереди. Порядок очереди
определяется планировщиком потоков Java.

Почему методы wait() и notify() вызываются только в синхронизированном блоке?


Монитор надо захватывать в явном виде (через synchronized-блок), потому что методы wait() и
notify() не синхронизированы.

Чем отличается работа метода wait() с параметром и без параметра?


wait() без параметров освобождает монитор и переводит вызывающий поток в состояние
ожидания до тех пор, пока другой поток не вызовет метод notify()/notifyAll(),
с параметрами заставит поток ожидать заданное количество времени или вызова
notify()/notifyAll().

13. sleep() vs. yield()


Чем отличаются методы [Link]() и [Link]()?
Метод yield() служит причиной того, что поток переходит из состояния работающий (running) в
состояние работоспособный (runnable), давая возможность другим потокам активизироваться.
Но следующий выбранный для запуска поток может и не быть другим.
Метод sleep() вызывает засыпание текущего потока на заданное время, состояние изменяется
с работающий (running) на ожидающий (waiting).

Что значит «усыпить» поток?


Это значит приостановить его на определенный промежуток времени, вызвав в ходе его
выполнения статический метод [Link]() передав в качестве параметра необходимое
количество времени в миллисекундах. До истечения этого времени поток может быть выведен
из состояния ожидания вызовом interrupt() с выбрасыванием InterruptedException.

14. [Link]()
Как работает метод [Link]()?
Когда поток вызывает join() для другого потока, текущий работающий поток будет ждать, пока
другой поток, к которому он присоединяется, не будет завершён:
void join()
void join(long millis)
void join(long millis, int nanos)

15. Race condition, deadlock, livelock, starvation.


Что такое deadlock?
Взаимная блокировка (deadlock) - явление при котором все потоки находятся в режиме
ожидания. Происходит, когда достигаются состояния:
взаимного исключения: по крайней мере один ресурс занят в режиме неделимости и
следовательно только один поток может использовать ресурс в любой данный момент
времени.
удержания и ожидания: поток удерживает как минимум один ресурс и запрашивает
дополнительные ресурсов, которые удерживаются другими потоками.
отсутствия предочистки: операционная система не переназначивает ресурсы: если они
уже заняты, они должны отдаваться удерживающим потокам сразу же.
цикличного ожидания:
поток ждет освобождения ресурса другим потоком, который в свою очередь ждёт
освобождения ресурса заблокированного первым потоком.
Простейший способ избежать взаимной блокировки – не допускать цикличного
ожидания. Этого можно достичь, получая мониторы разделяемых ресурсов в определенном
порядке и освобождая их в обратном порядке.

Что такое livelock?


livelock – тип взаимной блокировки, при котором несколько потоков выполняют
бесполезную работу, попадая в зацикленность при попытке получения каких-либо ресурсов.
При этом их состояния постоянно изменяются в зависимости друг от друга. Фактической
ошибки не возникает, но КПД системы падает до 0. Часто возникает в результате попыток
предотвращения deadlock.
Реальный пример livelock, – когда два человека встречаются в узком коридоре и
каждый, пытаясь быть вежливым, отходит в сторону, и так они бесконечно двигаются из
стороны в сторону, абсолютно не продвигаясь в нужном им направлении.
Что такое race condition?
Состояние гонки (race condition) - ошибка проектирования многопоточной системы или
приложения, при которой эта работа напрямую зависит от того, в каком порядке выполняются
потоки. Состояние гонки возникает когда поток, который должен исполнится в начале,
проиграл гонку и первым исполняется другой поток: поведение кода изменяется, из-за чего
возникают недетерменированные ошибки.

Существует ли способ решения проблемы race condition?


Распространённые способы решения:
Использование локальной копии — копирование разделяемой переменной в локальную
переменную потока. Этот способ работает только тогда, когда переменная одна и
копирование производится атомарно (за одну машинную команду), использование volatile.
Синхронизация - операции над разделяемым ресурсом происходят в синхронизированном
блоке (при использовании ключевого слова synchronized).
Комбинирование методов - вышеперечисленные способы можно комбинировать, копируя
«опасные» переменные в синхронизированном блоке. С одной стороны, это снимает
ограничение на атомарность, с другой — позволяет избавиться от слишком больших
синхронизированных блоков.
Очевидных способов выявления и исправления состояний гонки не существует. Лучший
способ избавиться от гонок — правильное проектирование многозадачной системы.

16. [Link]()
Как проверить, удерживает ли поток монитор определённого ресурса?
Метод [Link](lock) возвращает true, когда текущий поток удерживает
монитор у определённого объекта.

17. volatile.
Для чего используется ключевое слово volatile, synchronized, transient, native?
volatile - этот модификатор вынуждает потоки отключить оптимизацию доступа и
использовать единственный экземпляр переменной. Если переменная примитивного типа –
этого будет достаточно для обеспечения потокобезопасности. Если же переменная является
ссылкой на объект – синхронизировано будет исключительно значение этой ссылки. Все же
данные, содержащиеся в объекте, синхронизированы не будут!
synchronized - это зарезервированное слово позволяет добиваться синхронизации в
помеченных им методах или блоках кода.
Ключевые слова transient и native к многопоточности никакого отношения не имеют,
первое используется для указания полей класса, которые не нужно сериализовать, а второе -
сигнализирует о том, что метод реализован в платформо-зависимом коде.

18. Atomic переменные.


В чём различия между volatile и Atomic переменными?
volatile принуждает использовать единственный экземпляр переменной, но не
гарантирует атомарность. Например, операция count++ не станет атомарной просто потому
что count объявлена volatile. C другой стороны class AtomicInteger предоставляет атомарный
метод для выполнения таких комплексных операций атомарно, например getAndIncrement() –
атомарная замена оператора инкремента, его можно использовать, чтобы атомарно
увеличить текущее значение на один. Похожим образом сконструированы атомарные версии
и для других типов данных.

В чём заключаются различия между [Link]*.compareAndSwap() и


[Link]*.weakCompareAndSwap()?
weakCompareAndSwap() не создает memory barrier и не дает гарантии happens-before;
weakCompareAndSwap() сильно зависит от кэша/CPU, и может возвращать false без
видимых причин;
weakCompareAndSwap(), более легкая, но поддерживаемая далеко не всеми
архитектурами и не всегда эффективная операция.

19. Приоритет потока (от 1 до 10, default 5).


Что значит «приоритет потока»?
Приоритеты потоков используются планировщиком потоков для принятия решений о
том, когда какому из потоков будет разрешено работать. Теоретически высокоприоритетные
потоки получают больше времени процессора, чем низкоприоритетные. Практически объем
времени процессора, который получает поток, часто зависит от нескольких факторов помимо
его приоритета.
Чтобы установить приоритет потока, используется метод класса Thread: final void
setPriority(int level). Значение level изменяется в пределах от Thread.MIN_PRIORITY = 1 до
Thread.MAX_PRIORITY = 10. Приоритет по умолчанию - Thread.NORM_PRlORITY = 5.
Получить текущее значение приоритета потока можно вызвав метод: final int
getPriority() у экземпляра класса Thread.

20. Потоки демоны.


Что такое «потоки-демоны»?
Потоки-демоны работают в фоновом режиме вместе с программой, но не являются
неотъемлемой частью программы. Если какой-либо процесс может выполняться на фоне
работы основных потоков выполнения и его деятельность заключается в обслуживании
основных потоков приложения, то такой процесс может быть запущен как поток-демон с
помощью метода setDaemon(boolean value), вызванного у потока до его запуска. Метод
boolean isDaemon() позволяет определить, является ли указанный поток демоном или нет.
Базовое свойство потоков-демонов заключается в возможности основного потока приложения
завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода
метода main(), не обращая внимания на то, что поток-демон еще работает.

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


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

21. Runnable vs. Callable.


Чем отличаются два интерфейса Runnable и Callable?
Интерфейс Runnable появился в Java 1.0, а интерфейс Callable был введен в Java 5.0
в составе библиотеки [Link];
Классы, реализующие интерфейс Runnable для выполнения задачи должны
реализовывать метод run(). Классы, реализующие интерфейс Callable - метод call();
Метод [Link]() не возвращает никакого значения, [Link]() возвращает
объект Future, который может содержать результат вычислений;
Метод run() не может выбрасывать проверяемые исключения, в то время как метод
call() может.

22. FutureTask.
Что такое FutureTask?
FutureTask представляет собой отменяемое асинхронное вычисление в параллельном
Java приложении. Этот класс предоставляет базовую реализацию Future, с методами для
запуска и остановки вычисления, методами для запроса состояния вычисления и извлечения
результатов. Результат может быть получен только когда вычисление завершено, метод
получения будет заблокирован, если вычисление ещё не завершено. Объекты FutureTask
могут быть использованы для обёртки объектов Callable и Runnable. Так как FutureTask
реализует Runnable, его можно передать в Executor на выполнение.
23. CyclicBarrier vs. CountDownLatch.
В чем заключаются различия между CyclicBarrier и CountDownLatch?
CountDownLatch (замок с обратным отсчетом) предоставляет возможность любому
количеству потоков в блоке кода ожидать до тех пор, пока не завершится определенное
количество операций, выполняющихся в других потоках, перед тем как они будут «отпущены»,
чтобы продолжить свою деятельность. В конструктор CountDownLatch(int count) обязательно
передается количество операций, которое должно быть выполнено, чтобы замок «отпустил»
заблокированные потоки.
Примером CountDownLatch из жизни может служить сбор экскурсионной группы: пока
не наберется определенное количество человек, экскурсия не начнется.
CyclicBarrier реализует шаблон синхронизации «Барьер». Циклический барьер
является точкой синхронизации, в которой указанное количество параллельных потоков
встречается и блокируется. Как только все потоки прибыли, выполняется опционное действие
(или не выполняется, если барьер был инициализирован без него), и, после того, как оно
выполнено, барьер ломается и ожидающие потоки «освобождаются». В конструкторы барьера
CyclicBarrier(int parties) и CyclicBarrier(int parties, Runnable barrierAction) обязательно
передается количество сторон, которые должны «встретиться», и, опционально, действие,
которое должно произойти, когда стороны встретились, но перед тем когда они будут
«отпущены».
CyclicBarrier является альтернативой метода join(), который «собирает» потоки только
после того, как они выполнились.
CyclicBarrier похож на CountDownLatch, но главное различие между ними в том, что
использовать «замок» можно лишь единожды - после того, как его счётчик достигнет нуля, а
«барьер» можно использовать неоднократно, даже после того, как он «сломается».

24. [Link]() vs interrupt(). interrupted() vs. isInterrupted().


Как остановить поток?
На данный момент в Java принят уведомительный порядок остановки потока (хотя JDK
1.0 и имеет несколько управляющих выполнением потока методов, например stop(), suspend()
и resume() - в следующих версиях JDK все они были помечены как deprecated из-за
потенциальных угроз взаимной блокировки).
Для корректной остановки потока можно использовать метод класса Thread - interrupt().
Этот метод выставляет некоторый внутренний флаг-статус прерывания. В дальнейшем
состояние этого флага можно проверить с помощью метода isInterrupted() или
[Link]() (для текущего потока). Метод interrupt() также способен вывести поток из
состояния ожидания или спячки. Т.е. если у потока были вызваны методы sleep() или wait() –
текущее состояние прервется и будет выброшено исключение InterruptedException. Флаг в
этом случае не выставляется.
Схема действия при этом получается следующей:
Реализовать поток.
В потоке периодически проводить проверку статуса прерывания через вызов
isInterrupted().
Если состояние флага изменилось или было выброшено исключение во время
ожидания/спячки, следовательно поток пытаются остановить извне.
Принять решение – продолжить работу (если по каким-то причинам остановиться
невозможно) или освободить заблокированные потоком ресурсы и закончить выполнение.
Возможная проблема, которая присутствует в этом подходе – блокировки на
потоковом вводе-выводе. Если поток заблокирован на чтении данных - вызов interrupt() из
этого состояния его не выведет. Решения тут различаются в зависимости от типа источника
данных. Если чтение идет из файла – долговременная блокировка крайне маловероятна и
тогда можно просто дождаться выхода из метода read(). Если же чтение каким-то образом
связано с сетью – стоит использовать неблокирующий ввод-вывод из Java NIO.
Второй вариант реализации метода остановки (а также и приостановки) – сделать
собственный аналог interrupt(). Т.е. объявить в классе потока флаги – на остановку и/или
приостановку и выставлять их путем вызова заранее определённых методов извне. Методика
действия при этом остаётся прежней – проверять установку флагов и принимать решения при
их изменении. Недостатки такого подхода. Во-первых, потоки в состоянии ожидания таким
способом не «оживить». Во-вторых, выставление флага одним потоком совсем не означает,
что второй поток тут же его увидит. Для увеличения производительности виртуальная машина
использует кеш данных потока, в результате чего обновление переменной у второго потока
может произойти через неопределенный промежуток времени (хотя допустимым решением
будет объявить переменную-флаг как volatile).

Почему не рекомендуется использовать метод [Link]()?


При принудительной остановке (приостановке) потока, stop() прерывает поток в
недетерменированном месте выполнения, в результате становится совершенно непонятно,
что делать с принадлежащими ему ресурсами. Поток может открыть сетевое соединение - что
в таком случае делать с данными, которые еще не вычитаны? Где гарантия, что после
дальнейшего запуска потока (в случае приостановки) он сможет их дочитать? Если поток
блокировал разделяемый ресурс, то как снять эту блокировку и не переведёт ли
принудительное снятие к нарушению консистентности системы? То же самое можно
расширить и на случай соединения с базой данных: если поток остановят посередине
транзакции, то кто ее будет закрывать? Кто и как будет разблокировать ресурсы?

В чем разница между interrupted() и isInterrupted()?


Механизм прерывания работы потока в Java реализован с использованием
внутреннего флага, известного как статус прерывания. Прерывание потока вызовом
[Link]() устанавливает этот флаг. Методы [Link]() и isInterrupted()
позволяют проверить, является ли поток прерванным.
Когда прерванный поток проверяет статус прерывания, вызывая статический метод
[Link](), статус прерывания сбрасывается.
Нестатический метод isInterrupted() используется одним потоком для проверки статуса
прерывания у другого потока, не изменяя флаг прерывания.

25. Исключение в потоке.


Что происходит, когда в потоке выбрасывается исключение?
Если исключение не поймано – поток «умирает» (переходит в состяние мёртв (dead)).
Если установлен обработчик непойманных исключений, то он возьмёт управление на
себя. [Link] – интерфейс, определённый как вложенный
интерфейс для других обработчиков, вызываемых, когда поток внезапно останавливается из-
за непойманного исключения. В случае, если поток собирается остановиться из-за
непойманного исключения, JVM проверяет его на наличие UncaughtExceptionHandler,
используя [Link](), и если такой обработчик найдет, то вызовет
у него метод uncaughtException(), передав этот поток и исключение в виде аргументов.

26. Пулл потоков.


Что такое «пул потоков»?
Создание потока является затратной по времени и ресурсам операцией. Количество
потоков, которое может быть запущено в рамках одного процесса также ограниченно. Чтобы
избежать этих проблем и в целом управлять множеством потоков более эффективно в Java
был реализован механизм пула потоков (thread pool), который создаётся во время запуска
приложения и в дальнейшем потоки для обработки запросов берутся и переиспользуются уже
из него. Таким образом, появляется возможность не терять потоки, сбалансировать
приложение по количеству потоков и частоте их создания.

Какого размера должен быть пул потоков?


Настраивая размер пула потоков, важно избежать двух ошибок: слишком мало потоков
(очередь на выполнение будет расти, потребляя много памяти) или слишком много потоков
(замедление работы всей систему из-за частых переключений контекста).
Оптимальный размер пула потоков зависит от количества доступных процессоров и
природы задач в рабочей очереди. На N-процессорной системе для рабочей очереди, которая
будет выполнять исключительно задачи с ограничением по скорости вычислений, можно
достигнуть максимального использования CPU с пулом потоков, в котором содержится N или
N+1 поток. Для задач, которые могут ждать осуществления I/O (ввода - вывода) - например,
задачи, считывающей HTTP-запрос из сокета – может понадобиться увеличение размера
пула свыше количества доступных процессоров, потому, что не все потоки будут работать все
время. Используя профилирование, можно оценить отношение времени ожидания (WT) ко
времени обработки (ST) для типичного запроса. Если назвать это соотношение WT/ST, то для
N-процессорной системе понадобится примерно N*(1 + WT/ST) потоков для полной
загруженности процессоров.
Использование процессора – не единственный фактор, важный при настройке размера
пула потоков. По мере возрастания пула потоков, можно столкнуться с ограничениями
планировщика, доступной памяти, или других системных ресурсов, таких, как количество
сокетов, дескрипторы открытого файла, или каналы связи базы данных.

Что будет, если очередь пула потоков уже заполнена, но подается новая задача?
Если очередь пула потоков заполнилась, то поданная задача будет «отклонена».
Например - метод submit() у ThreadPoolExecutor выкидывает RejectedExecutionException,
после которого вызывается RejectedExecutionHandler.

В чём заключается различие между методами submit() и execute() у пула потоков?


Оба метода являются способами подачи задачи в пул потоков, но между ними есть
небольшая разница.
execute(Runnable command) определён в интерфейсе Executor и выполняет поданную
задачу и ничего не возвращает.
submit() – перегруженный метод, определённый в интерфейсе ExecutorService.
Способен принимать задачи типов Runnable и Callable и возвращать объект Future, который
можно использовать для контроля и управления процессом выполнения, получения его
результата.

27. Executor, Executors.


Начиная с Java 1.5 Java API предоставляет фреймворк Executor, который позволяет
создавать различные типы пула потоков:
Executor - упрощенный интерфейс пула, содержит один метод для передачи задачи на
выполнение;
ExecutorService - расширенный интерфейс пула, с возможностью завершения всех
потоков;
AbstractExecutorService - базовый класс пула, реализующий интерфейс
ExecutorService;
Executors - фабрика объектов связанных с пулом потоков, в том числе позволяет
создать основные типы пулов;
ThreadPoolExecutor - пул потоков с гибкой настройкой, может служить базовым
классом для нестандартных пулов;
ForkJoinPool - пул для выполнения задач типа ForkJoinTask;
... и другие.
Методы Executors для создания пулов:
newCachedThreadPool() - если есть свободный поток, то задача выполняется в нем,
иначе добавляется новый поток в пул. Потоки не используемые больше минуты завершаются
и удаляются из кэша. Размер пула неограничен. Предназначен для выполнения множество
небольших асинхронных задач;
newCachedThreadPool(ThreadFactory threadFactory) - аналогично предыдущему, но с
собственной фабрикой потоков;
newFixedThreadPool(int nThreads) - создает пул на указанное число потоков. Если
новые задачи добавлены, когда все потоки активны, то они будут сохранены в очереди для
выполнения позже. Если один из потоков завершился из-за ошибки, на его место будет
запущен другой поток. Потоки живут до тех пор, пока пул не будет закрыт явно методом
shutdown().
newFixedThreadPool(int nThreads, ThreadFactory threadFactory) - аналогично
предыдущему, но с собственной фабрикой потоков;
newSingleThreadScheduledExecutor() - однопотоковый пул с возможностью выполнять
задачу через указанное время или выполнять периодически. Если поток был завершен из-за
каких-либо ошибок, то для выполнения следующей задачи будет создан новый поток.
newSingleThreadScheduledExecutor(ThreadFactory threadFactory) - аналогично
предыдущему, но с собственной фабрикой потоков;
newScheduledThreadPool(int corePoolSize) - пул для выполнения задач через указанное
время или переодически;
newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) - аналогично
предыдущему, но с собственной фабрикой потоков;
unconfigurableExecutorService(ExecutorService executor) - обертка на пул, запрещающая
изменять его конфигурацию;

28. Fork/Join framework.


Что такое «фреймворк Fork/Join»?
Фреймворк Fork/Join, представленный в JDK 7, - это набор классов и интерфейсов
позволяющих использовать преимущества многопроцессорной архитектуры современных
компьютеров. Он разработан для выполнения задач, которые можно рекурсивно разбить на
маленькие подзадачи, которые можно решать параллельно.
Этап Fork: большая задача разделяется на несколько меньших подзадач, которые в
свою очередь также разбиваются на меньшие. И так до тех пор, пока задача не становится
тривиальной и решаемой последовательным способом.
Этап Join: далее (опционально) идёт процесс «свёртки» - решения подзадач
некоторым образом объединяются пока не получится решение всей задачи.
Решение всех подзадач (в т.ч. и само разбиение на подзадачи) происходит
параллельно.
Для решения некоторых задач этап Join не требуется. Например, для параллельного
QuickSort — массив рекурсивно делится на всё меньшие и меньшие диапазоны, пока не
вырождается в тривиальный случай из 1 элемента. Хотя в некотором смысле Join будет
необходим и тут, т.к. всё равно остаётся необходимость дождаться пока не закончится
выполнение всех подзадач.
Ещё одно замечательное преимущество этого фреймворка заключается в том, что он
использует work-stealing алгоритм: потоки, которые завершили выполнение собственных
подзадач, могут «украсть» подзадачи у других потоков, которые всё ещё заняты.

29. Semaphore.
Что такое Semaphore?
Semaphore – это новый тип синхронизатора: семафор со счётчиком, реализующий
шаблон синхронизации Семафор. Доступ управляется с помощью счётчика: изначальное
значение счетчика задается в конструкторе при создании синхронизатора, когда поток заходит
в заданный блок кода, то значение счетчика уменьшается на единицу, когда поток его
покидает, то увеличивается. Если значение счетчика равно нулю, то текущий поток
блокируется, пока кто-нибудь не выйдет из защищаемого блока. Semaphore используется для
защиты дорогих ресурсов, которые доступны в ограниченном количестве, например
подключение к базе данных в пуле.

30. ThreadLocal-переменная.
Что такое ThreadLocal-переменная?
ThreadLocal - класс, позволяющий имея одну переменную, иметь различное её
значение для каждого из потоков.
У каждого потока - т.е. экземпляра класса Thread - есть ассоциированная с ним
таблица ThreadLocal-переменных. Ключами таблицы являются cсылки на объекты класса
ThreadLocal, а значениями - ссылки на объекты, «захваченные» ThreadLocal-переменными,
т.е. ThreadLocal-переменные отличаются от обычных переменных тем, что у каждого потока
свой собственный, индивидуально инициализируемый экземпляр переменной. Доступ к
значению можно получить через методы get() или set().
Например, если мы объявим ThreadLocal-переменную: ThreadLocal<Object> locals =
new ThreadLocal<Object>();. А затем, в потоке, сделаем [Link](myObject), то ключом
таблицы будет ссылка на объект locals, а значением - ссылка на объект myObject. При этом
для другого потока существует возможность «положить» внутрь locals другое значение.
Следует обратить внимание, что ThreadLocal изолирует именно ссылки на объекты, а
не сами объекты. Если изолированные внутри потоков ссылки ведут на один и тот же объект,
то возможны коллизии.
Так же важно отметить, что т.к. ThreadLocal-переменные изолированы в потоках, то
инициализация такой переменной должна происходить в том же потоке, в котором она будет
использоваться. Ошибкой является инициализация такой переменной (вызов метода set()) в
главном потоке приложения, потому как в данном случае значение, переданное в методе
set(), будет «захвачено» для главного потока, и при вызове метода get() в целевом потоке
будет возвращен null.

31. synchronized vs. ReentrantLock.


Назовите различия между synchronized и ReentrantLock?
В Java 5 появился интерфейс Lock предоставляющий возможности более
эффективного и тонкого контроля блокировки ресурсов. ReentrantLock – распространённая
реализация Lock, которая предоставляет Lock с таким же базовым поведением и семантикой,
как у synchronized, но расширенными возможностями, такими как опрос о блокировании (lock
polling), ожидание блокирования заданной длительности и прерываемое ожидание
блокировки. Кроме того, он предлагает гораздо более высокую эффективность
функционирования в условиях жесткой состязательности.
Что понимается под блокировкой с повторным входом (reentrant)? Просто то, что есть
подсчет сбора данных, связанный с блокировкой, и если поток, который удерживает
блокировку, снова ее получает, данные отражают увеличение, и тогда для реального
разблокирования нужно два раза снять блокировку. Это аналогично семантике synchronized;
если поток входит в синхронный блок, защищенный монитором, который уже принадлежит
потоку, потоку будет разрешено дальнейшее функционирование, и блокировка не будет
снята, когда поток выйдет из второго (или последующего) блока synchronized, она будет снята
только когда он выйдет из первого блока synchronized, в который он вошел под защитой
монитора.
Lock lock = new ReentrantLock();

[Link]();
try {
// update object state
}
finally {
[Link]();
}
Реализация ReentrantLock гораздо более масштабируема в условиях
состязательности, чем реализация synchronized. Это значит, что когда много потоков
соперничают за право получения блокировки, общая пропускная способность обычно лучше у
ReentrantLock, чем у synchronized. JVM требуется меньше времени на установление
очередности потоков и больше времени на непосредственно выполнение.
У ReentrantLock (как и у других реализаций Lock) блокировка должна обязательно
сниматься в finally блоке (иначе, если бы защищенный код выбросил исключение, блокировка
не была бы снята). Используя синхронизацию, JVM гарантирует, что блокировка
автоматически снимаются.
Резюмируя можно сказать, что когда состязания за блокировку нет либо оно очень
мало, то synchronized возможно будет быстрее. Если присутствует заметное состязание за
доступ к ресурсу, то скорее всего ReentrantLock даст некое преимущество.
32. ReedWriteLock.
Что такое ReadWriteLock?
ReadWriteLock – это интерфейс расширяющий базовый интерфейс Lock. Используется
для улучшения производительности в многопоточном процессе и оперирует парой связанных
блокировок (одна - для операций чтения, другая - для записи). Блокировка чтения может
удерживаться одновременно несколькими читающими потоками, до тех пор пока не появится
записывающий. Блокировка записи является эксклюзивеной.
Существует реализующий интерфейс ReadWriteLock класс ReentrantReadWriteLock,
который поддерживает до 65535 блокировок записи и до стольки же блокировок чтения.
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock rLock = [Link]();
Lock wLock = [Link]();

[Link]();
try {
// exclusive write
} finally {
[Link]();
}

[Link]();
try {
// shared reading
} finally {
[Link]();
}

33. Блокирующий метод.


Что такое «блокирующий метод»?
Блокирующий метод – метод, который блокируется, до тех пор, пока задание не
выполнится, например метод accept() у ServerSocket блокируется в ожидании подключения
клиента. Здесь блокирование означает, что контроль не вернётся к вызывающему методу до
тех пор, пока не выполнится задание. Так же существуют асинхронные или неблокирующиеся
методы, которые могут завершится до выполнения задачи.

34. Busy spin.


Что такое busy spin?
busy spin – это техника, которую программисты используют, чтобы заставить поток
ожидать при определённом условии. В отличие от традиционных методов wait(), sleep() или
yield(), которые подразумевают уступку процессорного времени, этот метод вместо уступки
выполняет пустой цикл. Это необходимо, для того, чтобы сохранить кэш процессора, т.к. в
многоядерных системах, существует вероятность, что приостановленный поток продолжит
свое выполнение уже на другом ядре, а это повлечет за собой перестройку состояния
процессорного кэша, которая является достаточно затратной процедурой.
SQL
вопросы-ответы Хилькевич Игорь 16.04.2020

Вопросы из “all question”


1. Операторы DDL, DML, TCL, DCL.
2. null в SQL.
3. Временная таблица (temporary table) vs. представления (view).
4. SELECT.
5. Виды join'ов. JOIN vs. подзапросы (sub-query).
6. HAVING vs. WHERE.
7. ORDER BY.
8. GROUP BY (NULL).
9. GROUP BY vs. DISTINCT.
10. Агрегатные функции. COUNT(*) vs. COUNT({column}).
11. EXISTS.
12. IN, BETWEEN, LIKE.
13. UNION vs. UNIONALL (ограничение на UNION).
14. Ограничения (constraints: primary key, foreign key, check, notnull,
unique).
15. Суррогатные ключи.
16. MERGE.
17. DELETE vs. TRUNCATE.
18. Индексы.
19. Хранимая процедура.
20. Триггер.
21. Курсор.
22. DATETIME vs. TIMESTAMP.
23. Транзакции. Принципы ACID.
24. Уровни изолированности транзакций.
25. Нормализация. Нормальные формы. Денормализация.

Ответы

1. Операторы DDL, DML, TCL, DCL.


Какие существуют операторы SQL?
операторы определения данных (Data Definition Language, DDL):
 CREATE создает объект БД (базу, таблицу, представление, пользователя и т. д.),
 ALTER изменяет объект,
 DROP удаляет объект;
операторы манипуляции данными (Data Manipulation Language, DML):
 SELECT выбирает данные, удовлетворяющие заданным условиям,
 INSERT добавляет новые данные,
 UPDATE изменяет существующие данные,
 DELETE удаляет данные;
операторы определения доступа к данным (Data Control Language, DCL):
 GRANT предоставляет пользователю (группе) разрешения на определенные операции
с объектом,
 REVOKE отзывает ранее выданные разрешения,
 DENY задает запрет, имеющий приоритет над разрешением;
операторы управления транзакциями (Transaction Control Language, TCL):
 COMMIT применяет транзакцию,
 ROLLBACK откатывает все изменения, сделанные в контексте текущей транзакции,
 SAVEPOINT разбивает транзакцию на более мелкие.
w2. null в SQL.
Что означает NULL в SQL?
NULL - специальное значение (псевдозначение), которое может быть записано в поле
таблицы базы данных. NULL соответствует понятию «пустое поле», то есть «поле, не
содержащее никакого значения».
NULL означает отсутствие, неизвестность информации. Значение NULL не является
значением в полном смысле слова: по определению оно означает отсутствие значения и не
принадлежит ни одному типу данных. Поэтому NULL не равно ни логическому значению
FALSE, ни пустой строке, ни 0. При сравнении NULL с любым значением будет получен
результат NULL, а не FALSE и не 0. Более того, NULL не равно NULL!

команды: IS NULL, IS NOT NULL

3. Временная таблица (temporary table) vs. представления (view).


иерархические запросы и рекурсивные запросы
ste, оператор with
Что такое «временная таблица»? Для чего она используется?
Временная таблица - это объект базы данных, который хранится и управляется системой
базы данных на временной основе. Они могут быть локальными или глобальными.
Используется для сохранения результатов вызова хранимой процедуры, уменьшение числа
строк при соединениях, агрегирование данных из различных источников или как замена
курсоров и параметризованных представлений.

Что такое «представление» (view) и для чего оно применяется?


Представление, View - виртуальная таблица, представляющая данные одной или более
таблиц альтернативным образом.
В действительности представление – всего лишь результат выполнения оператора SELECT,
который хранится в структуре памяти, напоминающей SQL таблицу. Они работают в запросах
и операторах DML точно также как и основные таблицы, но не содержат никаких собственных
данных. Представления значительно расширяют возможности управления данными. Это
способ дать публичный доступ к некоторой (но не всей) информации в таблице.

4. SELECT.
Каков общий синтаксис оператора SELECT?
SELECT - оператор DML SQL, возвращающий набор данных (выборку) из базы данных,
удовлетворяющих заданному условию. Имеет следующую структуру:
SELECT
[DISTINCT | DISTINCTROW | ALL]
select_expression,...
FROM table_references
[WHERE where_definition]
[GROUP BY {unsigned_integer | column | formula}]
[HAVING where_definition]
[ORDER BY {unsigned_integer | column | formula} [ASC | DESC], ...]

5. Виды join'ов. JOIN vs. подзапросы (sub-query).


Что такое JOIN?
JOIN - оператор языка SQL, который является реализацией операции соединения
реляционной алгебры. Предназначен для обеспечения выборки данных из двух таблиц и
включения этих данных в один результирующий набор.
Особенностями операции соединения являются следующее:
в схему таблицы-результата входят столбцы обеих исходных таблиц (таблиц-операндов), то
есть схема результата является «сцеплением» схем операндов;
каждая строка таблицы-результата является «сцеплением» строки из одной таблицы-
операнда со строкой второй таблицы-операнда;
при необходимости соединения не двух, а нескольких таблиц, операция соединения
применяется несколько раз (последовательно).
SELECT
field_name [,... n]
FROM
Table1
{INNER | {LEFT | RIGHT | FULL} OUTER | CROSS } JOIN
Table2
{ON <condition> | USING (field_name [,... n])}

Какие существуют типы JOIN?


 (INNER) JOIN Результатом объединения таблиц являются записи, общие для левой и
правой таблиц. Порядок таблиц для оператора не важен, поскольку оператор является
симметричным.
 LEFT (OUTER) JOIN Производит выбор всех записей первой таблицы и
соответствующих им записей второй таблицы. Если записи во второй таблице не
найдены, то вместо них подставляется пустой результат (NULL). Порядок таблиц для
оператора важен, поскольку оператор не является симметричным.
 RIGHT (OUTER) JOIN LEFT JOIN с операндами, расставленными в обратном порядке.
Порядок таблиц для оператора важен, поскольку оператор не является
симметричным.
 FULL (OUTER) JOIN Результатом объединения таблиц являются все записи, которые
присутствуют в таблицах. Порядок таблиц для оператора не важен, поскольку
оператор является симметричным.
 CROSS JOIN (декартово произведение) При выборе каждая строка одной таблицы
объединяется с каждой строкой второй таблицы, давая тем самым все возможные
сочетания строк двух таблиц. Порядок таблиц для оператора не важен, поскольку
оператор является симметричным.

аЧто лучше использовать JOIN или подзапросы?


Обычно лучше использовать JOIN, поскольку в большинстве случаев он более понятен и
лучше оптимизируется§ СУБД (но 100% этого гарантировать нельзя). Так же JOIN имеет
заметное преимущество над подзапросами в случае, когда список выбора SELECT содержит
столбцы более чем из одной таблицы.
Подзапросы лучше использовать в случаях, когда нужно вычислять агрегатные значения и
использовать их для сравнений во внешних запросах.

6. HAVING vs. WHERE.


Для чего используется оператор HAVING?
HAVING используется для фильтрации результата GROUP BY по заданным логическим
условиям.
HAVING — необязательный элемент запроса, который отвечает за фильтрацию
на уровне сгруппированных данных (по сути, WHERE, но только на уровень
выше).
Пример:
Фильтрация агрегированной таблицы с количеством клиентов по городам, в
данном случае оставляем в выгрузке только те города, в которых не менее 5
клиентов:
select City, count(CustomerID) from Customers
group by City
HAVING count(CustomerID) >= 5
В чем различие между операторами HAVING и WHERE?
HAVING используется как WHERE, но в другой части SQL-выражения и, соответственно, на
другой стадии формирования ответа.

WHERE :
 нельзя использовать с агрегатными функциями,
 стоит после GROUP BY,

7. ORDER BY.
Для чего используется оператор ORDER BY?
ORDER BY упорядочивает вывод запроса согласно значениям в том или ином количестве
выбранных столбцов. Многочисленные столбцы упорядочиваются один внутри другого.
Возможно определять возрастание ASC или убывание DESC для каждого столбца. По
умолчанию установлено - возрастание.
ORDER BY — необязательный элемент запроса, который отвечает за сортировку
таблицы.
Простой пример сортировки по одному столбцу. В данном запросе
осуществляется сортировка по городу, который указал клиент:
select * from Customers
ORDER BY City
По умолчанию сортировка происходит по возрастанию для чисел и в алфавитном
порядке для текстовых значений. Если нужна обратная сортировка, то в
конструкции ORDER BY после названия столбца надо добавить DESC:
select * from Customers
order by CustomerID DESC

8. GROUP BY (NULL).
Для чего используется оператор GROUP BY?
GROUP BY используется для агрегации записей результата по заданным признакам-
атрибутам.
GROUP BY — необязательный элемент запроса, с помощью которого можно
задать агрегацию по нужному столбцу (например, если нужно узнать какое
количество клиентов живет в каждом из городов).
При использовании GROUP BY обязательно:
1. перечень столбцов, по которым делается разрез, был одинаковым
внутри SELECT и внутри GROUP BY,
2. агрегатные функции (SUM, AVG, COUNT, MAX, MIN) должны быть
также указаны внутри SELECT с указанием столбца, к которому такая
функция применяется.
Группировка количества клиентов по городу:
select City, count(CustomerID) from Customers
GROUP BY City
Группировка количества клиентов по стране и городу:
select Country, City, count(CustomerID) from Customers
GROUP BY Country, City

Как GROUP BY обрабатывает значение NULL?


При использовании GROUP BY все значения NULL считаются равными.

9. GROUP BY vs. DISTINCT.


В чем разница между операторами GROUP BY и DISTINCT?
DISTINCT указывает, что для вычислений используются только уникальные значения столбца.
NULL считается как отдельное значение. GROUP BY создает отдельную группу для всех
возможных значений (включая значение NULL).
Если нужно удалить только дубликаты лучше использовать DISTINCT, GROUP BY лучше
использовать для определения групп записей, к которым могут применяться агрегатные
функции.

10. Агрегатные функции. COUNT(*) vs. COUNT({column}).


Перечислите основные агрегатные функции.
Агрегатных функции - функции, которые берут группы значений и сводят их к одиночному
значению.
SQL предоставляет несколько агрегатных функций:
COUNT - производит подсчет записей, удовлетворяющих условию запроса; SUM - вычисляет
арифметическую сумму всех значений колонки; AVG - вычисляет среднее арифметическое
всех значений; MAX - определяет наибольшее из всех выбранных значений; MIN - определяет
наименьшее из всех выбранных значений.

В чем разница между COUNT(*) и COUNT({column})?


COUNT (*) подсчитывает количество записей в таблице, не игнорируя значение NULL,
поскольку эта функция оперирует записями, а не столбцами.
COUNT ({column}) подсчитывает количество значений в {column}. При подсчете количества
значений столбца эта форма функции COUNT не принимает во внимание значение NULL.

11. EXISTS.
Что делает оператор EXISTS?
EXISTS берет подзапрос, как аргумент, и оценивает его как TRUE, если подзапрос
возвращает какие-либо записи и FALSE, если нет.

12. IN, BETWEEN, LIKE.


Для чего используются операторы IN, BETWEEN, LIKE?
 IN - определяет набор значений.
SELECT * FROM Persons WHERE name IN ('Ivan','Petr','Pavel');
 BETWEEN определяет диапазон значений. В отличие от IN, BETWEEN чувствителен к
порядку, и первое значение в предложении должно быть первым по алфавитному или
числовому порядку.
SELECT * FROM Persons WHERE age BETWEEN 20 AND 25;
 LIKE применим только к полям типа CHAR или VARCHAR, с которыми он используется
чтобы находить подстроки. В качестве условия используются символы шаблонизации
(wildkards) - специальные символы, которые могут соответствовать чему-нибудь:
_ замещает любой одиночный символ. Например, 'b_t' будет соответствовать словам 'bat' или
'bit', но не будет соответствовать 'brat'.
% замещает последовательность любого числа символов. Например '%p%t' будет
соответствовать словам 'put', 'posit', или 'opt', но не 'spite'.
SELECT * FROM UNIVERSITY WHERE NAME LIKE '%o';

13. UNION vs. UNIONALL (ограничение на UNION).


Для чего применяется ключевое слово UNION?
В языке SQL ключевое слово UNION применяется для объединения результатов двух SQL-
запросов в единую таблицу, состоящую из схожих записей. Обddddddddddddа запроса
должны возвращать одинаковое число столбцов и совместимые типы данных в
соответствующих столбцах. Необходимо отметить, что UNION сам по себе не гарантирует
порядок записей. Записи из второго запроса могут оказаться в начале, в конце или вообще
перемешаться с записями из первого запроса. В случаях, когда требуется определенный
порядок, необходимо использовать ORDER BY.
С удалением дублей:
SELECT * FROM имя_таблицы1 WHERE условие
UNION SELECT * FROM имя_таблицы2 WHERE условие
Без удаления дублей:
SELECT * FROM имя_таблицы1 WHERE условие
UNION ALL SELECT * FROM имя_таблицы2 WHERE условие
Можно объединять не две таблицы, а три или более:
SELECT * FROM имя_таблицы1 WHERE условие
UNION SELECT * FROM имя_таблицы2 WHERE условие
UNION SELECT * FROM имя_таблицы3 WHERE условие
UNION SELECT * FROM имя_таблицы4 WHERE услови

14. Ограничения (constraints: primary key, foreign key, check, notnull,


unique).
Какие ограничения на целостность данных существуют в SQL?
 PRIMARY KEY - набор полей (1 или более), значения которых образуют уникальную
комбинацию и используются для однозначной идентификации записи в таблице. Для
таблицы может быть создано только одно такое ограничение. Данное ограничение
используется для обеспечения целостности сущности, которая описана таблицей.
 CHECK используется для ограничения множества значений, которые могут быть
помещены в данный столбец. Это ограничение используется для обеспечения
целостности предметной области, которую описывают таблицы в базе.
 UNIQUE обеспечивает отсутствие дубликатов в столбце или наборе столбцов.
 FOREIGN KEY защищает от действий, которые могут нарушить связи между
таблицами. FOREIGN KEY в одной таблице указывает на PRIMARY KEY в другой.
Поэтому данное ограничение нацелено на то, чтобы не было записей FOREIGN KEY,
которым не отвечают записи PRIMARY KEY.

Какие отличия между ограничениями PRIMARY и UNIQUE?


По умолчанию ограничение PRIMARY создает кластерный индекс на столбце, а UNIQUE -
некластерный. Другим отличием является то, что PRIMARY не разрешает NULL записей, в то
время как UNIQUE разрешает одну (а в некоторых СУБД несколько) NULL запись.

Может ли значение в столбце, на который наложено ограничение FOREIGN KEY,


равняться NULL?
Может, если на данный столбец не наложено ограничение NOT NULL.

15. Суррогатные ключи.


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

Дайте определение терминам «простой», «составной» (composite), «потенциальный»


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

Что такое «первичный ключ» (primary key)? Каковы критерии его выбора?
Первичный ключ (primary key) в реляционной модели данных один из потенциальных ключей
отношения, выбранный в качестве основного ключа (ключа по умолчанию).
Если в отношении имеется единственный потенциальный ключ, он является и первичным
ключом. Если потенциальных ключей несколько, один из них выбирается в качестве
первичного, а другие называют «альтернативными».
В качестве первичного обычно выбирается тот из потенциальных ключей, который наиболее
удобен. Поэтому в качестве первичного ключа, как правило, выбирают тот, который имеет
наименьший размер (физического хранения) и/или включает наименьшее количество
атрибутов. Другой критерий выбора первичного ключа — сохранение его уникальности со
временем. Поэтому в качестве первичного ключа стараются выбирать такой потенциальный
ключ, который с наибольшей вероятностью никогда не утратит уникальность.

Что такое «внешний ключ» (foreign key)?


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

16. MERGмE.
Что делает оператор MERGE?
MERGE позволяет осуществить слияние данных одной таблицы с данными другой таблицы.
При слиянии таблиц проверяется условие, и если оно истинно, то выполняется UPDATE, а
если нет - INSERT. При этом изменять поля таблицы в секции UPDATE, по которым идет
связывание двух таблиц, нельзя.

17. DELETE vs. TRUNCATE.


В чем отличие между операторами DELETE и TRUNCATE?
DELETE - оператор DML, удаляет записи из таблицы, которые удовлетворяют критерию
WHERE при этом задействуются триггеры, ограничения и т.д.
TRUNCATE - DDL оператор (удаляет таблицу и создает ее заново. Причем если на эту
таблицу есть ссылки FOREIGN KEY или таблица используется в репликации, то пересоздать
такую таблицу не получится).

18. Индексы.
Что такое «индексы»? Для чего их используют? В чём заключаются их преимущества и
недостатки?
Индекс (index) — объект базы данных, создаваемый с целью повышения производительности
выборки данных.
Наборы данных могут иметь большое количество записей, которые хранятся в произвольном
порядке, и их поиск по заданному критерию путем последовательного просмотра набора
данных запись за записью может занимать много времени. Индекс формируется из значений
одного или нескольких полей и указателей на соответствующие записи набора данных, -
таким образом, достигается значительный прирост скорости выборки из этих данных.
Преимущества
 ускорение поиска и сортировки по определенному полю или набору полей.
 обеспечение уникальности данных.
Недостатки
 требование дополнительного места на диске и в оперативной памяти и чем
больше/длиннее ключ, тем больше размер индекса.
 замедление операций вставки, обновления и удаления записей, поскольку при этом
приходится обновлять сами индексы.
Индексы предпочтительней для:
 Поля-счетчика, чтобы в том числе избежать и повторения значений в этом поле;
 Поля, по которому проводится сортировка данных;
 Полей, по которым часто проводится соединение наборов данных. Поскольку в этом
случае данные располагаются в порядке возрастания индекса и соединение
происходит значительно быстрее;
 Поля, которое объявлено первичным ключом (primary key);
 Поля, в котором данные выбираются из некоторого диапазона. В этом случае как
только будет найдена первая запись с нужным значением, все последующие значения
будут расположены рядом.
Использование индексов нецелесообразно для:
 Пол ей, которые редко используются в запросах;
 Полей, которые содержат всего два или три значения, например: мужской, женский
пол или значения «да», «нет».

Какие типы индексов существуют?


По порядку сортировки
 упорядоченные — индексы, в которых элементы упорядочены;
 возрастающие;
 убывающие;
 неупорядоченные — индексы, в которых элементы неупорядочены.
По источнику данных
 индексы по представлению (view);
 индексы по выражениям.
По воздействию на источник данных
 кластерный индекс - при определении в наборе данных физическое расположение
данных перестраивается в соответствии со структурой индекса. Логическая структура
набора данных в этом случае представляет собой скорее словарь, чем индекс. Данные
в словаре физически упорядочены, например по алфавиту. Кластерные индексы могут
дать существенное увеличение производительности поиска данных даже по
сравнению с обычными индексами. Увеличение производительности особенно
заметно при работе с последовательными данными.
 некластерный индекс — наиболее типичные представители семейства индексов. В
отличие от кластерных, они не перестраивают физическую структуру набора данных, а
лишь организуют ссылки на соответствующие записи. Для идентификации нужной
записи в наборе данных некластерный индекс организует специальные указатели,
включающие в себя: информацию об идентификационном номере файла, в котором
хранится запись; идентификационный номер страницы соответствующих данных;
номер искомой записи на соответствующей странице; содержимое столбца.
По структуре
 B*-деревья;
 B+-деревья;
 B-деревья;
 Хэши.
По количественному составу
 простой индекс (индекс с одним ключом) — строится по одному полю;
 составной (многоключевой, композитный) индекс — строится по нескольким полям при
этом важен порядок их следования;
 индекс с включенными столбцами — некластеризованный индекс, дополнительно
содержащий кроме ключевых столбцов еще и неключевые;
 главный индекс (индекс по первичному ключу) — это тот индексный ключ, под
управлением которого в данный момент находится набор данных. Набор данных не
может быть отсортирован по нескольким индексным ключам одновременно. Хотя, если
один и тот же набор данных открыт одновременно в нескольких рабочих областях, то у
каждой копии набора данных может быть назначен свой главный индекс.
По характеристике содержимого
 уникальный индекс состоит из множества уникальных значений поля;
 плотный индекс (NoSQL) — индекс, при котором, каждом документе в индексируемой
коллекции соответствует запись в индексе, даже если в документе нет индексируемого
поля.
 разреженный индекс (NoSQL) — тот, в котором представлены только те документы,
для которых индексируемый ключ имеет какое-то определённое значение
(существует).
 пространственный индекс — оптимизирован для описания географического
местоположения. Представляет из себя многоключевой индекс состоящий из широты и
долготы.
 составной пространственный индекс — индекс, включающий в себя кроме широты и
долготы ещё какие-либо мета-данные (например теги). Но географические координаты
должны стоять на первом месте.
 полнотекстовый (инвертированный) индекс — словарь, в котором перечислены все
слова и указано, в каких местах они встречаются. При наличии такого индекса
достаточно осуществить поиск нужных слов в нём и тогда сразу же будет получен
список документов, в которых они встречаются.
 хэш-индекс предполагает хранение не самих значений, а их хэшей, благодаря чему
уменьшается размер (а, соответственно, и увеличивается скорость их обработки)
индексов из больших полей. Таким образом, при запросах с использованием хэш-
индексов, сравниваться будут не искомое со значения поля, а хэш от искомого
значения с хэшами полей. Из-за нелинейнойсти хэш-функций данный индекс нельзя
сортировать по значению, что приводит к невозможности использования в сравнениях
больше/меньше и «is null». Кроме того, так как хэши не уникальны, то для
совпадающих хэшей применяются методы разрешения коллизий.
 битовый индекс (bitmap index) — метод битовых индексов заключается в создании
отдельных битовых карт (последовательностей 0 и 1) для каждого возможного
значения столбца, где каждому биту соответствует запись с индексируемым
значением, а его значение равное 1 означает, что запись, соответствующая позиции
бита содержит индексируемое значение для данного столбца или свойства.
 обратный индекс (reverse index) — B-tree индекс, но с реверсированным ключом,
используемый в основном для монотонно возрастающих значений (например,
автоинкрементный идентификатор) в OLTP системах с целью снятия конкуренции за
последний листовой блок индекса, т.к. благодаря переворачиванию значения две
соседние записи индекса попадают в разные блоки индекса. Он не может
использоваться для диапазонного поиска.
 функциональный индекс, индекс по вычисляемому полю (function-based index) —
индекс, ключи которого хранят результат пользовательских функций. Функциональные
индексы часто строятся для полей, значения которых проходят предварительную
обработку перед сравнением в команде SQL. Например, при сравнении строковых
данных без учета регистра символов часто используется функция UPPER. Кроме того,
функциональный индекс может помочь реализовать любой другой отсутствующий тип
индексов данной СУБД.
 первичный индекс — уникальный индекс по полю первичного ключа.
 вторичный индекс — индекс по другим полям (кроме поля первичного ключа).
 XML-индекс — вырезанное материализованное представление больших двоичных
XML-объектов (BLOB) в столбце с типом данных xml.
По механизму обновления
 полностью перестраиваемый — при добавлении элемента заново перестраивается
весь индекс.
 пополняемый (балансируемый) — при добавлении элементов индекс перестраивается
частично (например одна из ветви) и периодически балансируется.
По покрытию индексируемого содержимого
 полностью покрывающий (полный) индекс — покрывает всё содержимое
индексируемого объекта.
 частичный индекс (partial index) — это индекс, построенный на части набора данных,
удовлетворяющей определенному условию самого индекса. Данный индекс создан
для уменьшения размера индекса.
 инкрементный (delta) индекс — индексируется малая часть данных(дельта), как
правило, по истечении определенного времени. Используется при интенсивной
записи. Например, полный индекс перестраивается раз в сутки, а дельта-индекс
строится каждый час. По сути это частичный индекс по временной метке.
 индекс реального времени (real-time index) — особый вид инкрементного индекса,
характеризующийся высокой скоростью построения. Предназначен для часто
меняющихся данных.
Индексы в кластерных системах
 глобальный индекс — индекс по всему содержимому всех сегментов БД (shard).
 сегментный индекс — глобальный индекс по полю-сегментируемому ключу (shard key).
Используется для быстрого определения сегмента, на котором хранятся данные в
процессе маршрутизации запроса в кластере БД.
 локальный индекс — индекс по содержимому только одного сегмента БД.

В чем отличие между кластерными и некластерными индексами?


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

Имеет ли смысл индексировать данные, имеющие небольшое количество возможных


значений?
Примерное правило, которым можно руководствоваться при создании индекса - если объем
информации (в байтах) НЕ удовлетворяющей условию выборки меньше, чем размер индекса
(в байтах) по данному условию выборки, то в общем случае оптимизация приведет к
замедлению выборки.

Когда полное сканирование набора данных выгоднее доступа по индексу?


Полное сканирование производится многоблочным чтением. Сканирование по индексу -
одноблочным. Также, при доступе по индексу сначала идет сканирование самого индекса, а
затем чтение блоков из набора данных. Число блоков, которые надо при этом прочитать из
набора зависит от фактора кластеризации. Если суммарная стоимость всех необходимых
одноблочных чтений больше стоимости полного сканирования многоблочным чтением, то
полное сканирование выгоднее и оно выбирается оптимизатором.
Таким образом, полное сканирование выбирается при слабой селективности предикатов
зароса и/или слабой кластеризации данных, либо в случае очень маленьких наборов данных.

Как создать индекс?


Индекс можно создать либо с помощью выражения CREATE INDEX:
CREATE INDEX index_name ON table_name (column_name)
либо указав ограничение целостности в виде уникального UNIQUE или первичного PRIMARY
ключа в операторе создания таблицы CREATE TABLE.

19. Хранимая процедура.


Что такое «хранимая процедура»?
Хранимая процедура — объект базы данных, представляющий собой набор SQL-инструкций,
который хранится на сервере. Хранимые процедуры очень похожи на обыкновенные
процедуры языков высокого уровня, у них могут быть входные и выходные параметры и
локальные переменные, в них могут производиться числовые вычисления и операции над
символьными данными, результаты которых могут присваиваться переменным и параметрам.
В хранимых процедурах могут выполняться стандартные операции с базами данных (как DDL,
так и DML). Кроме того, в хранимых процедурах возможны циклы и ветвления, то есть в них
могут использоваться инструкции управления процессом исполнения.
Хранимые процедуры позволяют повысить производительность, расширяют возможности
программирования и поддерживают функции безопасности данных. В большинстве СУБД при
первом запуске хранимой процедуры она компилируется (выполняется синтаксический анализ
и генерируется план доступа к данным) и в дальнейшем её обработка осуществляется
быстрее.
Хранимая процедура — объект базы данных, представляющий собой набор SQL-инструкций,
который хранится на сервере. Хранимые процедуры очень похожи на обыкновенные
процедуры языков высокого уровня, у них могут быть входные и выходные параметры и
локальные переменные, в них могут производиться числовые вычисления и операции над
символьными данными, результаты которых могут присваиваться переменным и параметрам.
В хранимых процедурах могут выполняться стандартные операции с базами данных (как DDL,
так и DML). Кроме того, в хранимых процедурах возможны циклы и ветвления, то есть в них
могут использоваться инструкции управления процессом исполнения.
Хранимые процедуры позволяют повысить производительность, расширяют возможности
программирования и поддерживают функции безопасности данных. В большинстве СУБД при
первом запуске хранимой процедуры она компилируется (выполняется синтаксический анализ
и генерируется план доступа к данным) и в дальнейшем её обработка осуществляется
быстрее.

20. Триггер.
Что такое «триггер»?
Триггер (trigger) — это хранимая процедура особого типа, которую пользователь не вызывает
непосредственно, а исполнение которой обусловлено действием по модификации данных:
добавлением, удалением или изменением данных в заданной таблице реляционной базы
данных. Триггеры применяются для обеспечения целостности данных и реализации сложной
бизнес-логики. Триггер запускается сервером автоматически и все производимые им
модификации данных рассматриваются как выполняемые в транзакции, в которой выполнено
действие, вызвавшее срабатывание триггера. Соответственно, в случае обнаружения ошибки
или нарушения целостности данных может произойти откат этой транзакции.
Момент запуска триггера определяется с помощью ключевых слов BEFORE (триггер
запускается до выполнения связанного с ним события) или AFTER (после события). В случае,
если триггер вызывается до события, он может внести изменения в модифицируемую
событием запись. Кроме того, триггеры могут быть привязаны не к таблице, а к
представлению (VIEW). В этом случае с их помощью реализуется механизм «обновляемого
представления». В этом случае ключевые слова BEFORE и AFTER влияют лишь на
последовательность вызова триггеров, так как собственно событие (удаление, вставка или
обновление) не происходит.

21. Курсор.
Что такое «курсор»?
Курсор — это объект базы данных, который позволяет приложениям работать с записями «по-
одной», а не сразу с множеством, как это делается в обычных SQL командах.
Порядок работы с курсором такой:
1. Определить курсор (DECLARE)
2. Открыть курсор (OPEN)
3. Получить запись из курсора (FETCH)
4. Обработать запись...
5. Закрыть курсор (CLOSE)
6. Удалить ссылку курсора (DEALLOCATE). Когда удаляется последняя ссылка курсора,
SQL освобождает структуры данных, составляющие курсор.

22. DATETIME vs. TIMESTAMP.


Опишите разницу типов данных DATETIME и TIMESTAMP.
DATETIME предназначен для хранения целого числа: YYYYMMDDHHMMSS. И это время не
зависит от временной зоны настроенной на сервере. Размер: 8 байт
TIMESTAMP хранит значение равное количеству секунд, прошедших с полуночи 1 января
1970 года по усреднённому времени Гринвича. При получении из базы отображается с учётом
часового пояса. Размер: 4 байта
23. Транзакции. Принципы ACID.
Что такое «транзакция»?
Транзакция - это воздействие на базу данных, переводящее её из одного целостного
состояния в другое и выражаемое в изменении данных, хранящихся в базе данных.

Назовите основные свойства транзакции.


1. Атомарность (atomicity) гарантирует, что никакая транзакция не будет зафиксирована в
системе частично. Будут либо выполнены все её подоперации, либо не выполнено ни
одной.
2. Согласованность (consistency). Транзакция, достигающая своего нормального
завершения и, тем самым, фиксирующая свои результаты, сохраняет согласованность
базы данных.
3. Изолированность (isolation). Во время выполнения транзакции параллельные
транзакции не должны оказывать влияние на ее результат.
4. Долговечность (durability). Независимо от проблем на нижних уровнях (к примеру,
обесточивание системы или сбои в оборудовании) изменения, сделанные успешно
завершённой транзакцией, должны остаться сохраненными после возвращения
системы в работу.

24. Уровни изолированности транзакций.


Какие существуют уровни изолированности транзакций?
В порядке увеличения изолированности транзакций и, соответственно, надежности работы с
данными:
 Чтение неподтверждённых данных (грязное чтение) (read uncommitted, dirty read) —
чтение незафиксированных изменений как своей транзакции, так и параллельных
транзакций. Нет гарантии, что данные, измененные другими транзакциями, не будут в
любой момент изменены в результате их отката, поэтому такое чтение является
потенциальным источником ошибок. Невозможны потерянные изменения, возможны
неповторяемое чтение и фантомы.
 Чтение подтвержденных данных (read committed) — чтение всех изменений своей
транзакции и зафиксированных изменений параллельных транзакций. Потерянные
изменения и грязное чтение не допускается, возможны неповторяемое чтение и
фантомы.
 Повторяемость чтения (repeatable read, snapshot) — чтение всех изменений своей
транзакции, любые изменения, внесенные параллельными транзакциями после
начала своей, недоступны. Потерянные изменения, грязное и неповторяемое чтение
невозможны, возможны фантомы.
 Упорядочиваемость (serializable) — результат параллельного выполнения
сериализуемой транзакции с другими транзакциями должен быть логически
эквивалентен результату их какого-либо последовательного выполнения. Проблемы
синхронизации не возникают.

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


транзакций?
При параллельном выполнении транзакций возможны следующие проблемы:
 Потерянное обновление (lost update) — при одновременном изменении одного блока
данных разными транзакциями одно из изменений теряется;
 «Грязное» чтение (dirty read) — чтение данных, добавленных или измененных
транзакцией, которая впоследствии не подтвердится (откатится);
 Неповторяющееся чтение (non-repeatable read) — при повторном чтении в рамках
одной транзакции ранее прочитанные данные оказываются измененными;
 Фантомное чтение (phantom reads) — одна транзакция в ходе своего выполнения
несколько раз выбирает множество записей по одним и тем же критериям. Другая
транзакция в интервалах между этими выборками добавляет или удаляет записи или
изменяет столбцы некоторых записей, используемых в критериях выборки первой
транзакции, и успешно заканчивается. В результате получится, что одни и те же
выборки в первой транзакции дают разные множества записей.

25. Нормализация. Нормальные формы. Денормализация.


Что такое «нормализация»?
Нормализация - это процесс преобразования отношений базы данных к виду, отвечающему
нормальным формам (пошаговый, обратимый процесс замены исходной схемы другой
схемой, в которой наборы данных имеют более простую и логичную структуру).
Нормализация предназначена для приведения структуры базы данных к виду,
обеспечивающему минимальную логическую избыточность, и не имеет целью уменьшение
или увеличение производительности работы или же уменьшение или увеличение
физического объема базы данных. Конечной целью нормализации является уменьшение
потенциальной противоречивости хранимой в базе данных информации.

Какие существуют нормальные формы?


 Первая нормальная форма (1NF) - Отношение находится в 1NF, если значения всех
его атрибутов атомарны (неделимы).
Более кратко и понятнее:
 В каждой ячейке таблицы должно находиться только 1 значение.
 Строки не должны повторяться.
 Вторая нормальная форма (2NF) - Отношение находится в 2NF, если оно находится в
1NF, и при этом все неключевые атрибуты зависят только от ключа целиком, а не от
какой-то его части.
Более кратко и понятно:
 Таблица в 1NF.
 Все атрибуты зависят целиком от primary key,а не от его части.
Чтобы привести таблицу в 2NF необходимо ее декомпозировать на несколько
зависящих друг от друга.
 Третья нормальная форма (3NF) - Отношение находится в 3NF, если оно находится в
2NF и все неключевые атрибуты не зависят друг от друга.
Более кратко и понятно:
 Таблица в 2NF.
 Все атрибуты зависят только от primary key, но не от других атрибутов.
 Четвёртая нормальная форма (4NF) - Отношение находится в 4NF , если оно
находится в 3NF и если в нем не содержатся независимые группы атрибутов, между
которыми существует отношение «многие-ко-многим».
Более кратко и понятно:
 Таблица в 3NF.
 Ключевые атрибуты не должны зависеть от неключевых.
 Пятая нормальная форма (5NF) - Отношение находится в 5NF, когда каждая
нетривиальная зависимость соединения в ней определяется потенциальным ключом
(ключами) этого отношения.
 Шестая нормальная форма (6NF) - Отношение находится в 6NF, когда она
удовлетворяет всем нетривиальным зависимостям соединения, т.е. когда она
неприводима, то есть не может быть подвергнута дальнейшей декомпозиции без
потерь. Каждая переменная отношения, которая находится в 6NF, также находится и в
5NF. Введена как обобщение пятой нормальной формы для хронологической базы
данных.
 Нормальная форма Бойса-Кодда, усиленная 3 нормальная форма (BCNF) -
Отношение находится в BCNF, когда каждая её нетривиальная и неприводимая слева
функциональная зависимость имеет в качестве своего детерминанта некоторый
потенциальный ключ.
 Доменно-ключевая нормальная форма (DKNF) - Отношение находится в DKNF, когда
каждое наложенное на нее ограничение является логическим следствием ограничений
доменов и ограничений ключей, наложенных на данное отношение.

Что такое «денормализация»? Для чего она применяется?


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

hibernate

Hiberna1ate
вопросы-ответы Хилькевич Игорь 22.03.2020
Вопросы из “all question”
1. ORM. JPA. Hibernate.2
2. JDBC vs. Hibernat Что такое ORM? Что такое JPA? Что такое Hibernate?e.
3. Важные интерфейсы Hibernate (SessionFactory, Session, Transaction).
4. Требования к Entity в JPA.
5. EntityManager.
6. Виды связи (OneToOne, OneToMany, ManyToOne, ManyToMany). Unidirectional, bidirectional.
7. Владелец связи (mappedBy).
8. Каскадирование.
9. FetchType.
10. orphanRemoval
11. @Basic vs. @Column.
12. Маппинг энамов и дат.
13. Жизненный цикл сущности (new, managed, detached, removed) и операции (persist, merge,
detach, remove, refresh) над сущностью в разных состояниях жизненного цикла.
14. Inheritance (3 стратегии построения иерархии) vs. @MappedSuperClass.
15. n + 1 select (описание и решения).
16. Entity Grpah.
17. Блокировки (оптимистические и пессимистические).
18. Кеширование (уровни кеширование, @Cacheable, @Cache, ehcache).
19. HQL, JPQL.
20. Criteria API.
21. Hibernate proxy (lazy load).
22. Транзакции в Hibernate.
Дополнительно
23. Servlet.
24. JSP.
Доп.вопросы
1. Основные различия между JPA и JDBC (уровень абстракции)
2. Что такое хибер
3. Будет ли JPQL запрос считаться корректным HQL запросом?
4. Когда вызываешь метод createQuery() какой интерфейс получаешь
5. Основные интерфейсы JPA (ОСНОВНЫЕ 2)
6. Чем EntityManager отличается от Session
7. Разница между EntityManagerFactory и SessionFactory
8. В каком случае я могу восстановить удаленную сущность
9. Что такое PersistenceContext (это и есть кэш 1-го уровня у EntityManager)
10. Как отсоединить сущность от контекста, какими методами
11. Как можно конфигурировать хибернейт - какими способами (SessionFactory &
EntityManagerFactory)
12. Основные аннотации хибера
13. @Id
14. Стратегии генерации @Id - знать все!!!!
15. Что означает стратегия @[Link]
16. На какой стороне инкрементится id- на стороне базы или хибернейта
17. @Column
18. @Acess
19. Что означает двусторонняя связь. Как это отобразится в коде
20. Что произойдет если не поставить mappedBy на @OneToOne @OneToMany - почему
выгодно ставить mapped by на этих типах связей
21. @JoinColumn
22. @JoinTable
23. Университеты и Студенты - @MаnyToMany - можно ли обойтись просто этой аннотацией
без @JoinTable и @JoinColumn
24. При аннотации @ManyToMany - ОБА ВЛАДЕЛЬЦЫ СВЯЗИ
25. Что ты знаешь про стратегии загрузки
26. Стратегии загрузки по умолчанию для всех видов связей, а также для аннотации @Basic и
@Collection
27. Что такое встраиваемый класс (@Embedded)
28. Как создать составной первичный ключ - где это указывать, как это должно правильно
работать
29. Для чего еще нужен @Embedded - второй случай, если опустить составной ключ
30. Как работает первый уровень кэша - когда он есть - когда нет - к чему он привязан (к
какому объекту)
31. К какому объекту привязан кэш второго уровня (к EntityManagerFactory)
32. Как настроить кэш второго уровня.
33. Какой кэш еще есть. Кэш запросов - как настроить. Желательно понимать как объекты
хранятся в кэше второго уровня и в кэше запросов.
34 Как контролировать объекты второго уровня кэша - как удалить как посмотреть.
35. @Basic
36. @ElementCollection
37. @OrderBy
38. @OrderColumn - как работает, где ставится
39. Различия между @OrderBy и @OrderColumn - пример с базой данных
40. OrphanRemoval
41. Почему есть Cascade Removal и orphanRemoval - есть еще один случай - когда есть
разница? А при удалении разницы нет - в каком случае есть?
42. Метод unWrap()
43. Каким методом очищается кэш 1-го уровня

Ответы на вопросы “all question”


1. ORM. JPA. Hibernate.
1. ORM: Object Relational Mapping - это концепция/процесс преобразования данных
из объектно-ориентированного языка в реляционные БД и наоборот. Например, в
Java это делается с помощью рефлексии и jdbc.
2. Hibernate: реализация вышеуказанной концепции.
3. JPA: Это на один шаг выше ORM. Его высокоуровневый API и спецификация,
позволяющие реализовать различные инструменты ORM, что дает разработчику
возможность гибко изменять реализацию с одной ORM на другую (например, если
приложение использует API-интерфейс JPA и реализация находится в Hibernateв
спящем режиме. В будущем он может переключиться на IBatis, если требуется.
Но, с другой стороны, если приложение напрямую блокирует реализацию с
помощью Hibernate без платформы JPA, переключение будет непростой задачей)
[Link]

2. JDBC vs. Hibernate.


Hibernate является одним из самых востребованных ORM фреймворков для Java:
1. Hibernate устраняет множество спагетти кода (повторяющегося), который постоянно
преследует разработчика при работе с JDBC. Скрывает от разработчика множество
кода, необходимого для управления ресурсами и позволяет сосредоточиться на
бизнес логике.
2. Hibernate поддерживает XML так же как и JPA аннотации, что позволяет сделать
реализацию кода независимой.
3. Hibernate предоставляет собственный мощный язык запросов (HQL), который похож на
SQL. Стоит отметить, что HQL полностью объектно-ориентирован и понимает такие
принципы, как наследование, полиморфизм и ассоциации (связи).
4. Hibernate — широко распространенный open source проект. Благодаря этому доступны
тысячи открытых статей, примеров, а также документации по использованию
фреймворка.
5. Hibernate легко интегрируется с другими Java EE фреймворками, например, Spring
Framework поддерживает встроенную интеграцию с Hibernate.
6. Hibernate поддерживает ленивую инициализацию используя proxy объекты и
выполняет запросы к базе данных только по необходимости.
7. Hibernate поддерживает разные уровни cache, а следовательно может повысить
производительность.
8. Важно, что Hibernate может использовать чистый SQL, а значит поддерживает
возможность оптимизации запросов и работы с любым сторонним вендором БД и его
фичами.
9. Hibernate неявно использует управление транзакциями. Большинство запросов нельзя
выполнить вне транзакций.
10. Hibernate использует HibernateException (unchecked) значит нет необходимости
проверять их в коде каждый раз.

О proxy:
Hibernate Proxy используется для замены реальной сущности POJO (Plain Old Java Object).
Класс Proxy генерируется во время выполнения и расширяет исходный класс сущности.
Hibernate использует объекты Proxy для объектов, чтобы разрешить отложенную загрузку.
При доступе к основным свойствам прокси просто делегирует вызов исходной сущности.
Каждый List, Set, Map тип в классе сущностей замещен PersistentList, PersistentSet,
PersistentMap. Эти классы отвечают за перехват вызова неинициализированной коллекции.
Прокси не выдает никаких операторов SQL. Он просто запускает InitializeCollectionEvent,
который обрабатывается связанным прослушивателем, который знает, какой запрос
инициализации выпустить (зависит от настроенного плана выборки).
Proxy объект получаем через метод [Link](), если вызываем геттеры и сеттеры -
выполняется select в базу на получение реального объекта.
[Link]
[Link]

3. Важные интерфейсы Hibernate (SessionFactory, Session,


Transaction).
1. Session - обеспечивает физическое соединение между приложением и БД. Основная
функция Session - предлагать операции создания, чтения и удаления для экземпляров
классов сопоставленных сущностей.
2. SessionFactory - это фабрика для объектов Session. Обычно создается во время
запуска приложения и сохраняется для последующего использования. Является
потокобезопасным объектом и используется всеми потоками приложения.
3. Configuration - объект используется для конфигурирования и нача тольной загрузки
Hibernate.
4. Transaction - однопоточный короткоживущий объект, используемый для атомарных
операций. Это абстракция приложения от основных JDBC транзакций. Session может
занимать несколько Transaction в определенных случаях, является необязательным
API.
5. Query - интерфейс позволяет выполнять запросы к БД. Запросы написаны на HQL или
на нативном диалекте SQL. Criteria позволяет создавать и выполнять объектно-
ориентированные критерии запросов.
[Link]
[Link]
[Link]

4. Требования к Entity в JPA.


1) Entity класс должен быть помечен аннотацией Entity или описан в XML файле конфигурации
JPA,
2) Entity класс должен содержать public или protected конструктор без аргументов (он также
может иметь конструкторы с аргументами),
3) Entity класс должен быть классом верхнего уровня (top-level class), только у хибернейта
4) Entity класс не может быть enum или интерфейсом,
5) Entity класс не может быть финальным классом (final class),
6) Entity класс не может содержать финальные поля или методы, если они участвуют в
маппинге (persistent final methods or persistent final instance variables),
7) Если объект Entity класса будет передаваться по значению как отдельный объект (detached
object), например через удаленный интерфейс (through a remote interface), он так же должен
реализовывать Serializable интерфейс
8) Поля Entity класс должны быть напрямую доступны только методам самого Entity класса и
не должны быть напрямую доступны другим классам, использующим этот Entity. Такие классы
должны обращаться только к методам (getter/setter методам или другим методам бизнес-
логики в Entity классе),
9) Entity класс должен содержать первичный ключ, то есть атрибут или группу атрибутов
которые уникально определяют запись этого Entity класса в базе данных
[Link]

Доп. инфа
Entity класс может наследоваться от не Entity классов (non-entity classes)
Entity класс может наследоваться от других Entity классов
не Entity класс может наследоваться от Entity класса
Entity может быть абстрактным классом (при этом он сохраняет все свойства Entity, за
исключением того что его нельзя непосредственно инициализировать)
[Link]

5. EntityManager.
xEntityManager это интерфейс, который описывает API для всех основных операций над Entity,
получение данных и других сущностей JPA. По сути главный API для работы с JPA. Основные
операции:
1) Для операций над Entity: persist (добавление Entity под управление JPA), merge
(обновление), remove (удаления), refresh (обновление данных), detach (удаление из
управление JPA), lock (блокирование Entity от изменений в других thread),
2) Получение данных: find (поиск и получение Entity), createQuery, createNamedQuery,
createNativeQuery, contains, createNamedStoredProcedureQuery, createStoredProcedureQuery
3) Получение других сущностей JPA: getTransaction, getEntityManagerFactory, getCriteriaBuilder,
getMetamodel, getDelegate
4) Работа с EntityGraph: createEntityGraph, getEntityGraph
5) Общие операции над EntityManager или всеми Entities: close, isOpen, getProperties,
setProperty, clear.
[Link]
usp=sharing

6. Виды связи (OneToOne, OneToMany, ManyToOne, ManyToMany).


Unidirectional, bidirectional.
Существуют следующие четыре типа связей
1. OneToOne (связь один к одному, то есть один объект Entity может связан не больше чем с
один объектом другого Entity ),
2. OneToMany (связь один ко многим, один объект Entity может быть связан с целой
коллекцией других Entity),
3. ManyToOne (связь многие к одному, обратная связь для OneToMany),
4. ManyToMany (связь многие ко многим)
Каждую из которых можно разделить еще на два вида:
1. Bidirectional (пер. - Двунаправленный) - две связи
2. Unidirectional (пер. - Однонаправленный ) — ссылка на связь устанавливается у всех Entity,
то есть в случае OneToOne A-B в Entity A есть ссылка на Entity B, в Entity B есть ссылка на
Entity A, Entity A считается владельцем этой связи (это важно для случаев каскадного
удаления данных, тогда при удалении A также будет удалено B, но не наоборот).Unidirectional
- ссылка на связь устанавливается только с одной стороны, то есть в случае OneToOne A-B
только у Entity A будет ссылка на Entity B, у Entity B ссылки на A не будет.
[Link]
usp=sharing

7. Владелец связи (mappedBy).


У mappedBy удалится колонка с внешним ключом, т.к. она будет на другой стороне и так.
Этот атрибут говорит хибернейту что ключ для связи лежит на другой стороне. Это значит, что
несмотря на то, что у нас есть две таблицы - только одна из них содержит ограничение на
внешний ключ. Этот атрибут позволяет по-прежнему ссылаться из таблицы, которая не
содержит ограничения на другую таблицу. Атрибут mappedBy тесно связан с аннотацией
@JoinColumn. Если применить атрибут mappedBy на одной стороне связи - хибернейт не
станет создавать смежную таблицу.
[Link]

8. Каскадирование.
Каскадирование - это когда мы выполняем какое-то действие с целевой сущностью, то же
самое действие будет применено к связанной сущности.
JPA CascadeType:
ALL - распространяет все операции, включая специфичные для Hibernate, от родительского
объекта к дочернему объекту.
PERSIST - делает временный экземпляр постоянным. [Link] передает
операцию persist от родительского объекта к дочернему объекту . Когда мы сохраняем
личность лица, адрес также будет сохранена.
MERGE - операция слияния копирует состояние данного объекта в постоянный объект с тем
же идентификатором. [Link] передает операцию слияния от родителя к
дочерней сущности.
REMOVE - удаляет строку, соответствующую объекту, из базы данных, а также из постоянного
контекста.
DETACH - удаляет объект из постоянного контекста. Когда мы используем
[Link], дочерняя сущность также будет удалена из постоянного контекста.
fHibernate CascadeType:
LOCK - повторно присоединяет сущность и связанную дочернюю сущность с постоянным
контекстом снова.
REFRESH - повторно считывают значение данного экземпляра из базы данных. В некоторых
случаях мы можем изменить экземпляр после сохранения в базе данных, но позже нам нужно
отменить эти изменения.
REPLICATE - используется, когда у нас более одного источника данных, и мы хотим, чтобы
данные были синхронизированы. С [Link] операция синхронизации также
распространяется на дочерние объекты всякий раз, когда выполняется над родительским
объектом.
SAVE_UPDATE - распространяет ту же операцию на связанный дочерний объект. Это
полезно, когда мы используем специфичные для Hibernate операции, такие как save, update и
saveOrUpdate.
[Link]

9. FetchType.
В JPA описаны два типа fetch стратегии:
1) LAZY (пер. - ленивый)— данные поля будут загружены только во время первого доступа к
этому полю,
2) EAGER (произношение - эйга, пер. - жаждущий) — данные поля будут загружены
немедленно.
Используются по умолчанию:
one(many)to many - lazy
one(many)to one - eagerle
[Link]
usp=sharing

10. orphanRemoval.
Директива orphanRemoval объявляет, что связанные экземпляры сущностей должны быть
удалены, когда они отсоединены от родителя, или эквивалентно, когда родитель удален
[Link]
usp=sharing

11. @Basic vs. @Column.


Различия:
Атрибуты @Basic применяются к сущностям JPA, тогда как атрибуты @Column применяются
к столбцам базы данных.
@Basic имеет атрибут optional - может ли поле объекта быть нулевым или нет; с другой
стороны у @Column атрибут nullable позволяет пометить столбец NOT NULL при создании
схемы.
Мы можем использовать @Basic, чтобы указать, что поле должно быть загружено лениво
@Column аннотации позволяют указать имя отображаемого столбца базы данных
[Link]

12. Маппинг энамов и дат.


@Enumerated указывает, следует ли сохранять Enum по имени или по порядковому номеру
(по умолчанию).
[Link]
[Link]

@Enumerated([Link]) - означает, что в базе будет хранятся имена Enam.


@Enumerated([Link]) - означает, что в базе будет хранятся порядковые номера
Enum.
[Link]

Аннотация @Temporal до Java 8.


@Temporal (пер. временный) – применяется к полям или свойствам с типом [Link] и
[Link].
Уже не нужно ставить аннотацию в Java 8
[Link]

13. Жизненный цикл сущности (new, managed, detached, removed) и


операции (persist, merge, detach, remove, refresh) над сущностью в
разных состояниях жизненного цикла.
1. new - объект создан, не имеет primary key
2. managed (пер. - удалось) - объект создан, имеет primary key, управляется JPA
3. detached (пер. - отстраненный) - объект создан, не управляется JPA
4. removed (пер. удаленный) - объект создан, управляется JPA, будет удален при
commit-е
[Link]

persist()
 new → managed, и объект будет сохранен в базу при commit-е транзакции или в
результате flush операций
 managed → операция игнорируется, однако зависимые Entity могут поменять статус на
managed, если у них есть аннотации каскадных изменений
 removed → managed
 detached → exception сразу или на этапе commit-а транзакции
remove()
 new → операция игнорируется, однако зависимые Entity могут поменять статус на
removed, если у них есть аннотации каскадных изменений и они имели статус managed
 managed → removed и запись объект в базе данных будет удалена при commit-е
транзакции (также произойдут операции remove для всех каскадно зависимых
объектов)
 removed → операция игнорируется
 detached → exception сразу или на этапе commit-а транзакции,
merge()
 detached → либо данные будут скопированы в существующий managed entity с тем же
первичным ключом, либо создан новый managed в который скопируются данные
 new → будет создана новый managed entity, в который будут скопированы данные
прошлого объекта
 managed → операция игнорируется, однако операция merge сработает на каскадно
зависимые Entity, если их статус не managed
 removed → exception сразу или на этапе commit-а транзакции,
refresh()
 managed → будут восстановлены все изменения из базы данных данного Entity, также
произойдет refresh всех каскадно зависимых объектов
 new, removed, detached → exception
detach()
 managed, removed → detached.
 new, detached → операция игнорируется
[Link]

14. Inheritance (3 стратегии построения иерархии) vs.


@MappedSuperClass.
В JPA описаны три стратегии наследования маппинга (Inheritance Mapping Strategies), то есть
как JPA будет работать с классами-наследниками Entity:
1) одна таблица на всю иерархию наследования (a single table per class hierarchy) — все entity,
со всеми наследниками записываются в одну таблицу, для идентификации типа entity
определяется специальная колонка “discriminator column”. Например, если есть entity Animals
c классами-потомками Cats и Dogs, при такой стратегии все entity записываются в таблицу
Animals, но при это имеют дополнительную колонку animalType в которую соответственно
пишется значение «cat» или «dog».Минусом является то что в общей таблице, будут созданы
все поля уникальные для каждого из классов-потомков, которые будет пусты для всех других
классов-потомков. Например, в таблице animals окажется и скорость лазанья по дереву от
cats и может ли пес приносить тапки от dogs, которые будут всегда иметь null для dog и cat
соответственно.
2) объединяющая стратегия (joined subclass strategy) — в этой стратегии каждый класс entity
сохраняет данные в свою таблицу, но только уникальные колонки (не унаследованные от
классов-предков) и первичный ключ, а все унаследованные колонки записываются в таблицы
класса-предка, дополнительно устанавливается связь (relationships) между этими таблицами,
например в случае классов Animals (см.выше), будут три таблицы animals, cats, dogs, причем
в cats будет записана только ключ и скорость лазанья, в dogs — ключ и умеет ли пес
приносить палку, а в animals все остальные данные cats и dogs c ссылкой на
соответствующие таблицы. Минусом тут являются потери производительности от
объединения таблиц (join) для любых операций.
3) одна таблица для каждого класса (table per concrete class strategy) — тут все просто каждый
отдельный класс-наследник имеет свою таблицу, т.е. для cats и dogs (см.выше) все данные
будут записываться просто в таблицы cats и dogs как если бы они вообще не имели общего
суперкласса. Минусом является плохая поддержка полиморфизма (polymorphic relationships) и
то что для выборки всех классов иерархии потребуются большое количество отдельных sql
запросов или использование UNION запроса.
MappedSuperclass это класс от которого наследуются Entity, он может содержать аннотации
JPA, однако сам такой класс не является Entity, ему не обязательно выполнять все
требования установленные для Entity (например, он может не содержать первичного ключа).
Такой класс не может использоваться в операциях EntityManager или Query. Такой класс
должен быть отмечен аннотацией @MappedSuperclass или соответственно описан в xml
файле.
[Link]
usp=sharing

, что это поможет, может есть смысл поменять тип базы данных?
15. n + 1 select (описание и решения).
Допустим, у вас есть коллекция Car объектов (строк базы данных), и у каждого Car есть
коллекция Wheel объектов (также строк). Другими словами, Car→ Wheel это отношение 1-ко-
многим.
Теперь предположим, что вам нужно пройтись по всем машинам, и для каждой распечатать
список колес:
SELECT * FROM Cars;
И тогда для каждого Car:
SELECT * FROM Wheel WHERE CarId = ?
Другими словами, у вас есть один выбор для автомобилей, а затем N дополнительных
выборов, где N - общее количество автомобилей.
В качестве альтернативы можно получить все колеса и выполнить поиск в памяти:
SELECT * FROM Wheel
Это уменьшает количество обращений к базе данных с N+1 до 2. Большинство инструментов
ORM предоставляют несколько способов предотвратить выбор N+1.
[Link]
mapping

Решения:
unidirectional unidirectional bidirectional
OneToMany ManyToone OneToMany/ManyToOne
solution jpql native jpql native jpql native
join fetch + - + - + -
[Link] + - - - +/-** -
BatchSize + + - - +/-** +/-**
EntityGraph + - + - + -
SqlResultSetMapping - - - + - -/+***
HibernateSpecificMapping - -* - + - +
* - если не используем аннотацию JoinColumn и оставляем связанную третью таблицу
** - работает только при выборке собственника с коллекцией зависимых сущностей
*** - работает только при выборке дочерней сущности с ссылкой на родительскую сущность
Выводы:
1. Лучшим вариантом решения N+1 проблемы для простых запросов (1-3 уровня вложенности
связанных объектов) будет join fetch и jpql запрос. Следует придерживаться тактики, когда мы
выбираем из jpql и нативного запроса jpql
2. Если у нас имеется нативный запрос, и мы не заботимся о слабой связанности кода - то
хорошим вариантом будет использование Hibernate Specific Mapping. В противном случае
стоит использовать @SqlResultSetMapping
3. В случаях, когда нам нужно получить по-настоящему много данных, и у нас jpql запрос -
лучше всего использовать EntityGraph
4. Если мы знаем примерное количество коллекций, которые будут использоваться в любом
месте приложения - можно использовать @BatchSize
[Link]

16. Entity Grpah.


[Link] используется почти во всех случаях, чтобы получить хорошо работающее и
масштабируемое приложение. Определение графа сущностей не зависит от запроса и
определяет, какие атрибуты нужно извлечь из базы данных. Граф сущностей может
использоваться в качестве выборки или графика загрузки. Если используется график
выборки, только атрибуты, указанные в графе сущностей, будут обрабатываться как
[Link]. Все остальные атрибуты будут ленивыми. Если используется график
загрузки, все атрибуты, которые не указаны в графе объектов, сохранят свой тип выборки по
умолчанию.
[Link]
[Link]

Для этого существует EntityGraph API, используется он так: с помощью аннотации


@NamedEntityGraph для Entity, создаются именованные EntityGraph объекты, которые
содержат список атрибутов у которых нужно поменять fetchType на EAGER, а потом данное
имя указывается в hits запросов или метода find. В результате fetchType атрибутов Entity
меняется, но только для этого запроса. Существует две стандартных property для указания
EntityGraph в hit:
1) [Link] — все атрибуты перечисленные в EntityGraph меняют fetchType
на EAGER, все остальные на LAZY
2) [Link] — все атрибуты перечисленные в EntityGraph меняют fetchType
на EAGER, все остальные сохраняют свой fetchType (то есть если у атрибута, не указанного в
EntityGraph, fetchType был EAGER, то он и останется EAGER)С помощью NamedSubgraph
можно также изменить fetchType вложенных объектов Entity.
[Link]
usp=sharing

Определение именованного графа сущностей выполняется аннотацией @NamedEntityGraph


в сущности. Он определяет уникальное имя и список атрибутов ( attributeNodes ), которые
должны быть загружены.
[Link]
[Link]

17. Блокировки (оптимистические и пессимистические).


Блокировки, это механизм, позволяющий параллельную работу с одними и теми же данными
в базе данных
Оптимистичный подход предполагает, что параллельно выполняющиеся транзакции редко
обращаются к одним и тем же данным и позволяет им спокойно и свободно выполнять любые
чтения и обновления данных. Но, при окончании транзакции, то есть записи данных в базу,
производится проверка, изменились ли данные в ходе выполнения данной транзакции и если
да, транзакция обрывается и выбрасывается исключение.
Пессимистичный подход напротив, ориентирован на транзакции, которые постоянно или
достаточно часто конкурируют за одни и те же данные и поэтому блокирует доступ к данным
превентивно, в тот момент когда читает их. Другие транзакции останавливаются, когда
пытаются обратиться к заблокированным данным и ждут снятия блокировки (или кидают
исключение)
Оптимистичная блокировка делится на 2 типа : [Link] - блокировка на
чтение и LockModeType.OPTIMISTIC_FORCE_INCREMENT - блокировка на запись
Пессимистичная блокировка на 2 типа - LockModeType.PESSIMISTIC_READ — данные
блокируются в момент чтения, LockModeType.PESSIMISTIC_WRITE — данные блокируются в
момент записи
[Link]
usp=sharing

18. Кеширование (уровни кэширования, @Cacheable, @Cache,


ehcache).
Кеширование является одним из способов оптимизации работы приложения, ключевой
задачей которого является уменьшить количество прямых обращений к базе данных.
Кэш первого уровня – это кэш Сессии (Session), который является обязательным. Через него
проходят все запросы. Перед тем, как отправить объект в БД, сессия хранит объект за счёт
своих ресурсов.
В том случае, если мы выполняем несколько обновлений объекта, Hibernate старается
отсрочить (насколько это возможно) обновление для того, чтобы сократить количество
выполненных запросов. Если мы закроем сессию, то все объекты, находящиеся в кэше
теряются, а далее – либо сохраняются, либо обновляются. Кэш первого уровня это и есть
PersistenceContext.
Кэш второго уровня является необязательным (опциональным) и изначально Hibernate будет
искать необходимый объект в кэше первого уровня. В основном, кэширование второго уровня
отвечает за кэширование объектов. Кэш второго уровня привязан к EntityManagerFactory.
В Hibernate предусмотрен кэш для запросов и он интегрирован с кэшем второго уровня. Это
требует двух дополнительных физических мест для хранения кэшированных запросов и
временных меток для обновления таблицы БД. Этот вид кэширования эффективен только для
часто используемых запросов с одинаковыми параметрами.
[Link]

одновременного доступа к объектам в кэше в hibernate существует четыре:


transactional — полноценное разделение транзакций. Каждая сессия и каждая транзакция
видят объекты, как если бы только они с ним работали последовательно одна транзакция за
другой. Плата за это — блокировки и потеря производительности.
read-write — полноценный доступ к одной конкретной записи и разделение её состояния
между транзакциями. Однако суммарное состояние нескольких объектов в разных
транзакциях может отличаться.
nonstrict-read-write — аналогичен read-write, но изменения объектов могут запаздывать и
транзакции могут видеть старые версии объектов. Рекомендуется использовать в случаях,
когда одновременное обновление объектов маловероятно и не может привести к проблемам.
read-only — объекты кэшируются только для чтения и изменение удаляет их из кэша.
Hibernate реализует область кэша для запросов resultset, который тесно взаимодействует с
кэшем второго уровня Hibernate. Для подключения этой дополнительной функции требуется
несколько дополнительных шагов в коде. Query Cache полезны только для часто
выполняющихся запросов с повторяющимися параметрами. Для начала необходимо добавить
эту запись в файле конфигурации Hibernate:
Уже внутри кода приложения для запроса применяется метод setCacheable(true).
[Link]
usp=sharing

19. HQL, JPQL.


JPQL основан на Hibernate Query Language (HQL), более раннем не стандартизированном
языке запросов, включенном в библиотеку объектно-реляционного отображения Hibernate.
Hibernate и HQL были созданы до появления спецификации JPA. JPQL является
подмножеством языка запросов HQ.
[Link]
usp=sharing

20. Criteria API.


Hibernate Criteria API является более объектно-ориентированным для запросов, которые
получают результат из базы данных. Для операций update, delete или других DDL
манипуляций использовать Criteria API нельзя. Критерии используются только для выборки из
базы данных в более объектно-ориентированном стиле. Используется для динамических
запросов
Вот некоторые области применения Criteria API:
Criteria API поддерживает проекцию, которую мы можем использовать для агрегатных
функций вроде sum(), min(), max() и т.д.
Criteria API может использовать ProjectionList для извлечения данных только из выбранных
колонок.
Criteria API может быть использована для join запросов с помощью соединения нескольких
таблиц, используя методы createAlias(), setFetchMode() и setProjection().
Criteria API поддерживает выборку результатов согласно условиям (ограничениям). Для этого
используется метод add() с помощью которого добавляются ограничения (Restrictions).
Criteria API позволяет добавлять порядок (сортировку) к результату с помощью метода
addOrder().
[Link]
usp=sharing

21. Hibernate proxy (lazy load).


Hibernate использует прокси объект для поддержки отложенной загрузки. Обычно при загрузке
данных из таблицы Hibernate не загружает все отображенные (замаппинные) объекты. Как
только вы ссылаетесь на дочерний объект или ищите объект с помощью геттера, если
связанная сущность не находиться в кэше сессии, то прокси код перейдет к базе данных для
загрузки связанной сущности. Для этого используется javassist, чтобы эффективно и
динамически создавать реализации подклассов ваших entity объектов.
[Link]
usp=sharing

22. Транзакции в Hibernate.


Hibernate построен поверх JDBC API и реализует модель транзакций JDBC. Если быть
точным, Hibernate способен работать или с JDBC транзакциями или с JTA транзакциями —
Java Transaction API.
Транзакцию можно начать вызовом beginTransaction() объекта Session, либо запросить у
Session связанный с ней объект Transaction и позвать у последнего метод begin(). С объектом
Session всегда связан ровно один объект Transaction, доступ к которому может быть получен
вызовом getTransaction().
Методов для подтверждения или отката транзакции у объекта Session нет, необходимо всегда
обращаться к объекту Transaction.
В отличие от JDBC в Hibernate не поддерживаются Savepoints и транзакция может только
быть подтверждена или откачена, без промежуточных вариантов.
Операции над транзакциями
У объекта Transaction есть ещё несколько методов, кроме commit() и rollback(), которые
позволяют тонко управлять поведением транзакции. Метод isActive() позволяет проверить,
есть ли в рамках объекта Transaction управляемая им транзакция. Очевидно, что такая
транзакция существует в промежутке времени между вызовами begin() и commit()/rollback().
Метод setRollbackOnly() помечает транзакцию как откаченную в будущем. В отличие от
rollback() этот метод не закрывает транзакцию и все последующие запросы к базе будут
продолжать выполняться в рамках той же самой транзакции, но завершить эту транзакцию
можно будет только откатом и вызовом rollback(). Вызов commit() на такой транзакции
выбросит исключение. Проверить состояние транзакции можно вызовом getRollbackOnly().
[Link]

Дополнительно
23. Servlet.
24. JSP.
Ответы на доп.вопросы
1. Основные различия между JPA и JDBC (уровень абстракции)
Основное различие между JPA и JDBC - уровень абстракции:
JDBC - это стандарт низкого уровня для взаимодействия с базами данных посредством SQL.
JPA (Java Persistence API) это спецификация Java EE и Java SE, описывающая систему
управления сохранением java объектов в таблицы реляционных баз данных в удобном виде.
Сама Java не содержит реализации JPA, однако существует много реализаций данной
спецификации от разных компаний (открытых и нет). Это не единственный способ сохранения
java объектов в базы данных (ORM систем), но один из самых популярных в Java мире.
[Link]
[Link]

2. Что такое хибер


Hibernate одна из самых популярных открытых реализаций спецификации JPA. JPA только
описывает правила и API, а Hibernate реализует эти описания, впрочем у Hibernate (как и у
многих других реализаций JPA) есть дополнительные возможности, не описанные в JPA (и не
переносимые на другие реализации JPA).
[Link]

Hibernate — библиотека для языка программирования Java, предназначенная для решения


задач объектно-реляционного отображения (object-relational mapping — ORM). Она
представляет собой свободное программное обеспечение с открытым исходным кодом (open
source). Данная библиотека предоставляет легкий в использовании каркас (фреймворк) для
отображения объектно-ориентированной модели данных в традиционные реляционные базы
данных.

3. Будет ли JPQL запрос считаться корректным HQL запросом?


Hibernate Query Language(JPQL) основан на Hibernate Query Language (HQL).
HQL ориентирован на запросы не к таблицам, а к классам. Любой JPQL запрос является
одновременно и корректным HQL запросом. Обратное может быть не верно.
[Link]

4. Когда вызываешь метод createQuery() какой интерфейс получаешь


При вызове createQuery() получаем интерфейс Query.
Это объектно-ориентированное представление запроса Hibernate. Запрос экземпляра Query
получается вызовом [Link]().
[Link]

5. Основные интерфейсы JPA (ОСНОВНЫЕ 2)


На следующем рисунке показана архитектура уровня класса JPA. Он показывает основные
классы и интерфейсы JPA.

1. EntityManagerFactory - это фабричный класс EntityManager. Он создает и управляет


несколькими экземплярами EntityManager.
2. EntityManager - это интерфейс; он управляет операциями сохранения на объектах. Это
работает как фабрика для экземпляра Query .
3. Entity - Сущности - это постоянные объекты, хранящиеся в виде записей в базе
данных.
4. EntityTransaction - имеет непосредственное отношение к EntityManager . Для каждого
EntityManager операции поддерживаются классом EntityTransaction .
5. Persistence - этот класс содержит статические методы для получения
EntityManagerFactory экземпляра.
6. Query - этот интерфейс реализуется каждым поставщиком JPA для получения
реляционных объектов, соответствующих критериям.
[Link]

6. Чем EntityManager отличается от Session


Реализация JPA EntityManager является оберткой (wrap) реализации Hibernate Session.
Session расширяет интерфейс EntityManager. Если Вы выбираете путь JPA, то всегда имеете
возможность быстро перейти, на другие реализации JPA - EclipseLink, OpenJPA, DataNucleus.
При желании, можно работая с EntityManager дотянуться через unwrap до интерфейса Session
и воспользоваться его функционалом.
[Link]
%D0%B8%D1%86%D0%B0-%D0%BC%D0%B5%D0%B6%D0%B4%D1%83-session-%D0%B8-
entitymanager
[Link]

7. Разница между EntityManagerFactory и SessionFactory


Hibernate SessionFactory расширяет JPA EntityManagerFactory.
Таким образом, Hibernate SessionFactory это также JPA EntityManagerFactory.
Оба SessionFactory и EntityManagerFactory содержат метаданные отображения сущностей и
позволяют вам создавать Hibernate Session или EntityManager.
Session против EntityManager
Так же как SessionFactory и EntityManagerFactory, Hibernate Session расширяет JPA
EntityManager. Итак, все методы, определенные в EntityManager, доступны в Hibernate
Session.
[Link]
[Link]

8. В каком случае я могу восстановить удаленную сущность


Refresh
 Если статус Entity managed, то в результате операции Refresh будут восстановлены
все изменения из базы данных данного Entity, также произойдет refresh всех каскадно
зависимых объектов

9. Что такое PersistenceContext (это и есть кэш 1-го уровня у


EntityManager)
PersistenceContext (контекст постоянства) находится между клиентским кодом и БД. Это
промежуточная область, где постоянные данные преобразуются в сущности, готовые для
чтения и изменения клиентским кодом.
Он отслеживает все загруженные данные, отслеживает изменения этих данных и несет
ответственность за то, чтобы в конечном итоге синхронизировать любые изменения обратно в
базу данных в конце транзакции.
Hibernate, использует контекст постоянства для управления жизненным циклом объекта в
приложении.
Экземпляр EntityManager связан с PersistenceContext. PersistenceContext - это набор
экземпляров объекта, в котором для любого постоянного идентификатора объекта
существует уникальный экземпляр объекта. В PersistenceContext экземпляры сущности и
их жизненный цикл управляются. EntityManager API используется для создания и удаления
постоянных экземпляров сущностей, для поиска сущностей по их первичному ключу и для
запросов к сущностям.
PersistenceContext - это кэш первого уровня, в котором все объекты извлекаются из базы
данных или сохраняются в базе данных. PersistenceContext отслеживает любые изменения,
внесенные в управляемый объект. Если что-то меняется во время транзакции, то объект
помечается как грязный. Когда транзакция завершается, эти изменения сбрасываются в
постоянное хранилище.
EntityManager это интерфейс, который позволяет нам взаимодействовать с контекстом
постоянства. Всякий раз, когда мы используем EntityManager, мы фактически
взаимодействуем с контекстом постоянства.
PersistenceContext доступны в двух типах:
 PersistenceContext в области транзакций
 PersistenceContext расширенной области

Транзакционный контекст персистентности.


Контекст постоянства транзакции привязан к транзакции. Как только транзакция
заканчивается, объекты, присутствующие в контексте постоянства, будут сброшены в
постоянное хранилище.
Когда мы выполняем какую-либо операцию внутри транзакции, EntityManager проверяет
PersistenceContext. Если он существует, он будет использован. В противном случае это
создаст PersistenceContext.
Тип контекста персистентности по умолчанию - [Link]. Чтобы
указать EntityManager использовать контекст постоянства транзакции, мы просто аннотируем
его с помощью @PersistenceContext :
@PersistenceContext
private EntityManager entityManager;
Расширенный персистентный контекст
Расширенный контекст постоянства может охватывать несколько транзакций. Мы можем
сохранить сущность без транзакции, но не можем сбросить ее без транзакции.
Чтобы сказать EntityManager использовать контекст персистентности расширенной области,
нам нужно применить атрибут type @PersistenceContext:
@PersistenceContext(type = [Link])
private EntityManager entityManager;
[Link]

10. Как отсоединить сущность от контекста, какими методами


Когда сущность только создана и записана в базу данных или когда наоборот, прочитана из
базы данных, она входит в PersistenceContext и обладает неким экземпляром Session,
который ей управляет. Однако из этого состояния она может внезапно перейти в состояние
«отделенная» (detached). В этом состоянии сущность не связана со своим контекстом
(отделена от него) и нет экземпляра Session, который бы ей управлял.
Перейти в это состояние сущность может по следующим причинам:
 Явный перевод из persisted в detached вызовом метода evict() у Session.
 Сброс контекста методом clear() у Session.
 Явное закрытие сессии методом close().
 Неявное закрытие сессии связанное с удалением объекта Session.
Над detached объектом нельзя выполнять операции, которые требуют наличия
PersistenceContext.
detached сущность можно вернуть в состояние persisted вызовами merge(), lock() или update(),
но не saveOrUpdate().
[Link]

11. Как можно конфигурировать хибернейт - какими способами


(SessionFactory & EntityManagerFactory)
Существует четыре способа конфигурации работы с Hibernate :
— используя аннотации;
— [Link];
— [Link];
— [Link].
Самый частый способ конфигурации: через аннотации и файл [Link], что касается
файлов [Link] и [Link], то [Link] главнее (если в приложение
есть оба файла, то принимаются настройки из файла [Link]). Конфигурация
аннотациями, хоть и удобна, но не всегда возможна, например, если для разных баз данных
или для разных ситуаций вы хотите иметь разные конфигурацию сущностей, то следует
использовать xml файлы конфигураций.
По мимо этого хибернейт можно сконфигурировать с использованием SessionFactory или
EntityManagerFactory.
При использовании JPA или Hibernate у вас есть два варианта:
1. Вы можете загрузиться с помощью встроенного механизма Hibernate и создать
SessionFactory.
2. Или вы можете создать JPA EntityManagerFactory
Начальная загрузка через JPA должна быть предпочтительной. Кроме того, если вы
использовали JPA, и вы вводили EntityManagerFactory через @PersistenceUnit аннотации:
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
Вы можете легко получить доступ к базовому SessionFactory используя unwrap метод:
SessionFactory sessionFactory = [Link]([Link]);
То же самое можно сделать с JPA EntityManager. Если вы вводите EntityManager через
@PersistenceContext аннотацию:
@PersistenceContext
private EntityManager entityManager;
Вы можете легко получить доступ к базовому, Session используя unwrap метод:
Session session = [Link]([Link]);
Вывод
Таким образом, вам следует загружать через JPA, использовать EntityManagerFactory и
EntityManager и развертывать их только в связанных с ними интерфейсах Hibernate, когда вы
хотите получить доступ к некоторым специфичным для Hibernate методам, которые
недоступны в JPA, например, к извлечению объекта через его естественный идентификатор.
[Link]
[Link]

12. Основные аннотации хибера


Hibernate поддерживает как аннотации из JPA, так и свои собственные, которые находятся в
пакете [Link]. Наиболее важные аннотации JPA и Hibernate:
1. @Entity: используется для указания класса как entity bean.
2. @Table: используется для определения имени таблицы из БД, которая будет
отображаться на entity bean.
3. @Access: определяет тип доступа, поле или свойство. Поле — является значением по
умолчанию и если нужно, чтобы hibernate использовал методы getter/setter, то их
необходимо задать для нужного свойства.
4. @Id: определяет primary key в entity bean.
5. @EmbeddedId: используется для определения составного ключа в бине.
6. @Column: определяет имя колонки из таблицы в базе данных.
7. @GeneratedValue: задает стратегию создания основных ключей. Используется в
сочетании с [Link] enum.
8. @OneToOne: задает связь один-к-одному между двумя сущностными бинами.
Соответственно есть другие аннотации @OneToMany, @ManyToOne и @ManyToMany.
9. @Cascade: определяет каскадную связь между двумя entity бинами. Используется в
связке с [Link].
10. @PrimaryKeyJoinColumn: определяет внешний ключ для свойства. Используется
вместе с [Link] и
[Link].

13. @Id
С помощью @Id мы указываем первичный ключ (Primary Key) данного класса.
Типы переменных для @Id: примитивные и примитивные типы-оболочки, String, Date,
BigDecimal, BigInteger.
[Link]
[Link]
[Link]

14. Стратегии генерации @Id - знать все!!!!


[Link] является типом генерации по умолчанию. Выбирает стратегию
генерации на основе конкретного диалекта базы данных. Для большинства популярных баз
данных он выбирает [Link].
[Link] является самым простым в использовании, но не самый
лучший с точки зрения производительности. Он опирается на автоматически
увеличивающийся столбец базы данных и позволяет базе данных генерировать новое
значение при каждой операции вставки. Hibernate требует значения первичного ключа для
каждого управляемого объекта и поэтому должен немедленно выполнить оператор вставки.
Это предотвращает использование различных методов оптимизации, таких как пакетная
обработка JDBC. (Идентити делает инсерт до персиста).
[Link] использует последовательность базы данных для
генерации уникальных значений.
Для получения следующего значения из последовательности базы данных требуются
дополнительные операторы select. Но это не влияет на производительность для большинства
приложений. (Секвенс делает селект, чтобы сгенерить id).
[Link] используется редко. Он моделирует последовательность,
сохраняя и обновляя ее текущее значение в таблице базы данных, что требует
использования пессимистических блокировок, которые помещают все транзакции в
последовательный порядок. Это замедляет работу вашего приложения
[Link]
[Link]

15. Что означает стратегия @[Link]


[Link] является самым простым в использовании, но не самый
лучший с точки зрения производительности. Он опирается на автоматически
увеличивающийся столбец базы данных и позволяет базе данных генерировать новое
значение id при каждой операции вставки. Hibernate требует значения первичного ключа для
каждого управляемого объекта и поэтому должен немедленно выполнить оператор вставки.
Это предотвращает использование различных методов оптимизации, таких как пакетная
обработка JDBC. (Идентити делает инсерт до персиста).
[Link]

16. На какой стороне инкрементится id- на стороне базы или


хибернейта
[Link] - id инкрементится на стороне базы
[Link] - id инкрементится на стороне хибера, использует
дополнительные селекты, чтобы запросить id
[Link]

17. @Column
@Column нужна чтобы указать детали столбца в таблице.
@Column аннотации имеет много атрибутов, такие как name, length, nullable и unique.
Элемент name указывает имя столбца в таблице. Элемент length указывает его длину.
атрибут nullable определяет, является ли элемент обнуляемым, и unique атрибут определяет,
является ли уникальным столбец.
Если мы не укажем эту аннотацию, имя поля будет считаться именем столбца в таблице.
[Link]
[Link]
[Link]

18. @Acess
@Access: определяет тип доступа, поле или свойство. Поле — является значением по
умолчанию и если нужно, чтобы hibernate использовал методы getter/setter, то их необходимо
задать для нужного свойства.
[Link]
[Link]
[Link]

19. Что означает двусторонняя связь. Как это отобразится в коде


У нас есть таблица для каждой корзины и еще одна таблица для каждого товара. В одной
корзине может быть много товаров, поэтому здесь у нас есть отображение «один ко многим» .
То, как это работает на уровне базы данных, заключается в том, что у нас есть cart_id в
качестве первичного ключа в таблице ко
рзины, а также cart_id в качестве внешнего ключа в элементах .
И способ, которым мы делаем это в коде, с @OneToMany .
Давайте сопоставим класс Cart объекту Items таким образом, чтобы он отражал отношения в
базе данных:
@Entity
@Table(name="CART")
public class Cart {
//...
@OneToMany(mappedBy="cart")
private Set<Items> items;
// getters and setters
}

@Entity
@Table(name="ITEMS")
public class Items {
//...
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
public Items() {}
// getters and setters
}
Мы также можем добавить ссылку на корзину в пунктах, используя @ManyToOne, что делает
это двунаправленным отношением. Двунаправленный означает, что мы можем получить
доступ к предметам из корзины, а также к корзине из предметов.
Свойство mappedBy - это то, что мы используем, чтобы сообщить Hibernate, какую
переменную мы используем для представления родительского класса в нашем дочернем
классе.
@OneToMany используется для определения свойства в классе Items, которое будет
использоваться для сопоставления переменной mappedBy. Вот почему у нас есть свойство с
именем «cart» в классе Items:
@ManyToOne связана с переменной класса Cart. Аннотация @JoinColumn ссылается на
сопоставленный столбец.
[Link]

20. Что произойдет если не поставить mappedBy на @OneToOne


@OneToMany - почему выгодно ставить mapped by на этих типах
связей
mappedBy делает связь двунаправленной. Без mappedBy связь будет однонаправленной.
После того, как мы определили сторону-владельца отношений, Hibernate уже располагает
всей информацией, необходимой для отображения этих отношений в нашей базе данных.
Чтобы сделать эту ассоциацию двунаправленной, все, что нам нужно сделать, это определить
сторону ссылки. Обратная или ссылочная сторона просто отображается на сторону-
владельца.
Мы можем легко использовать mappedBy атрибут @OneToMany аннотацию , чтобы сделать
это. Итак, давайте определим нашу сущность Employee :
@Entity
public class Employee {

@Id
@GeneratedValue(strategy = [Link])
private Long id;

@OneToMany(fetch = [Link], mappedBy = "employee")


private List<Email> emails;

// ...
}
@Entity
@Table(name="ITEMS")
public class Items {
//...
@ManyToOne
@JoinColumn(name="cart_id", nullable=false)
private Cart cart;
public Items() {}
// getters and setters
}
Здесь значение mappedBy является именем атрибута сопоставления ассоциации на стороне
владельца. Благодаря этому мы установили двустороннюю связь между нашими
сотрудниками и сотрудниками электронной почты.
[Link]

21. @JoinColumn
Указывает столбец для присоединения к связной сущности или коллекции элементов. Если
сама аннотация @JoinColumn имеет значение по умолчанию, то предполагается наличие
одного столбца соединения и применяются значения по умолчанию.
[Link]
@JoinColumn - в связях вида One2Many/Many2One сторона, находящаяся в собственности
обычно называется стороной “многих”. Обычно, это сторона, которая держит внешний ключ.
Эта аннотация, фактически, описывает физический (как в бд) маппинг на стороне “многих”. В
атрибут name этой аннотации дается название колонки, которая будет присоединена к
таблице “многих” и которая будет заполняться значениями первичных ключей из таблицы-
собственника. Таким образом - мы даём название для внешнего ключа. По факту -
использование этой аннотации опционально, т.к. хибернейт, проанализировав сущность -
поймет сам, что в таблице для этого класса нужно создать колонку, обозначающую внешний
ключ, и даст ей соответствующее название “{name}_id”.
[Link]

22. @JoinTable;щ
Определяет сопоставление ассоциации. Он применяется к владельцу ассоциации.
@JoinTable обычно используется при отображении связей «многие ко многим» и
однонаправленных связей «один ко многим». Он также может использоваться для
сопоставления двунаправленных ассоциаций «многие к одному» или «один ко многим»,
однонаправленных связей «многие к одному» и связей «один к одному» (как
двунаправленных, так и однонаправленных).
Когда @JoinTable используется при отображении отношения с встраиваемым классом на
стороне-владельце отношения, содержащая сущность, а не встраиваемый класс считается
владельцем отношения.
Если @JoinTable аннотация отсутствует, применяются значения по умолчанию для элементов
аннотации.
[Link]

23. Университеты и Студенты - @MаnyToMany - можно ли обойтись


просто этой аннотацией без @JoinTable и @JoinColumn
Вроде как нет, т.к. нам нужна третья таблица для связи ManyToMany (связать первичный и
внешний ключ).
[Link]

24. При аннотации @ManyToMany - ОБА ВЛАДЕЛЬЦЫ СВЯЗИ


Настройка каскадов этой связи немного сложнее т.к. связь по умолчанию является
двунаправленной, и, более того, в этой связи зачастую невозможно выделить собственность и
собственника, т.к. границы между ними сильно размыт. Получается картина, когда обе
стороны могут передавать каскадные изменения друг на друга.
Важно не использовать на связи @ManyToMany [Link], т.к. последний включает в
себя каскад на Remove, а этот каскад заставляет удалять хибернейт данные не только из
смежной таблицы, но и дальше, из таблицы сущности, которая привязана к данной. А за этим,
если к этой сущности были привязаны ещё несколько других - произойдет и их удаление из
базы
[Link]

25. Что ты знаешь про стратегии загрузки


В JPA описаны два типа fetch стратегии:
1) LAZY — данные поля будут загружены только во время первого доступа к этому полю
2) EAGER — данные поля будут загружены немедленно
[Link]

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


для аннотации @Basic и @Collection
EAGER для @Basic и ToOne
LAZY для @Collection и ToMany
[Link]
[Link]

27. Что такое встраиваемый класс (@Embedded)


Встраиваемый (Embeddable) класс это класс который не используется сам по себе, только как
часть одного или нескольких Entity классов. Entity класс могут содержать как одиночные
встраиваемые классы, так и коллекции таких классов. Также такие классы могут быть
использованы как ключи или значения map. Во время выполнения каждый встраиваемый
класс принадлежит только одному объекту Entity класса и не может быть использован для
передачи данных между объектами Entity классов (то есть такой класс не является общей
структурой данных для разных объектов). В целом, такой класс служит для того чтобы
выносить определение общих атрибутов для нескольких Entity, можно считать что JPA просто
встраивает в Entity вместо объекта такого класса те атрибуты, которые он содержит.
1. Такие классы должны удовлетворять тем же правилам что Entity классы, за
исключением того что они не обязаны содержать первичный ключ и быть отмечены
аннотацией Entity
2. Embeddable класс должен быть помечен аннотацией @Embeddable или описан в
XML файле конфигурации JPA,
[Link]
[Link]

28. Как создать составной первичный ключ - где это указывать, как это
должно правильно работать
Допустимые типы атрибутов, входящих в первичный ключ:
1. примитивные типы и их обертки Java
2. строки
3. BigDecimal и BigInteger
4. [Link] и [Link]
В случае автогенерируемого первичного ключа (generated primary keys) допустимы
1. только числовые типы
В случае использования других типов данных в первичном ключе, он может работать только
для некоторых баз данных, т.е. становится непереносимым (not portable)
[Link]

@EmbeddedId указывает на поле составного первичного ключа, а @Embeddable объявляет


класс составным ключом.
@Embeddable
public class BillingAddress implements Serializable {...}
@Entity
@Table(name = "PURCHASE_ORDERS")
@IdClass([Link])
public class PurchaseOrder {...}
Обратите внимание, что есть некоторые ключевые требования, которым должен
соответствовать класс составного ключа:
Мы должны пометить его с помощью @Embeddable.
Он должен реализовать [Link]
Мы должны обеспечить реализацию hashCode() и Equals() методы
Ни одно из полей не может быть сущностью
[Link]
[Link]

29. Для чего еще нужен @Embedded - второй случай, если опустить
составной ключ
В целом, такой класс служит для того чтобы выносить определение общих атрибутов для
нескольких Entity, можно считать что JPA просто встраивает в Entity вместо объекта такого
класса те атрибуты, которые он содержит.
[Link]

30. Как работает первый уровень кэша - когда он есть - когда нет - к
чему он привязан (к какому объекту)
Кэширование - это средство, предоставляемое средами ORM, которое помогает
пользователям быстро запустить веб-приложение, а сама структура помогает сократить
количество запросов к базе данных за одну транзакцию. Hibernate достигает второй цели,
внедряя кэш первого уровня.
Кэш первого уровня в hibernate включен по умолчанию, и вам не нужно ничего делать, чтобы
эта функция работала. На самом деле, вы не можете отключить его даже принудительно.
Кэш первого уровня легко понять, если мы понимаем тот факт, что он связан с объектом
Session. Как мы знаем, объект сеанса создается по требованию из фабрики сеансов и
теряется при закрытии сеанса. Аналогично, кэш первого уровня, связанный с объектом
сеанса, доступен только до тех пор, пока объект сеанса не станет активным. Он доступен
только для объекта сеанса и не доступен для любого другого объекта сеанса в любой другой
части приложения.
Важные факты
1. Кэш первого уровня связан с объектом Session, а другие объекты сеанса в
приложении его не видят.
2. Область действия объектов кэша имеет сессию. Как только сессия закрыта,
кэшированные объекты исчезают навсегда.
3. Кэш первого уровня включен по умолчанию, и вы не можете его отключить.
4. Когда мы запрашиваем объект в первый раз, он извлекается из базы данных и
сохраняется в кэше первого уровня, связанном с сессией хибернейта.
5. Если мы снова запросим тот же объект с тем же объектом сеанса, он будет
загружен из кэша, и никакой SQL-запрос не будет выполнен.
6. Загруженный объект можно удалить из сеанса с помощью метода evict().
Следующая загрузка этого объекта снова вызовет базу данных, если она была
удалена с помощью метода evict().
7. Весь кэш сеанса можно удалить с помощью метода clear(). Это удалит все
сущности, хранящиеся в кэше.

Несколько фактов про кэш первого уровня:


1. Кэш первого уровня не является потокобезопасным.
2. Кэш первого уровня привязан к сессии и уничтожается следом за уничтожением сессии.
Из этого следует один важный вывод: кэш первого уровня не является средством
оптимизации большого количества повторяющихся запросов на выборку со стороны клиента,
т.к. каждый запрос будет обрабатываться в отдельной транзакции, на которую будет выделен
новый объект entityManager, который связан напрямую с новой сессией. Соответственно, на
20 одинаковых запросов пользователя будет создано 20 entityManager и 20 сессий. Будет
выделено 20 транзакций, даже если запросы обрабатываются и поступают одновременно.
Кэш первого уровня нужен:
1. Для сохранения целостности данных
2. Оптимизации запросов на изменение/удаление
3. Оптимизация запросов на выборку в рамках одной транзакции
В пределах жизненного цикла одной сессии и в рамках одной транзакции мы можем изменить
внутреннее состояние сущности неограниченное количество раз, каждое изменение будет
вноситься в кэш первого уровня. Но в базу запрос отправится только тогда, когда будет
сделан комит транзакции. В базу отправятся те данные, которые содержит сущность на
момент последнего изменения. До тех пор, пока транзакция не будет закончена - все
изменения будут храниться в кэше. Даже если мы вызовем 20 раз метод setField() у любой
сущности - в базу в итоге отправится только один запрос.
Если же мы вынуждены читать в рамках одной транзакции несколько раз одни и те же
данные, то, единожды загрузив данные запросом из базы мы будем в дальнейшем работать с
данными внутри кэша, не повторяя дополнительных запросов. Например, если достать
List<User> и затем достать конкретного юзера с id=2, то запрос в базу не будет произведен,
т.к. список всех пользователей уже лежит в кэше. Так же, если мы, уже после того как достали
пользователя с id=2 изменили 10 раз его имя, а затем снова выберем список всех
пользователей - мы и в этом случае не получим дополнительных запросов. В описанном
выше случае будет произведено только два запроса: на выборку списка всех пользователей в
самом начала и один запрос на изменение состояния пользователя уже в конце транзакции.
[Link]

31. К какому объекту привязан кэш второго уровня (к


EntityManagerFactory)
Кэш второго уровня создается в области фабрики EntityManagerFactory и доступен для
использования во всех EntityManager, которые создаются с использованием этой конкретной
фабрики.
Это также означает, что после закрытия фабрики весь кэш, связанный с ним, умирает, а
менеджер кэша также закрывается.
Кроме того, это также означает, что если у вас есть два экземпляра фабрики, в вашем
приложении будет два менеджера кэша, и при доступе к кэшу, хранящемуся в физическом
хранилище, вы можете получить непредсказуемые результаты, такие как пропадание кеша.
1. Всякий раз, когда сессия пытается загрузить объект, самое первое место, где он
ищет кэшированную копию объекта в кэше первого уровня.
2. Если кэшированная копия объекта присутствует в кэше первого уровня, она
возвращается как результат метода загрузки.
3. Если в кэше первого уровня нет кэшированной сущности, то для кэшированной
сущности ищется кэш второго уровня.
4. Если кэш второго уровня имеет кэшированный объект, он возвращается как
результат метода load(). Но перед возвратом объекта он также сохраняется в
кэше первого уровня, так что при следующем вызове метода загрузки объект
будет возвращен из самого кэша первого уровня, и больше не потребуется
обращаться в кэш второго уровня.
5. Если объект не найден в кэше первого уровня и кэше второго уровня, то
выполняется запрос к базе данных, и объект сохраняется на обоих уровнях кэша
перед возвратом в качестве ответа метода load().
6. Кэш второго уровня проверяет себя для измененных объектов.
7. Если какой-либо пользователь или процесс вносят изменения непосредственно
в базу данных, то само по себе кэширование второго уровня не может
обновляться до тех пор, пока не истечет время «timeToLiveSeconds» для этой
области кэша. В этом случае хорошей идеей будет сделать недействительным
весь кеш и позволить hibernate снова построить кэш.
[Link]

32. Как настроить кэш второго уровня.


Со следующими двумя свойствами мы сообщаем Hibernate, что кэширование L2 включено, и
даем ему имя класса фабрики региона:
[Link].use_second_level_cache=true
[Link].factory_class=[Link]
eRegionFactory
Чтобы сделать объект пригодным для кэширования второго уровня, мы помечаем его
аннотацией @Cache или @Cacheable, специфичной для Hibernate, и указываем стратегию
параллельного использования кэша:
1) ALL — все Entity могут кэшироваться в кэше второго уровня
2) NONE — кеширование отключено для всех Entity
3) ENABLE_SELECTIVE — кэширование работает только для тех Entity, у которых
установлена аннотация Cacheable(true), для всех остальных кэширование отключено
4) DISABLE_SELECTIVE — кэширование работает для всех Entity, за исключением тех у
которых установлена аннотация Cacheable(false)
5) UNSPECIFIED — кеширование не определенно, каждый провайдер JPA использует свою
значение по умолчанию для кэширования
[Link]

33. Какой кэш еще есть. Кэш запросов - как настроить. Желательно
понимать как объекты хранятся в кэше второго уровня и в кэше
запросов.
Hibernate также поддерживает QueryCache, который может хранить результаты запроса. Вам
необходимо активировать его в файле [Link], установив для параметра
[Link].use_query_cache=true
и определив
[Link].factory_class
Кроме того, вам также необходимо активировать кэширование для конкретного запроса, для
которого вы хотите кэшировать результаты, вызывая
setCacheable(true)
[Link]

34 Как контролировать объекты второго уровня кэша - как удалить как


посмотреть.
Сохранение или обновление элемента:
save()
update()
saveOrUpdate()
Получение предмета:
load()
get()
list()
iterate()
scroll()
Состояние объекта синхронизируется с базой данных при вызове метода flush(). Чтобы
избежать этой синхронизации, вы можете удалить объект и все коллекции из кэша первого
уровня с помощью evict() метода. Чтобы удалить все элементы из кэша сеанса, используйте
метод [Link]():
ScrollableResult cats = [Link]("from Cat as cat").scroll(); //a
huge result set
while ( [Link]() ) {
Cat cat = (Cat) [Link](0);
doSomethingWithACat(cat);
[Link](cat);
}
Определение того, принадлежит ли элемент кешу сеанса. Сеанс предоставляет contains()
метод для определения того, принадлежит ли экземпляр кешу сеанса.
[Link]

35. @Basic
@Basic — указывает на простейший тип маппинга данных на колонку таблицы базы
данных. Также в параметрах аннотации можно указать fetch стратегию доступа к полю и
является ли это поле null или нет.
[Link]

Самый простой тип сопоставления со столбцом базы данных. Базовая аннотация может быть
применена к постоянному свойству или переменной экземпляра любого из следующих типов:
примитивные типы Java, оболочки примитивных типов, String, BigInteger, BigDecimal, Date,
Calendar, Time, Timestamp, byte[], Byte[], char[], Character[], enums, и любой другой тип,
который реализует [Link].
Использование базовой аннотации является необязательным для постоянных полей и
свойств этих типов. Если базовая аннотация не указана для такого поля или свойства, то
будут применяться значения по умолчанию базовой аннотации.
[Link]

36. @ElementCollection
Определяет коллекцию экземпляров базового типа или встраиваемого класса. Должен быть
указан, если коллекция должна отображаться с помощью таблицы коллекции.
[Link]

37. @OrderBy
Определяет порядок элементов коллекции, оцениваемой ассоциацией или коллекцией
элементов, в тот момент, когда ассоциация или коллекция извлекаются.
[Link]

@OrderBy могут быть применены к элементу коллекции. Когда OrderBy применяется к


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

38. @OrderColumn - как работает, где ставится


Указывает столбец, который используется для поддержания постоянного порядка списка.
@OrderColumn указывается в отношении OneToMany или ManyToMany или в коллекции
элементов. @OrderColumn указывается на стороне отношения, ссылающейся на коллекцию,
которая должна быть упорядочена.
[Link]

39. Различия между @OrderBy и @OrderColumn - пример с базой


данных
@OrderBy в запросе отсортирует, а в кэше вернет неотсортированный порядок.
@OrderedColumn сортирует данные с учетом данных в колонке, и в кеше и в запросе.
Указанный порядок @OrderBy применяется только во время выполнения при получении
результата запроса.

@OrderColumn приводит к постоянному упорядочению соответствующих данных.


[Link]

40. OrphanRemoval
@OrphanRemoval - управляет поведением осиротевшими сущностями.
OrphanRemoval: если мы вызовем setOrders(null), Order энтити будет удалена из БД
автоматически.
Если каскад ремув, то setOrders(null), Order НЕ БУДЕТ УДАЛЕНА из БД автоматически.
Удаление сирот в отношениях
Когда целевой объект в отношении «один-к-одному» или «один-ко-многим» удаляется из
отношения, часто желательно каскадно удалить операцию для целевого объекта. Такие
целевые объекты считаются «сиротами», а атрибут orphanRemoval может использоваться для
указания того, что потерянные объекты должны быть удалены. Например, если в заказе много
позиций, и одна из них удалена из заказа, удаленная позиция считается сиротой. Если для
orphanRemoval установлено значение true, объект позиции будет удален при удалении
позиции из заказа.
[Link]

41. Почему есть Cascade Removal и orphanRemoval - есть еще один


случай - когда есть разница? А при удалении разницы нет - в каком
случае есть?
CascadeType определяет каскадные операции, которые применяются в элементе каскада
аннотаций отношений.
Пример : позиция является частью заказа; если заказ удален, позиция также должна быть
удалена. Это называется каскадным отношением удаления.
orphanRemoval - когда целевой объект в отношении один-к-одному или один-ко-многим
удаляется из отношения.
Пример: если в заказе много позиций, и одна из них удалена из заказа, удаленная позиция
считается сиротой. Если для orphanRemoval установлено значение true, объект позиции будет
удален при удалении позиции из заказа.
Когда использовать:
Каскадное удаление удаляет все дочерние элементы при удалении родителя.
Таким образом, если вы удалите пользовательскую сущность, JPA удалит также все его
фотографии.
[Link]
related-entities/

42. Метод unWrap()


JPA обеспечивает легкий доступ к API базовых реализаций. EntityManager и
EntityManagerFactory обеспечивают разворачивают метод, который возвращает
соответствующие классы реализации JPA. В случае Hibernate это Session и SessionFactory.
Session session = [Link]([Link]);
SessionFactory sessionFactory =
[Link]().unwrap([Link]);
В первой строке я получаю текущий сеанс Hibernate от EntityManager . Поэтому я называю
UnWrap метод на EntityManager и обеспечить сеанс класса в качестве параметра.
Вторая строка выглядит очень похоже. Я получаю EntityManagerFactory для текущего
EntityManager и вызываю метод unwrap специфичный для Hibernate класс SessionFactory.
Эти классы предоставляют вам полный доступ к проприетарным функциям Hibernate, таким
как поддержка Streams и Optional.
[Link]

43. Каким методом очищается кэш 1-го уровня


Это делается с помощью двух методов:
evict()
clear()
Здесь evict() используется для удаления конкретного объекта из кэша, связанного с сеансом,
а метод clear() используется для удаления всех кэшированных объектов, связанных с
сеансом.
[Link]

Spring

ekerjvjhmz

Spring вопросы-ответы
Сохранено на Диске.
р‌Spring‌‌
вопросы-ответы‌‌Хилькевич‌‌Игорь‌‌31.03.2020‌‌

Вопросы‌‌из‌‌“all‌‌question”‌‌
1.‌‌IOC.‌‌
2.‌‌Application‌‌context.‌‌
3.‌‌Bean‌‌Factory.‌‌
4.‌‌Dependency‌‌injection.‌‌
5.‌‌@Qualifier‌‌vs.‌‌@Primary.‌‌
6.‌‌@Bean‌‌vs.‌‌@Component.‌‌
7.‌‌@Component,‌‌@Service,‌‌@Repository.‌‌
8.‌‌@Controller‌‌vs.‌‌@RestController.‌‌
9.‌‌@ResponseBody,‌‌@ResponseEntity.‌‌
10.‌‌Транзакции‌‌в‌‌Spring.‌‌@Transactional‌‌
11.‌‌Жизненный‌‌цикл‌‌бина‌‌в‌‌Spring.‌‌
12.‌‌Model,‌‌ModelMap,‌‌ModelAndView.‌‌
13.‌‌Какие‌‌паттерны‌‌используются‌‌в‌‌Spring‌‌(singleton,‌‌prototype,‌
‌builder,‌‌proxy,‌‌chain‌‌of‌‌
responsibility,‌‌dependency‌‌injection).‌‌
14.‌‌Bean‌‌scopes.‌‌
15.‌‌Spring‌‌Data.‌‌
16.‌‌Spring‌‌Security.‌‌
17.‌‌@Recource,‌‌@Autowired,‌‌@Inject.‌‌
18.‌‌@Conditional.‌‌
19.‌‌@ComponentScan.‌‌
20.‌‌Spring‌‌Boot.‌‌
21.‌‌Нововведение‌‌Spring‌‌5.‌‌
Дополнительно‌‌
22.‌‌Spring‌‌Cloud‌‌(Data‌‌Flow).‌‌
23.‌‌Spring‌‌Integration.‌‌
24.‌‌Spring‌‌Batch.‌‌
25.‌‌Spring‌‌Hateoas.‌‌
26.‌‌Spring‌‌Rest‌‌Docs.‌‌
27.‌‌Spring‌‌AMQP.‌‌
28.‌‌Spring‌‌Web‌‌Flow.‌‌
29.‌‌Spring‌‌Kafka.‌‌
30.‌‌etc.‌‌Spring‌‌projects.‌‌

Доп.‌‌вопросы‌‌
1.‌‌Контейнеры‌‌спринга‌‌
2.‌‌Части‌‌спринга,‌‌модули‌‌
3.‌‌АОР‌‌
4.‌‌Spring‌‌Framework‌‌
5.‌‌BeanFactory‌‌и‌‌ApplicationContext‌‌разница‌‌
6.‌‌Работа‌‌спринга‌‌с‌‌ДАО‌‌
7.‌‌Что‌‌такое‌‌контроллер‌‌
8.‌‌View-resolver‌‌
Вы предлагаете.
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Включить программу чтения с экрана
рSpring
вопросы-ответы Хилькевич Игорь 31.03.2020
чччччччччччччччччччччччччччччччччччччччччччччччччччччччччччччччч
чччччччччччччччччччччч
Вопросы из “all question”
1. IOC.
2. Application context.
3. Bean Factory.
4. Dependency injection.
5. @Qualifier vs. @Primary.
6. @Bean vs. @Component.
7. @Component, @Service, @Repository.
8. @Controller vs. @RestController.
9. @ResponseBody, @ResponseEntity.
10. Транзакции в Spring. @Transactional
11. Жизненный цикл бина в Spring.
12. Model, ModelMap, ModelAndView.
13. Какие паттерны используются в Spring (singleton, prototype, builder, proxy, chain of
responsibility, dependency injection).
14. Bean scopes.
15. Spring Data.
16. Spring Security.
17. @Recource, @Autowired, @Inject.
18. @Conditional.
19. @ComponentScan.
20. Spring Boot.
21. Нововведение Spring 5.
Дополнительно
22. Spring Cloud (Data Flow).
23. Spring Integration.
24. Spring Batch.
25. Spring Hateoas.
26. Spring Rest Docs.
27. Spring AMQP.
28. Spring Web Flow.
29. Spring Kafka.
30. etc. Spring projects.

Доп. вопросы
1. Контейнеры спринга
2. Части спринга, модули
3. АОР
4. Spring Framework
5. BeanFactory и ApplicationContext разница
6. Работа спринга с ДАО
7. Что такое контроллер
8. View-resolver
9. [Link] разница [Link]
10. form binding
11. Как сделать локализацию в приложении
12. spring mvc interceptor
13. parent in [Link]
14. @Sheduler
15. Внедрение prototype в singleton
16. @Profile
17. Транзакция с аннотацией @Transactional вызывает метод без аннотации
18. @Around
19. SPELL
20. Container - как устроен (Map)
21. FileSystemApplicationContext
22. bean definition
23. Статические методы в бинах
24. ContextLoaderListener
25. HandlerException
26. @ControllerAdvice
27. @ModelAttribute над методом
28. Реактивное программирование и 4 принципа
29. WebSocket
30. @Lookup
31. @Target и @Retention
32. Bean
33. Прокси
34. Spring MVC
35. Starter-pack Spring Boot - как создать?
36. SOAP vs REST
37. Spring Data - что под капотом
38. Spring Security - что под капотом
39. @Secured
40. Как исключить класс, автоконфигурацию класса в Spring Boot
41. Как заинжектить примитив
42. Как заинжектить коллекцию
43. BeanPostProcessor vs BeanFactoryPostProcessor
44. Часто используемые аннотации спринга
==========================================
45. @ExceptionHandler

Ответы на вопросы “all question”


1. IOC.
Центральной частью Spring является подход Inversion of Control, который позволяет
конфигурировать и управлять объектами Java с помощью рефлексии. Вместо ручного
внедрения зависимостей, фреймворк забирает ответственность за это посредством
контейнера. Контейнер отвечает за управление жизненным циклом объекта: создание
объектов, вызов методов инициализации и конфигурирование объектов путём связывания их
между собой.
Объекты, создаваемые контейнером, также называются управляемыми объектами
(beans). Обычно, конфигурирование контейнера, осуществляется путём внедрения аннотаций
(начиная с 5 версии J2SE), но также, есть возможность, по старинке, загрузить XML-файлы,
содержащие определение bean’ов и предоставляющие информацию, необходимую для
создания bean’ов.
Объекты могут быть получены одним из двух способов:
Dependency Lookup (поиск зависимости) — шаблон проектирования, в котором
вызывающий объект запрашивает у объекта-контейнера экземпляр объекта с определённым
именем или определённого типа.
Dependency Injection (внедрение зависимости) — шаблон проектирования, в котором
контейнер передает экземпляры объектов по их имени другим объектам с помощью
конструктора, свойства или фабричного метода.
[Link]

2. Application context.
ApplicationContext - это главный интерфейс в Spring-приложении, который предоставляет
информацию о конфигурации приложения. Так же, как BeanFactory, ApplicationContext
загружает бины, связывает их вместе и конфигурирует их определённым образом. Но кроме
этого, ApplicationContext обладает дополнительной функциональностью.
ApplicationContext предоставляет:
 Фабричные методы бина для доступа к компонентам приложения
 Возможность загружать файловые ресурсы в общем виде
 Возможность публиковать события и регистрировать обработчики на них
 Возможность работать с сообщениями с поддержкой интернационализации
 Наследование от родительского контекста
[Link]
[Link]
[Link]

Основные этапы поднятия ApplicationContext:


1 этап: Парсирование конфигурации (xml, groovy, JavaConfig и пр.) и создание всех
BeanDefinition (AnnotatedBeanDefinitionReader, BeanDefinitionReader,
ClassPathBeanDefinitionScanner)
2 этап: Настройка созданных BeanDefinition (BeanFactoryPostProcessor)
3 этап: Создание кастомных FactoryBean (FactoryBean<T>)
4 этап: BeanFactory создает экземпляры бинов, при необходимости делегируя
создание FactoryBean (BeanFactory)
5 этап: Настройка созданных бинов (BeanPostProcessor)
[Link]

3. Bean Factory.
BeanFactory - это фактический контейнер, который создает, настраивает и управляет
рядом bean-компонентов. Эти бины обычно взаимодействуют друг с другом и, таким образом,
имеют зависимости между собой. Эти зависимости отражены в данных конфигурации,
используемых BeanFactory. BeanFactory обычно используется тогда, когда ресурсы
ограничены (мобильные устройства). Поэтому, если ресурсы не сильно ограничены, то лучше
использовать ApplicationContext.
[Link]
[Link]

4. Dependency injection.
Dependency Injection (внедрение зависимости) - это набор паттернов и принципов разработки
программного обеспечения, которые позволяют писать слабосвязный код. В полном
соответствии с принципом единой обязанности объект отдаёт заботу о построении требуемых
ему зависимостей внешнему, специально предназначенному для этого общему механизму.
[Link]

Под DI понимают то Dependency Inversion (инверсию зависимостей, то есть попытки не делать


жестких связей между вашими модулями/классами, где один класс напрямую завязан на
другой), то Dependency Injection (внедрение зависимостей, это когда объекты котиков
создаете не вы в main-е и потом передаете их в свои методы, а за вас их создает спринг, а вы
ему просто говорите что-то типа "хочу сюда получить котика" и он вам его передает в ваш
метод). Мы чаще будем сталкиваться в дальнейших статьях со вторым.
Внедрение зависимости (Dependency injection, DI) — процесс, когда один объект реализует
свой функционал через другой. Является специфичной формой «инверсии управления»
(Inversion of control, IoC), когда она применяется к управлению зависимостями. В полном
соответствии с принципом единой обязанности объект отдаёт заботу о построении требуемых
ему зависимостей внешнему, специально предназначенному для этого общему механизму.
К достоинствам применения DI можно отнести:
 Сокращение объема связующего кода. Одним из самых больших плюсов DI является
возможность значительного сокращения объема кода, который должен быть написан
для связывания вместе различных компонентов приложения. Зачастую этот код очень
прост — при создании зависимости должен создаваться новый экземпляр
соответствующего объекта.
 Упрощенная конфигурация приложения. За счет применения DI процесс
конфигурирования приложения значительно упрощается. Для конфигурирования
классов, которые могут быть внедрены в другие классы, можно использовать
аннотации или XML-файлы.
 Возможность управления общими зависимостями в единственном репозитории. При
традиционном подходе к управлению зависимостями в общих службах, к которым
относятся, например, подключение к источнику данных, транзакция, удаленные
службы и т.п., вы создаете экземпляры (или получаете их из определенных
фабричных классов) зависимостей там, где они нужны — внутри зависимого класса.
Это приводит к распространению зависимостей по множеству классов в приложении,
что может затруднить их изменение. В случае использования DI вся информация об
общих зависимостях содержится в единственном репозитории (в Spring есть
возможность хранить эту информацию в XML-файлах или Java классах), что
существенно упрощает управление зависимостями и снижает количество возможных
ошибок.
 Улучшенная возможность тестирования. Когда классы проектируются для DI,
становится возможной простая замена зависимостей. Это особенно полезно при
тестировании приложения.
 Стимулирование качественных проектных решений для приложений. Вообще говоря,
проектирование для DI означает проектирование с использованием интерфейсов.
Используя Spring, вы получаете в свое распоряжение целый ряд средств DI и можете
сосредоточиться на построении логики приложения, а не на поддерживающей DI
платформе.
Как реализуется DI в Spring Framework?
Реализация DI в Spring основана на двух ключевых концепциях Java — компонентах JavaBean
и интерфейсах. При использовании Spring в качестве поставщика DI вы получаете гибкость
определения конфигурации зависимостей внутри своих приложений разнообразными путями
(т.е. внешне в XML-файлах, с помощью конфигурационных Java классов Spring или
посредством аннотаций Java в коде). Компоненты JavaBean (также называемые POJO (Plain
Old Java Object — простой старый объект Java)) предоставляют стандартный механизм для
создания ресурсов Java, которые являются конфигурируемыми множеством способов. За счет
применения DI объем кода, который необходим при проектировании приложения на основе
интерфейсов, снижается почти до нуля. Кроме того, с помощью интерфейсов можно получить
максимальную отдачу от DI, потому что бины могут использовать любую реализацию
интерфейса для удовлетворения их зависимости.
К типам реализации внедрения зависимостей в Spring относят:
Constructor Dependency Injection — это тип внедрения зависимостей, при котором
зависимости компонента предоставляются ему в его конструкторе (или конструкторах).
Рекомендуется как основной способ, т.к. даже без спринга внедрение зависимостей будет
работать корректно.
Setter Dependency Injection — контейнер IoC внедряет зависимости компонента в
компонент через методы установки в стиле JavaBean. В основном через сеттеры. При
модификации не создает новые экземпляры, в отличии от конструктора. Он при каждой
модификации создаёт новый экземпляр.
[Link]

5. @Qualifier vs. @Primary.


Если есть два одинаковых бина (по типу и имени) спринг не знает какой именно использовать
и выдает exception. Если над одним из этих бинов установлена @Primary, то его использовать
предпочтительнее (может использоваться на этапе конфигурации с соответствующими
аннотациями). Но если нам нужно использовать в работе оба этих бина, можно над каждым
поставить @Qualifier и задать имя, для идентификации этих бинов (работает только с
@Autowired).

[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

6. @Bean vs. @Component.


@Component и @Bean делают две совершенно разные вещи
@Component (и @Service и @Repository) используются для автоматического
обнаружения и автоматической настройки bean-компонентов с использованием сканирования
пути к классам. Существует неявное взаимно-однозначное соответствие между
аннотированным классом и компонентом (т.е. Один компонент на класс). При таком подходе
управление проводкой довольно ограничено, поскольку оно чисто декларативное.
@Bean используется для явного объявления одного компонента, а не для того, чтобы
Spring делал это автоматически, как указано выше. Он отделяет объявление bean-компонента
от определения класса и позволяет вам создавать и настраивать bean-компоненты точно по
вашему выбору.
[Link]
@Bean используется например для того чтобы внедрить объект сторонней закрытой
библиотеки

7. @Component, @Service, @Repository.


Они все служат для обозначения класса как Бин.
 @Compone@vmishinant - Spring определяет этот класс как кандидата для создания
bean.
 @Service - класс содержит бизнес-логику и вызывает методы на уровне хранилища.
Ничем не отличается от классов с @Component.
 @Repository - указывает, что класс выполняет роль хранилища (объект доступа к
DAO). При этом автоматически перехватывает специфические Java исключения и
пробрасывает их дальше как неконтролируемые исключения доступа к данным Spring.
Для этого в контексте прописывается класс
PersistenceExceptionTranslationPostProcessor. В @Repository будет полный стектрайс
со всеми методами откуда летит эксепшн, в @Component/@Service будет один
эксепшн.
 @Controller - указывает, что класс выполняет роль контроллера MVC. Диспетчер
сервлетов просматривает такие классы для поиска @RequestMapping.
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

8. @Controller vs. @RestController.


@Controller помечает класс как контроллер HTTP запросов. @Controller обычно используется
в сочетании с аннотацией @RequestMapping, используемой в методах обработки запросов.
В Spring 4.0 была представлена аннотация @RestController. Применив ее к контроллеру
автоматически добавляются аннотации @Controller, а также @ResponseBody применяется ко
всем методам. Аннотация @ResponseBody сообщает контроллеру, что возвращаемый объект
автоматически сериализуется в JSON и передается обратно в объект HttpResponse.
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

@RestController = @Controller + @ResponseBody


@RestController превращает помеченный класс в Spring-бин. Этот бин для конвертации
входящих/исходящих данных использует Jackson message converter. Как правило целевые
данные представлены в json или xml.

9. @ResponseBody, @ResponseEntity.
@ResponseEntity необходим, только если мы хотим кастомизировать ответ, добавив к нему
статус ответа (1хх - информационные, 2хх - успешные, 3хх - перенаправления, 4хх - ошибки
клиента, 5хх - ошибки сервера). Во всех остальных случаях будем использовать
@ResponseBody. Будет передаваться ответ состоящий из заголовка, тела и статуса.
Аннотация @ResponseBody сообщает контроллеру, что возвращаемый объект автоматически
сериализуется в JSON и передается обратно в объект HttpResponse.
[Link]

10. Транзакции в Spring. @Transactional.


Spring поддерживает два типа управления транзакциями:
Программное управление транзакциями: вы должны управлять транзакциями с
помощью программирования. Это способ достаточно гибкий, но его сложно поддерживать.
Либо через использование TransactionTemplate, либо через реализацию
PlatformTransactionManager напрямую. Используется, если нужно работать с небольшим
количеством транзакций.
Декларативное управление транзакциями: вы отделяете управление транзакциями
от бизнес-логики. Вы используете только аннотации @Transactional и конфигурацией на
основе XML для управления транзакциями. Наиболее предпочтительный способ.
Аннотация сама по себе определяет область действия одной транзакции БД.
Транзакция БД происходит внутри области действий persistence context.
Persistence контекстом в JPA является EntityManager, который использует внутри класс
Session ORM-фреймворка Hibernate (при использовании Hibernate как persistence
провайдера). Persistence контекст это объект-синхронайзер, который отслеживает состояния
ограниченного набора Java объектов и синхронизирует изменения состояний этих объектов с
состоянием соответствующих записей в БД.
Один объект Entity Manager не всегда соответствует одной транзакции БД. Один
объект Entity Manager может быть использован несколькими транзакциями БД. Самый частый
случай такого использования - когда приложение использует шаблон «Open Session in View»
для предотвращения исключений «ленивой» инициализации. В этом случае запросы, которые
могли быть выполнены одной транзакцией при вызове из слоя сервиса, выполняются в
отдельных транзакциях в слое View, но они совершаются через один и тот же Entity Manager.
При этом @PersistenceContext не может внедрить entity manager напрямую. Entity
Manager это интерфейс, и то что внедряется в бин не является самим по себе entity
менеджером, это context aware proxy, который будет делегировать к конкретному entity
менеджеру в рантайме.
Но прокси persistence контекста, которое имплементирует EntityManager не является
достаточным набором компонентов для осуществления декларативного управления
транзакциями. На самом деле нужно три компонента:
 Прокси Entity менеджера
 Аспект транзакций
 Менеджер транзакций
Аспект транзакций — «around» аспект, который вызывается и до и после выполнения
аннотированного бизнес метода. Конкретный класс для имплементации этого аспекта это
TransactionInterceptor.
Аспект транзакций имеет две главные функции:
В момент «до» аспект определяет выполнить ли выполняемый метод в пределах уже
существующей транзакции БД или должна стартовать новая отдельная транзакция. В момент
«до» аспект сам не содержит логики по принятию решения, решение начать новую
транзакцию, если это нужно, делегируется Transaction менеджеру.
В момент «после» аспект решает что делать с транзакцией, делать коммит, откат или
оставить незакрытой.
Transaction менеджер. Менеджер транзакций должен предоставить ответ на два
вопроса: должен ли создаться новый Entity Manager? Должна ли стартовать новая транзакция
БД?
Ответы необходимы предоставить в момент когда вызывается логика аспекта
транзакций в момент «до». Менеджер транзакций принимает решение, основываясь на
следующих фактах: выполняется ли хоть одна транзакция в текущий момент; нет ли атрибута
«propagation» у метода, аннотированного @Transactional (для примера, REQUIRES_NEW
всегда стартует новую транзакцию).
Если менеджер решил создать новую транзакцию, тогда:
 Создается новый entity менеджер
 «Привязка» entity менеджера к текущему потоку (Thread)
 «Взятие» соединения из пула соединений БД
 «Привязка» соединения к текущему потоку
И entity менеджер и это соединение привязываются к текущему потоку, используя
переменные ThreadLocal. Они хранятся в потоке, пока выполняется транзакция, и затем
передаются менеджеру транзакций для очистки, когда они уже будут не нужны. Любая часть
программы, которой нужен текущий entity manager или соединение, может заполучить их из
потока. Этим компонентом программы, который делает именно так является Entity Manager
Proxy.
EntityManager proxy. Когда бизнес метод делает вызов, например,
[Link](), этот вызов не вызывается напрямую у entity менеджера. Вместо этого
бизнес метод вызывает прокси, который достает текущий entity менеджер из потока, в
который его положил менеджер транзакций.
Как использовать:
В файле конфигурации нужно определить менеджер транзакций transactionManager для
DataSource.
<bean id="transactionManager"
class="[Link]">
<property name="dataSource" ref="dataSource">
</property></bean>
Включить поддержку аннотаций, добавив запись в контекстном xml файле вашего spring-
приложения ИЛИ добавьте @EnableTransactionManagement в ваш конфигурационный файл
Добавить аннотацию @Transactional в класс (метод класса) или интерфейс (метод
интерфейса).
У @Transactional есть ряд параметров:
@Transactional (isolation=Isolation.READ_COMMITTED) - уровень изоляции.
@Transactional(timeout=60) - По умолчанию используется таймаут, установленный по
умолчанию для базовой транзакционной системы. Сообщает менеджеру tx о
продолжительности времени, чтобы дождаться простоя tx, прежде чем принять решение об
откате не отвечающих транзакций.
@Transactional(propagation=[Link]) - (Если не указано, распространяющееся
поведение по умолчанию — REQUIRED) Указывает, что целевой метод не может работать
без другой tx. Если tx уже запущен до вызова этого метода, то он будет продолжаться в том
же tx, или новый tx начнется вскоре после вызова этого метода.
 REQUIRES_NEW - Указывает, что новый tx должен запускаться каждый раз при вызове
целевого метода. Если уже идет tx, он будет приостановлен, прежде чем запускать
новый.
 NESTED
 MANDATORY - Указывает, что для целевого метода требуется активный tx. Если tx не
будет продолжаться, он не сработает, выбросив исключение.
 SUPPORTS - Указывает, что целевой метод может выполняться независимо от tx.
Если tx работает, он будет участвовать в том же tx. Если выполняется без tx, он все
равно будет выполняться, если ошибок не будет. Методы, которые извлекают данные,
являются лучшими кандидатами для этой опции.
 NOT_SUPPORTED - Указывает, что целевой метод не требует распространения
контекста транзакции. В основном те методы, которые выполняются в транзакции, но
выполняют операции с оперативной памятью, являются лучшими кандидатами для
этой опции.
 NEVER - Указывает, что целевой метод вызовет исключение, если выполняется в
транзакционном процессе. Этот вариант в большинстве случаев не используется в
проектах.
@Transactional (rollbackFor=[Link]) - Значение по умолчанию:
rollbackFor=[Link] В Spring все классы API бросают RuntimeException, это
означает, что если какой-либо метод не выполняется, контейнер всегда откатывает текущую
транзакцию. Проблема заключается только в проверенных исключениях. Таким образом, этот
параметр можно использовать для декларативного отката транзакции, если происходит
Checked Exception.
@Transactional (noRollbackFor=[Link]) - Указывает, что откат не должен
происходить, если целевой метод вызывает это исключение. Если внутри метода с
@Transactional есть другой метод с аннотацией @Transactional (вложенная транзакция), то
отработает только первая (в которую вложена). Из-за особенностей создания proxy. Но у
аннотации @Transactional можно указать параметры.
=================================
Распространение транзакций
Когда вызывается метод с @Transactional происходит особая уличная магия: proxy, который
создал Spring, создаёт persistence context (или соединение с базой), открывает в нём
транзакцию и сохраняет всё это в контексте нити исполнения (натурально, в ThreadLocal). По
мере надобности всё сохранённое достаётся и внедряется в бины. Привязка транзакций к
нитям (threads) позволяет использовать семантику серверов приложений J2EE, в которой
гарантируется, что каждый запрос получает свою собственную нить.

Таким образом, если в вашем коде есть несколько параллельных нитей, у вас будет и
несколько параллельных транзакций, которые будут взаимодействовать друг с другом
согласно уровням изоляции. Но что произойдёт, если один метод с @Transactional вызовет
другой метод с @Transactional? В Spring можно задать несколько вариантов поведения,
которые называются правилами распространения.

[Link] — применяется по умолчанию. При входе в @Transactional метод


будет использована уже существующая транзакция или создана новая транзакция, если
никакой ещё нет
Propagation.REQUIRES_NEW — второе по распространенности правило. Транзакция всегда
создаётся при входе метод с Propagation.REQUIRES_NEW, ранее созданные транзакции
приостанавливаются до момента возврата из метода.
 [Link] — корректно работает только с базами данных, которые умеют
savepoints. При входе в метод в уже существующей транзакции создается savepoint,
который по результатам выполнения метода будет либо сохранен, либо откачен. Все
изменения, внесенные методом, подтвердятся только позднее, с подтверждением
всей транзакции. Если текущей транзакции не существует, будет создана новая.
 [Link] — обратный по отношению к Propagation.REQUIRES_NEW:
всегда используется существующая транзакция и кидается исключение, если текущей
транзакции нет.
 [Link] — метод с этим правилом будет использовать текущую
транзакцию, если она есть, либо будет исполнятся без транзакции, если её нет.
 Propagation.NOT_SUPPORTED — одно из самых забавных правил. При входе в метод
текущая транзакция, если она есть, будет приостановлена и метод будет выполняться
без транзакции.
 [Link] — правило, которое явно запрещает исполнение в контексте
транзакции. Если при входе в метод будет существовать транзакция, будет
выброшено исключение.
Все эти правила действуют как при вызове метода в текущем потоке, так и выполнения в
другом потоке. В случае другого потока транзакция будет относится к нему.

Куда же ставить @Transactional?


Классическое приложение обычно имеет многослойную архитектуру:

контроллеры > слой логики > слой доступа к данным > слой ORM

Где здесь место для @Transactional? Слой ORM обычно никто не пишет сам и использует
какое-либо стандартное решение, в которое аннотации не вставишь.

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

Слой логики представляется идеальным местом для @Transactional: именно здесь набор
запросов к базе оформляется в единую осмысленную операцию в приложении. Зная, что
делает ваше приложение, вы можете четко разграничить логические единицы работы в нём и
расставить границы транзакций.

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

И последнее — никогда не аннотируйте интерфейсы. Аннотации не наследуются и поэтому, в


зависимости от настроек Spring, вы можете внезапно оказаться фактически без своих
@Transactional.
[Link]
[Link]
[Link]

Транзакциями в Spring управляют с помощью Declarative Transaction Management


(программное управление). Используется аннотация @Transactional для описания
необходимости управления транзакцией. В файле конфигурации нужно добавить настройку
transactionManager для DataSource.

11. Жизненный цикл бина в Spring.

Жизненный цикл Spring бина — время существования класса. Spring бины


инициализируются при инициализации Spring контейнера и происходит внедрение всех
зависимостей. Когда контейнер уничтожается, то уничтожается и всё содержимое. Если нам
необходимо задать какое-либо действие при инициализации и уничтожении бина, то нужно
воспользоваться методами init() и destroy(). Для этого можно использовать аннотации
@PostConstruct и @PreDestroy().
Запуск Spring приложение - запускается Spring контейнер, далее проходится по всем
файлам и если видит аннотацию Bean - создается объект бина, затем внедряются
зависимости (Dependency Injection) - затем вызывается init метод у данного бина - бин будет
готов к использованию и пользователь получит доступ к нему - когда пользователь завершит
приложение - спринг вызовет дестрой метод - и затем приложение остановится
Жизненный цикл бинов:
 Загрузка описаний бинов, создание графа зависимостей(между бинами)
 Создание и запуск BeanFactoryPostProcessors
 Создание бинов
 Spring внедряет значения и зависимости в свойства бина
 Если бин реализует метод setBeanName() из интерфейса NameBeanAware, то ID бина
передается в метод
 Если бин реализует BeanFactoryAware, то Spring устанавливает ссылку на bean factory
через setBeanFactory() из этого интерфейса.
 Если бин реализует интерфейс ApplicationContextAware, то Spring устанавливает
ссылку на ApplicationContext через setApplicationContext().
 BeanPostProcessor это специальный интерфейс, и Spring позволяет бинам
имплементировать этот интерфейс. Реализуя метод postProcessBeforeInitialization(),
можно изменить экземпляр бина перед его(бина) инициализацией(установка свойств и
т.п.)
 Если определены методы обратного вызова, то Spring вызывает их. Например, это
метод, аннотированный @PostConstruct или метод initMethod из аннотации @Bean.
 Теперь бин готов к использованию. Его можно получить с помощью метода
ApplicationContext#getBean().
 После того как контекст будет закрыт(метод close() из ApplicationContext), бин
уничтожается.
 Если в бине есть метод, аннотированный @PreDestroy, то перед уничтожением
вызовется этот метод. Если бин имплементирует DisposibleBean, то Spring вызовет
метод destroy(), чтобы очистить ресурсы или убить процессы в приложении. Если в
аннотации @Bean определен метод destroyMethod, то вызовется и он.
Интерфейс BeanPostProcessor позволяют разработчику самому имплементировать некоторые
методы бинов перед инициализацией и после уничтожения экземпляров бина. Имеется
возможность настраивать несколько имплементаций BeanPostProcessor и определить
порядок их выполнения. Данный интерфейс работает с экземплярами бинов, а это означает,
что Spring IoC создаёт экземпляр бина, а затем BeanPostProcessor с ним работает.
ApplicationContext автоматически обнаруживает любые бины, с реализацией
BeanPostProcessor и помечает их как “post-processors” для того, чтобы создать их
определенным способом.
Интерфейс BeanPostProcessor имеет всего два метода: postProcessBeforeInitialization и
postProcessAfterInitialization
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

12. Model, ModelMap, ModelAndView.


Интерфейс Model инкапсулирует (объединяет) данные приложения. ModelMap реализует этот
интерфейс, с возможностью передавать коллекцию значений. Затем он обрабатывает эти
значения, как если бы они были внутри Map. Следует отметить, что в Model и ModelMap мы
можем хранить только данные. Мы помещаем данные и возвращаем имя представления.
С другой стороны, с помощью ModelAndView мы возвращаем сам объект. Мы
устанавливаем всю необходимую информацию, такую как данные и имя представления, в
объекте, который мы возвращаем.
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

Model - это интерфейс,Он определяет держатель для атрибутов модели и в первую


очередь предназначен для добавления атрибутов в модели. в то время как ModelMap - это
класс, реализация Map для использования при построении данных модели для
использования с инструментами пользовательского интерфейса.Поддерживает цепные
вызовы и генерацию атрибута модели имена.
ModelAndView - это просто контейнер для ModelMap и объект представления. Это
позволяет контроллеру возвращать оба как одно значение.

13. Какие паттерны используются в Spring (singleton, prototype,


builder, proxy, chain of responsibility, dependency injection).
Вот некоторые известные паттерны, используемые в Spring Framework:
 Chain of Responsibility - это поведенческий паттерн проектирования, который позволяет
передавать запросы последовательно по цепочке обработчиков. Каждый
последующий обработчик решает, может ли он обработать запрос сам и стоит ли
передавать запрос дальше по цепи. Ему Spring Security
 Singleton (Одиночка) - Паттерн Singleton гарантирует, что в памяти будет существовать
только один экземпляр объекта, который будет предоставлять сервисы. Spring область
видимости бина (scope) по умолчанию равна singleton и IoC-контейнер создаёт ровно
один экземпляр объекта на Spring IoC-контейнер. Spring-контейнер будет хранить этот
единственный экземпляр в кэше синглтон-бинов, и все последующие запросы и ссылки
для этого бина получат кэшированный объект. Рекомендуется использовать область
видимости singleton для бинов без состояния. Область видимости бина можно
определить как singleton или как prototype (создается новый экземпляр при каждом
запросе бина).
 Model View Controller (Модель-Представление-Контроллер) - Преимущество Spring
MVC в том, что ваши контроллеры являются POJO, а не сервлетами. Это облегчает
тестирование контроллеров. Стоит отметить, что от контроллеров требуется только
вернуть логическое имя представления, а выбор представления остаётся за
ViewResolver. Это облегчает повторное использование контроллеров при различных
вариантах представления.
 Front Controller (Контроллер запросов) - Spring предоставляет DispatcherServlet, чтобы
гарантировать, что входящий запрос будет отправлен вашим контроллерам. Паттерн
Front Controller используется для обеспечения централизованного механизма
обработки запросов, так что все запросы обрабатываются одним обработчиком. Этот
обработчик может выполнить аутентификацию, авторизацию, регистрацию или
отслеживание запроса, а затем передать запрос соответствующему контроллеру. View
Helper отделяет статическое содержимое в представлении, такое как JSP, от
обработки бизнес-логики.
 Dependency injection и Inversion of control (IoC) (Внедрение зависимостей и инверсия
управления) - IoC-контейнер в Spring, отвечает за создание объекта, связывание
объектов вместе, конфигурирование объектов и обработку всего их жизненного цикла
от создания до полного уничтожения. В контейнере Spring используется инъекция
зависимостей (Dependency Injection, DI) для управления компонентами приложения.
Эти компоненты называются "Spring-бины" (Spring Beans).
 Service Locator (Локатор служб) - ServiceLocatorFactoryBean сохраняет информацию
обо всех бинах в контексте. Когда клиентский код запрашивает сервис (бин) по имени,
он просто находит этот компонент в контексте и возвращает его. Клиентскому коду не
нужно писать код, связанный со Spring, чтобы найти бин. Паттерн Service Locator
используется, когда мы хотим найти различные сервисы, используя JNDI. Учитывая
высокую стоимость поиска сервисов в JNDI, Service Locator использует кэширование.
При запросе сервиса первый раз Service Locator ищет его в JNDI и кэширует объект.
Дальнейший поиск этого же сервиса через Service Locator выполняется в кэше, что
значительно улучшает производительность приложения.
 Observer-Observable (Наблюдатель) - Используется в механизме событий
ApplicationContext. Определяет зависимость "один-ко-многим" между объектами, чтобы
при изменении состояния одного объекта все его подписчики уведомлялись и
обновлялись автоматически.
 Context Object (Контекстный объект) - Паттерн Context Object, инкапсулирует
системные данные в объекте-контексте для совместного использования другими
частями приложения без привязки приложения к конкретному протоколу.
ApplicationContext является центральным интерфейсом в приложении Spring для
предоставления информации о конфигурации приложения.
 Proxy (Заместитель) - позволяет подставлять вместо реальных объектов специальные
объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту,
позволяя сделать что-то до или после передачи вызова оригиналу.
 Factory (Фабрика) - определяет общий интерфейс для создания объектов в
суперклассе, позволяя подклассам изменять тип создаваемых объектов.
 Template (Шаблон) - Этот паттерн широко используется для работы с повторяющимся
бойлерплейт кодом (таким как, закрытие соединений и т. п.).
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

Singleton: Creating beans with default scope.


Factory: Bean Factory classes
Prototype: Bean scopes
Adapter: Spring Web and Spring MVC
Proxy: Spring Aspect Oriented Programming support
Template Method: JdbcTemplate, HibernateTemplate etc
Front Controller: Spring MVC DispatcherServlet
Data Access Object: Spring DAO support
Dependency Injection and Aspect Oriented Programming
14. Bean scopes.
В Spring предусмотрены различные области времени действия бинов:
 singleton — может быть создан только один экземпляр бина. Этот тип используется
спрингом по умолчанию, если не указано другое. Следует осторожно использовать
публичные свойства класса, т.к. они не будут потокобезопасными.
 prototype — создается новый экземпляр при каждом запросе.
 request — аналогичен prototype, но название служит пояснением к использованию
бина в веб приложении. Создается новый экземпляр при каждом HTTP request.
 session — новый бин создается в контейнере при каждой новой HTTP сессии.
 global-session: используется для создания глобальных бинов на уровне сессии для
Portlet приложений.
[Link]

В Spring 5:
 singleton
 prototype
 request
 session
 application - один экземпляр будет создан и доступен в течение всего жизненного
цикла ServletContext
 websocket - один экземпляр будет создан и доступен в течение всего жизненного цикла
WebSocket
[Link]

15. Spring Data.


Spring Data — дополнительный удобный механизм для взаимодействия с сущностями базы
данных, организации их в репозитории, извлечение данных, изменение, в каких то случаях
для этого будет достаточно объявить интерфейс и метод в нем, без имплементации.
Основное понятие в Spring Data — это репозиторий. Это несколько интерфейсов которые
используют JPA Entity для взаимодействия с ней. Так например интерфейс
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID>
обеспечивает основные операции по поиску, сохранения, удалению данных (CRUD операции),
так же есть PagingAndSortingRepository, JpaRepository.
Создание нативного запроса в SpringData: использование аннотаций @Modifying,
@Transactional, и в @Query пишем наш запрос.

16. Spring Security.


Spring Security предоставляет широкие возможности для защиты приложения. Кроме
стандартных настроек для аутентификации, авторизации и распределения ролей и маппинга
доступных страниц, ссылок и т.п., предоставляет защиту от различных вариантов атак
 SecurityContextHolder, в нем содержится информация о текущем контексте
безопасности приложения, который включает в себя подробную информацию о
пользователе(Principal) работающем в настоящее время с приложением. По
умолчанию SecurityContextHolder используетThreadLocal для хранения такой
информации, что означает, что контекст безопасности всегда доступен для методов
исполняющихся в том же самом потоке. Для того чтобы изменить стратегию хранения
этой информации можно воспользоваться статическим методом класса
[Link](String strategy).
 SecurityContext, содержит объект Authentication и в случае необходимости
информацию системы безопасности, связанную с запросом от пользователя.
 Authentication представляет пользователя (Principal) с точки зрения Spring Security.
 GrantedAuthority отражает разрешения выданные пользователю в масштабе всего
приложения, такие разрешения (как правило называются «роли»), например
ROLE_ANONYMOUS, ROLE_USER, ROLE_ADMIN.
 UserDetails предоставляет необходимую информацию для построения объекта
Authentication из DAO объектов приложения или других источников данных системы
безопасности. Объект UserDetailsсодержит имя пользователя, пароль, флаги:
isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled и
Collection — прав (ролей) пользователя.
 UserDetailsService, используется чтобы создать UserDetails объект путем реализации
единственного метода этого интерфейса
Позволяет получить из источника данных объект пользователя и сформировать из него
объект UserDetails который будет использоваться контекстом Spring Security.
Аннотации:
@EnableGlobalMethodSecurity - включает глобальный метод безопасности.
@EnableWebMvcSecurity - "включает" Spring Security. Не будет работать, если наш класс не
наследует WebSecurityConfigurerAdapter
@Secured используется для указания списка ролей в методе
@PreAuthorize и @PostAuthorize обеспечивают контроль доступа на основе выражений.
@PreAuthorize проверяет данное выражение перед входом в метод, тогда как аннотация
@PostAuthorize проверяет его после выполнения метода и может изменить результат.
@PreFilter для фильтрации аргумента коллекции перед выполнением метода
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B
17. @Recource, @Autowired, @Inject.
Аннотации для внедрения зависимостей.
@Resource (java) пытается получить зависимость: по имени, по типу, затем по
описанию. Имя извлекается из имени аннотируемого сеттера или поля, либо берется из
параметра name.
@Inject (java) или @Autowired (spring) в первую очередь пытается подключить
зависимость по типу, затем по описанию и только потом по имени.
Для @Inject аналог @Named, для @Autowired идет @Qulifier.
[Link]
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring

18. @Conditional.
@Conditional позволяет накладывать определенные условия, выполнив которые, можно
например инициализировать бин. Пример: нам нужен определенные бин только когда
выполняется некое условие. Мы прописываем в этом бине @Conditional, и если оно
выполняется (true) бин создастся. Существует много готовых решений @Conditional.
[Link]

19. @ComponentScan.
Первый шаг для описания Spring Beans это добавление аннотации — @Component, или
@Service, или @Repository.
Однако, Spring ничего не знает об этих бинах, если он не знает где искать их. То, что
скажет Spring где искать эти бины и называется Component Scan. В @ComponentScan вы
указываете пакеты, которые должны сканироваться.
Spring будет искать бины не только в пакетах для сканирования, но и в их подпакетах.
20. Spring Boot.
По сути, Spring Boot это просто набор классов конфигурации, которые создают нужные бины в
контексте. Точно так же их можно создать руками, просто Boot это автоматизирует. При этом
помогая решить проблему конфликтов разных версий компонентов.
Чтобы ускорить процесс управления зависимостями, Spring Boot неявно упаковывает
необходимые сторонние зависимости для каждого типа приложения на основе Spring и
предоставляет их разработчику посредством так называемых starter-пакетов (spring-boot-
starter-web, spring-boot-starter-data-jpa и т.д.)
Starter-пакеты представляют собой набор удобных дескрипторов зависимостей, которые
можно включить в свое приложение. Это позволит получить универсальное решение для
всех, связанных со Spring технологий, избавляя программиста от лишнего поиска примеров
кода и загрузки из них требуемых дескрипторов зависимостей. Например, если вы хотите
начать использовать Spring Data JPA для доступа к базе данных, просто включите в свой
проект зависимость spring-boot-starter-data-jpa и все будет готово (вам не придется искать
совместимые драйверы баз данных и библиотеки Hibernate)
В основе "магии" Spring Boot нет ничего магического, он использует совершенно базовые
понятия из Spring Framework. В кратком виде процесс можно описать так:
 Аннотация @SpringBootApplication включает сканирование компонентов и авто-
конфигурацию через аннотацию @EnableAutoConfiguration
 @EnableAutoConfiguration импортирует класс EnableAutoConfigurationImportSelector
 EnableAutoConfigurationImportSelector загружает список конфигураций из файла META-
INF/[Link]
 Каждая конфигурация пытается сконфигурировать различные аспекты приложения
(web, JPA, AMQP etc), регистрируя нужные бины и используя различные условия
(наличие / отсутствие бина, настройки, класса и т.п.)
 Созданный в итоге AnnotationConfigEmbeddedWebApplicationContext ищет в том же DI
контейнере фабрику для запуска embedded servlet container
 Servlet container запускается, приложение готово к работе!
Важное понятие Spring Boot это автоконфигурация. По сути, это просто набор
конфигурационных классов, которые создают и регистрируют определенные бины в
приложении. По большому счету, даже сам Embedded Servlet Container — это просто еще
один бин, который можно сконфигурировать! Пара важных моментов, которые важно знать об
автоконфигурации:
 Включается аннотацией @EnableAutoConfiguration
 Работает в последнюю очередь, после регистрации пользовательских бинов
 Принимает решения о конфигурации на основании доступных в classpath классов,
свойств в [Link] и т.п.
 Можно включать и выключать разные аспекты автоконфигурации, и применять ее
частично (например, только MySQL + JPA, но не веб)
 Всегда отдает приоритет пользовательским бинам. Если ваш код уже зарегистрировал
бин DataSource — автоконфигурация не будет его перекрывать
Логика при регистрации бинов управляется набором @ConditionalOn* аннотаций. Можно
указать, чтобы бин создавался при наличии класса в classpath (@ConditionalOnClass), наличии
существующего бина (@ConditionalOnBean), отсуствии бина (@ConditionalOnMissingBean) и
т.п.
Отключить ненужные автоконфигурации можно при помощи свойств exclude и
excludeName аннотаций @EnableAutoConfiguration, @ImportAutoConfiguration и
@SpringBootApplication. Или в property задать SpringAutoconfiguration exclude и передать
имена классов.
Можно отказаться от использования механизма автоконфигурации, вместо этого
указывая необходимые автоконфигурации вручную. Для этого надо избавиться от аннотаций
@SpringBootApplication и @EnableAutoConfiguration в коде вашего проекта, а для указания
нужных конфигурационных классов использовать аннотации @SpringBootConfiguration и
@ImportAutoConfiguration. Однако стоит помнить, что используемые автоконфигурации всё
ещё могут содержать неиспользуемые компоненты.
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B
21. Нововведение Spring 5.
 Используется JDK 8+ (Optional, CompletableFuture, Time API, [Link], default
methods)
 Поддержка Java 9 (Automatic-Module-Name in 5.0, module-info in 6.0+, ASM 6)
 Поддержка HTTP/2 (TLS, Push), NIO/NIO.2, Kotlin
 Поддержка Kotlin
 Реактивность (Web on Reactive Stack)
 Null-safety аннотации(@Nullable), новая документация
 Совместимость с Java EE 8 (Servlet 4.0, Bean Validation 2.0, JPA 2.2, JSON Binding API
1.0)
 Поддержка JUnit 5 + Testing Improvements (conditional and concurrent)
 Удалена поддержка: Portlet, Velocity, JasperReports, XMLBeans, JDO, Guava
[Link]

22. Spring Cloud (Data Flow).


Это инструменты для создания сложных топологий для потоковой и пакетной передачи
данных.
[Link]

23. Spring Integration.


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

24. Spring Batch.


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

25. Spring Hateoas


(произношение - хейтоас).
Spring HATEOAS предоставляет некоторые API для упрощения создания REST-
представлений, которые следуют принципу HATEOAS при работе с Spring и особенно Spring
MVC. Основной проблемой, которую он пытается решить, является создание ссылки и сборка
представления.
[Link]

26. Spring Rest Docs.


Spring REST Docs поможет вам документировать сервисы RESTful.
Он сочетает в себе рукописную документацию, написанную с помощью Asciidoctor, и
автоматически генерируемые фрагменты, созданные с помощью Spring MVC Test . Такой
подход освобождает вас от ограничений документации, создаваемой такими инструментами,
как Swagger .
Это помогает вам создавать документацию, которая является точной, краткой и хорошо
структурированной. Затем эта документация позволяет вашим пользователям получать
необходимую информацию с минимальными усилиями.
[Link]

27. Spring AMQP.


Проект Spring AMQP применяет основные концепции Spring для разработки решений для
обмена сообщениями на основе AMQP. Он предоставляет «шаблон» как абстракцию высокого
уровня для отправки и получения сообщений. Он также обеспечивает поддержку
управляемых сообщениями POJO с «контейнером слушателя». Эти библиотеки облегчают
управление ресурсами AMQP, способствуя использованию внедрения зависимостей и
декларативной конфигурации. Во всех этих случаях вы увидите сходство с поддержкой JMS в
Spring Framework.
Проект состоит из двух частей; spring-amqp - это базовая абстракция, а spring-rabbit - это
реализация RabbitMQ.
[Link]

28. Spring Web Flow.


Spring Web Flow основан на Spring MVC и позволяет реализовать «потоки» веб-приложения.
Поток заключает в себе последовательность шагов, которые направляют пользователя при
выполнении какой-либо бизнес-задачи. Он охватывает несколько HTTP-запросов, имеет
состояние, обрабатывает транзакционные данные, может использоваться повторно и может
быть динамичным и долгосрочным по своей природе.
[Link]

29. Spring Kafka.


Проект Spring for Apache Kafka (spring-kafka) применяет основные концепции Spring для
разработки решений для обмена сообщениями на основе Kafka. Он предоставляет «шаблон»
в качестве высокоуровневой абстракции для отправки сообщений. Он также обеспечивает
поддержку управляемых сообщениями POJO с @KafkaListener аннотациями и «контейнером
слушателя». Эти библиотеки способствуют использованию инъекций зависимостей и
декларативных. Во всех этих случаях вы увидите сходство с поддержкой JMS в Spring
Framework и поддержкой RabbitMQ в Spring AMQP.
[Link]

30. etc. Spring projects.

Ответы на доп.вопросы
1. Контейнеры спринга
Container создаёт объекты, связывает их вместе, настраивает и управляет ими от
создания до момента уничтожения. Spring Container получает инструкции какие объекты
инстанцировать и как их конфигурировать через метаданные: XML, Аннотации или Java код.
Spring BeanFactory Container - это самый простой контейнер, который обеспечивает
базовую поддержку DI и который основан на интерфейсе
[Link]. Такие интерфейсы, как BeanFactoryAware и
DisposableBean всё ещё присутствуют в Spring для обеспечения обратной совместимости.
Бины создаются при вызове метода getBean().
Наиболее часто используемая реализация интерфейса BeanFactory – XmlBeanFactory.
XmlBeanFactory получает метаданные из конфигурационного XML файла и использует его для
создания настроенного приложения или системы. BeanFactory обычно используется тогда,
когда ресурсы ограничены (мобильные устройства). Поэтому, если ресурсы не сильно
ограничены, то лучше использовать ApplicationContext.
Spring ApplicationContext Container. ApplicationContext является более сложным и
более продвинутым Spring Container-ом. Наследует BeanFactory и так же загружает бины,
связывает их вместе и конфигурирует их определённым образом. Но кроме этого,
ApplicationContext обладает дополнительной функциональностью: общий механизм работы с
ресурсами, распознавание текстовых сообщений из файлов настройки и отображение
событий, которые происходят в приложении различными способами. Этот контейнер
определяется интерфейсом [Link].
Бины создаются при "поднятии" контекста все сразу. Если не указана стратегия
инициализации.
Чаще всего используются следующие реализации AppicationContext:
 FileSystemXmlApplicationContext - Загружает данные о бине из XML файла. При
использовании этой реализации в конструкторе необходимо указать полный адрес
конфигурационного файла.
 ClassPathXmlApplicationContext - Этот контейнер также получает данные о бине из XML
файла. Но в отличие от FileSystemApplicationContext, в этом случае необходимо
указать относительный адрес конфигурационного файла (CLASSPATH).
 AnnotationConfigApplicationContext — метаданные конфигурируются с помощью
аннотаций прямо на классах.
 GenericGroovyApplicationContext - эта конфигурация работает по сути так же, как и Xml,
только с Groovy-файлами. К тому же, GroovyApplicationContext нормально работает и с
Xml-файлом. Принимает на вход строку с конфигурацией контекста. Чтением контекста
в данном случае занимается класс GroovyBeanDefinitionReader.
Groovy — объектно-ориентированный язык программирования разработанный для
платформы Java как альтернатива языку Java с возможностями Python, Ruby и Smalltalk.
Groovy использует Java-подобный синтаксис с динамической компиляцией в JVM байткод и
напрямую работает с другим Java кодом и библиотеками. Язык может использоваться в
любом Java проекте или как скриптовый язык.
При этом мы можем указать несколько файлов конфигурации Spring.
[Link]

По своей сути IoC, а, следовательно, и DI, направлены на то, чтобы предложить простой
механизм для предоставления зависимостей компонента (часто называемых
коллабораторами объекта) и управления этими зависимостями на протяжении всего их
жизненного цикла. Компонент, который требует определенных зависимостей, зачастую
называют зависимым объектом или, в случае IoC, целевым объектом. Вполне уместно сейчас
заявить, что IoC предоставляет службы, через которые компоненты могут получать доступ к
своим зависимостям, и службы для взаимодействия с зависимостями в течение их времени
жизни. В общем случае IoC может быть расщеплена на два подтипа: инверсия управления
(Dependency Injection) и поиск зависимости (Dependency Lookup). Инверсия управления — это
крупная часть того, делает Spring, и ядро реализации Spring основано на инверсии
управления, хотя также предоставляются и средства Dependency Lookup. Когда платформа
Spring предоставляет коллабораторы зависимому объекту автоматически, она делает это с
использованием инверсии управления (Dependency Injection). В приложении, основанном на
Spring, всегда предпочтительнее применять Dependency Injection для передачи
коллабораторов зависимым объектам вместо того, чтобы заставлять зависимые объекты
получать коллабораторы через поиск.

2. Части спринга, модули


Acce
Контейнер Core Container (основной) включает в себя Beans, Core, Context и SpEL (expression
language).
 Beans отвечает за BeanFactory которая является сложной реализацией паттерна
Фабрика (GoF).
 Модуль Core обеспечивает ключевые части фреймворка, включая свойства IoC и DI.
 Context построен на основе Beans и Core и позволяет получить доступ к любому
объекту, который определён в настройках. Ключевым элементом модуля Context
является интерфейс ApplicationContext.
 Модуль SpEL обеспечивает мощный язык выражений для манипулирования
объектами во время исполнения.
Контейнер Data Access/Integration состоит из JDBC, ORM, OXM, JMS и модуля Transactions.
 JDBC обеспечивает абстрактный слой JDBC и избавляет разработчика от
необходимости вручную прописывать монотонный код, связанный с соединением с БД.
 ORM обеспечивает интеграцию с такими популярными ORM, как Hibernate, JDO, JPA и
т.д.
 Модуль OXM отвечает за связь Объект/XML – XMLBeans, JAXB и т.д.
 Модуль JMS (Java Messaging Service) отвечает за создание, передачу и получение
сообщений.
 Transactions поддерживает управление транзакциями для классов, которые реализуют
определенные методы.
Контейнер Web. Этот слой состоит из Web, Web-MVC, Web-Socket, Web-Portlet
 Модуль Web обеспечивает такие функции, как загрузка файлов и т.д.
 Web-MVC содержит реализацию Spring MVC для веб-приложений.
 Web-Socket обеспечивает поддержку связи между клиентом и сервером, используя
Web-Socket-ы в веб-приложениях.
 Web-Portlet обеспечивает реализацию MVC с среде портлетов.
Spring также включает в себя ряд других важных модулей, таких как AOP, Aspects,
Instrumentation, Messaging и Test
 AOP реализует аспекто-ориентированное программирование и позволяет
использовать весь арсенал возможностей АОП.
 Модуль Aspects обеспечивает интеграцию с AspectJ, которая также является мощным
фреймворком АОП.
 Instrumentation отвечает за поддержку class instrumentation и classloader, которые
используются в серверных приложениях.
 Модуль Messaging обеспечивает поддержку STOMP.
 И наконец, модуль Test обеспечивает тестирование с использованием TestNG или
JUnit Framework.
[Link]
Inversion of Control - контейнер: конфигурирование компонентов приложений и
управление жизненным циклом Java-объектов.
Фреймворк аспектно-ориентированного программирования: работает с
функциональностью, которая не может быть реализована возможностями объектно-
ориентированного программирования на Java без потерь.
Фреймворк доступа к данным: работает с системами управления реляционными
базами данных на Java-платформе, используя JDBC- и ORM-средства и обеспечивая
решения задач, которые повторяются в большом числе Java-based environments.
Фреймворк управления транзакциями: координация различных API управления
транзакциями и инструментарий настраиваемого управления транзакциями для объектов
Java.
Фреймворк MVC: каркас, основанный на HTTP и сервлетах, предоставляющий
множество возможностей для расширения и настройки (customization).
Фреймворк удалённого доступа: конфигурируемая передача Java-объектов через
сеть в стиле RPC, поддерживающая RMI, CORBA,HTTP-based протоколы, включая web-
сервисы (SOAP).
Фреймворк аутентификации и авторизации: конфигурируемый инструментарий
процессов аутентификации и авторизации, поддерживающий много популярных и ставших
индустриальными стандартами протоколов, инструментов, практик через дочерний проект
Spring Security (ранее известный как Acegi).
Фреймворк удалённого управления: конфигурируемое представление и управление
Java-объектами для локальной или удалённой конфигурации с помощью JMX.
Фреймворк работы с сообщениями: конфигурируемая регистрация объектов-
слушателей сообщений для прозрачной обработки сообщений из очереди сообщений с
помощью JMS, улучшенная отправка сообщений по стандарту JMS API.
Тестирование: каркас, поддерживающий классы для написания модульных и
интеграционных тестов.аоыусгкшен

3. АОР
[Link]
Аспектно-ориентированное программирование (АОП) — это парадигма программирования,
целью которой является повышение модульности за счет разделения междисциплинарных
задач. Это достигается путем добавления дополнительного поведения к существующему коду
без изменения самого кода.
ООП, AOP и Spring - взаимодополняющие технологии, которые позволяют решать
сложные проблемы путем разделения функционала на отдельные модули. АОП
предоставляет возможность реализации сквозной логики - т.е. логики, которая применяется
к множеству частей приложения - в одном месте и обеспечения автоматического применения
этой логики по всему приложению. Подход Spring к АОП заключается в создании
"динамических прокси" для целевых объектов и "привязывании" объектов к
конфигурированному совету для выполнения сквозной логики.
Аспект (Aspect) - Это модуль, который имеет набор программных интерфейсов,
которые обеспечивают сквозные требования. К примеру, модуль логирования будет вызывать
АОП аспект для логирования. В зависимости от требований, приложение может иметь любое
количество аспектов.
Объединённая точка (Join point) - Это такая точка в приложении, где мы можем
подключить аспект. Другими словами, это место, где начинаются определённые действия
модуля АОП в Spring.
Совет (Advice) - Это фактическое действие, которое должно быть предпринято до
и/или после выполнения метода. Это конкретный код, который вызывается во время
выполнения программы.
 before - Запускает совет перед выполнением метода.
 after - Запускает совет после выполнения метода, независимо от результата его
работы (кроме случая остановки работы JVM).
 after-returning - Запускает совет после выполнения метода, только в случае его
успешного выполнения.
 after-throwing - Запускает совет после выполнения метода, только в случае, когда этот
метод “бросает” исключение.
 around - Запускает совет до и после выполнения метода.
Срез точек (Pointcut) - Срезом называется несколько объединённых точек (join points),
в котором должен быть выполнен совет.
Введение (Introduction) - Это сущность, которая помогает нам добавлять новые
атрибуты и/или методы в уже существующие классы.
Целевой объект (Target object) - Это объект на который направлены один или
несколько аспектов.
Плетение (Weaving) - Это процесс связывания аспектов с другими объектами
приложения для создания совета. Может быть вызван во время компиляции, загрузки или
выполнения приложения.
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring
[Link]

С помощью АОП мы можем прописать, например, что будет выполняться до или после
какого-то действия. Прописываем это один раз и этот функционал будет работать везде.
Например нам нужно сделать логирование во всех методах @Service, с ООП нам бы
пришлось прописывать этот функционал в каждом методе для всех @Service. А с АОП мы
можем в конфигах прописать для @Service что будет происходить с каждым вызовом его
методов, - в нашем случае писать логи. Элементы АОП такие как аспекты также используются
в транзакциях спринга.

4. Spring Framework
Spring Framework (или коротко Spring) — универсальный фреймворк с открытым исходным
кодом для Java-платформы. Spring можно использовать для построения любого приложения
на языке Java (т.е. автономных, веб приложений, приложений JEE и т.д.). Характеристика
“облегченная” в действительности не имеет никакого отношения к количеству классов или
размеру дистрибутива; напротив, она определяет принцип всей философии Spring —
минимальное воздействие. Платформа Spring является облегченной в том смысле, что для
использования ядра Spring вы должны вносить минимальные (если вообще какие-либо)
изменения в код своего приложения, а если в какой-то момент вы решите больше не
пользоваться ядром Spring, то и это сделать очень просто.

5. BeanFactory и ApplicationContext разница


Разница между (BeanFactory) и контейнером Advanced J2EE (ApplicationContext) заключается
в следующем:
BeanFactory создаст объекты для bean-компонентов (то есть для классов POJO), упомянутых
в файле [Link] (<bean></bean>) только когда вы вызываете метод .getBean (), но
ApplicationContext создает объекты для всех bean-компонентов (<bean></bean> если его
область явно не указана как «Prototype»), настроенная в [Link] при загрузке самого
жизненфайла [Link].
BeanFactory: (Ленивый контейнер, потому что он создает объекты для bean-
компонентов только при явном вызове из пользовательского / основного класса)
ApplicationContext: (нетерпеливый контейнер из-за создания объектов всех
одноэлементных компонентов при загрузке самого файла [Link])
Технически рекомендуется использовать ApplicationContext, поскольку в приложениях
реального времени объекты bean-объектов будут создаваться во время запуска приложения
на самом сервере. Это сокращает время ответа на запрос пользователя, поскольку объекты
уже доступны для ответа.

6. Работа спринга с ДАО


Spring DAO предоставляет возможность работы с доступом к данным с помощью технологий
вроде JDBC, Hibernate в удобном виде. Существуют специальные классы: JdbcDaoSupport,
HibernateDaoSupport, JdoDaoSupport, JpaDaoSupport.
В Spring DAO поддерживается иерархия исключений, что помогает не обрабатывать
некоторые исключения

7. Что такое контроллер


Ключевым интерфейсом в Spring MVC является Controller. Контроллер обрабатывает
запросы к действиям, осуществляемые пользователями в пользовательском интерфейсе,
взаимодействуя с уровнем обслуживания, обновляя модель и направляя пользователей на
соответствующие представления в зависимости от результатов выполнения. Controller —
управление, связь между моделью и видом.
Основным контроллером в Spring MVC является
[Link]. Задается аннотацией @Controller и часто
используется с аннотацией @RequestMapping, которая указывает какие запросы будут
обрабатываться этим контроллером.

8. View-resolver
ViewResolver — распознаватель представлений. Интерфейс ViewResolver в Spring
MVC (из пакета [Link]) поддерживает распознавание представлений
на основе логического имени, возвращаемого контроллером. Для поддержки различных
механизмов распознавания представлений предусмотрено множество классов реализации.
Например, класс UrlBasedViewResolver поддерживает прямое преобразование логических
имен в URL. Класс ContentNegotiatingViewResolver поддерживает динамическое
распознавание представлений в зависимости от типа медиа, поддерживаемого клиентом
(XML, PDF, JSON и т.д.). Существует также несколько реализаций для интеграции с
различными технологиями представлений, такими как FreeMarker (FreeMarkerViewResolver),
Velocity (VelocityViewResolver) и JasperReports (JasperReportsViewResolver).
InternalResourceViewResolver — реализация ViewResolver, которая позволяет находить
представления, которые возвращает контроллер для последующего перехода к нему. Ищет
по заданному пути, префиксу, суффиксу и имени.

9. [Link] разница [Link]


Метод addAttribute отделяет нас от работы с базовой структурой hashmap. По сути addAttribute
это обертка над put, где делается дополнительная проверка на null. Метод addAttribute в
отличии от put возвращает modelmap.
[Link](“attribute1”,”value1”).addAttribute(“attribute2”,”value2”);

10. Form Binding


Нам это может понадобиться, если мы, например, захотим взять некоторое значение с HTML
страницы и сохранить его в БД. Для этого нам надо это значение переместить в контроллер
Спринга.

11. Как сделать локализацию в приложении


Spring MVC предоставляет очень простую и удобную возможность локализации
приложения. Для этого необходимо сделать следующее:
Создать файл resource bundle, в котором будут заданы различные варианты
локализированной информации.
Определить messageSource в конфигурации Spring используя классы
ResourceBundleMessageSource или ReloadableResourceBundleMessageSource.
Определить localeResolver класса CookieLocaleResolver для включения возможности
переключения локали.
С помощью элемента spring:message DispatcherServlet будет определять в каком месте
необходимо подставлять локализированное сообщение в ответе.
12. Spring MVC interceptor
Перехватчики в Spring (Spring Interceptor) являются аналогом Servlet Filter и позволяют
перехватывать запросы клиента и обрабатывать их. Перехватить запрос клиента можно в
трех местах: preHandle, postHandle и afterCompletion.
preHandle — метод используется для обработки запросов, которые еще не были
переданы в метода обработчик контроллера. Должен вернуть true для передачи следующему
перехватчику или в handler method. False укажет на обработку запроса самим обработчиком и
отсутствию необходимости передавать его дальше. Метод имеет возможность выкидывать
исключения и пересылать ошибки к представлению.
postHandle — вызывается после handler method, но до обработки DispatcherServlet для
передачи представлению. Может использоваться для добавления параметров в объект
ModelAndView.
afterCompletion — вызывается после отрисовки представления.
Для создания обработчика необходимо расширить абстрактный класс
HandlerInterceptorAdapter или реализовать интерфейс HandlerInterceptor. Также нужно указать
перехватчики в конфигурационном файле Spring.

13. parent in [Link]


В [Link] дочерних проектов необходимо ввести секцию <parent> и определить GAV-
параметры родительского проекта.

14. @Sheduler
Шедулер — управляет таймерами запуска задач - планировщик задач.
Джоб — конкретная задача, запускаемая по таймеру
Триггер — условие выполнения задачи — задает временные рамки запуска задач и их
выполнения
pauseJob(String name, String Group) — остановить выполнение задачи шедулера в
указанной группе джобов. Остановка происходит путём остановки соответствующего триггера
(см. pauseTrigger)
resumeJob(String name, String Group) — возобновить выполнение задачи шедулера в
указанной группе джобов. Восстановление происходит путем запуска соответствующего
триггера (см. resumeTrigger)
pauseTrigger(String name, String Group) — останавливает триггер в соответствующей
группе
resumeTrigger(String name, String Group) — возобновляет работу триггера в
соответствующей группе
pauseAll — останавливает все задачи шедулера (pauseJobs(String group) — только у
конкретной группы)
resumeAll — возобновляет запуск всех задач шедулера (см. также resumeJobs)

Можно запланировать удаление пользователей из БД, например в каждую среду. Так же


может использоваться для логирования, пишем логи по расписанию.
Планировщик задач. Включается @EnableSheduller в конфигах.

15. Внедрение в синглтон prototype


@lookup аннотация позволяет создавать прототип бины через синглтон
через application context
Через прокси”

16. @Profile
Профили - это основная особенность фреймворка, позволяющая нам отображать
наши bean-компоненты на разные профили, например, dev, test, prod.
Рассмотрим базовый сценарий – у нас есть бин, который должен быть активен только во
время разработки, но не развернут в продакшене. Мы аннотируем этот бин профилем ”dev", и
он будет присутствовать только в контейнере во время разработки – в продакшене dev просто
не будет активен.
[Link]

17. Транзакция с аннотацией @Transactional вызывает метод без


аннотации
Обработано не будет из-за прокси

18. @Around
Запускает совет до и после выполнения метода.
[Link]

19. SPELL
Spring Expression Language (SpEL) - это мощный язык выражений, который
поддерживает запросы и манипулирование графом объектов во время выполнения. Он может
использоваться с конфигурациями Spring на основе XML или аннотаций.
[Link]

20. Container - как устроен (Map)

21. FileSystemApplicationContext
FileSystemXmlApplicationContext может получить доступ ко всей вашей файловой
системе, например c:/config/[Link].
[Link]

22. Bean Definition


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

23. Статические методы в бинах


[Link] (начало)

24. ContextLoaderListener
ContextLoaderListener — это тот экземпляр, который загружает ваш
WebApplicationContext, который по умолчанию использует класс XmlWebApplicationContext. А
он в свою очередь разбирает настройки для корректной работы Spring.
[Link]
[Link]

25. HandlerException
Обработка исключений до Spring 3.2
Вы можете добавить дополнительные (@ExceptionHandler) методы к любому
контроллеру для специальной обработки исключений, вызванных методами обработки
запросов (@RequestMapping) в том же контроллере. Такие методы могут:
1. Обрабатывать исключения без @ResponseStatus аннотации (обычно
предопределенные исключения, которые вы не написали)
2. Перенаправить пользователя в специальный просмотр ошибок
3. Создайте полностью индивидуальный ответ об ошибке
[Link]

26. @ControllerAdvice
Обработка исключений до Spring 3.2
Классы, помеченные как @ControllerAdvice могут быть явно объявлены как бины Spring
или автоматически обнаружены посредством сканирования пути к классам. Все такие bean-
компоненты сортируются на основе Ordered семантики или @Order/ @Priority объявлений,
причем Ordered семантика имеет приоритет над @Order/ @Priority объявлениями.
@ControllerAdvice бины затем применяются в этом порядке во время выполнения. Однако
обратите внимание, @ControllerAdvice что реализующим PriorityOrdered компонентам не
предоставляется приоритет над @ControllerAdvice реализуемыми компонентами Ordered.
Кроме того, Ordered не учитывается для @ControllerAdvice bean - объектов с ограниченным
диапазоном - например, если такой bean-компонент был сконфигурирован как bean-объект с
областью запроса или сессионный объем. Для обработки исключений,@ExceptionHandler
будет выбран по первому совету с подходящим методом обработчика исключений. Для
атрибутов модели и инициализации привязки данных, @ModelAttribute а также @InitBinder
методы будут следовать @ControllerAdvice порядку.
[Link]
annotation/[Link]

27. @ModelAttribute над методом

28. Реактивное программирование и 4 принципа


[Link]

Реактивное программирование — это программирование в многопоточной среде.


Реактивный подход повышает уровень абстракции вашего кода и вы можете
сконцентрироваться на взаимосвязи событий, которые определяют бизнес-логику, вместо
того, чтобы постоянно поддерживать код с большим количеством деталей реализации. Код в
реактивном программировании, вероятно, будет короче.
Поток — это последовательность, состоящая из постоянных событий,
отсортированных по времени. В нем может быть три типа сообщений: значения (данные
некоторого типа), ошибки и сигнал о завершении работы. Рассмотрим то, что сигнал о
завершении имеет место для экземпляра объекта во время нажатия кнопки закрытия.
Мы получаем эти cгенерированные события асинхронно, всегда. Согласно идеологии
реактивного программирования существуют три вида функций: те, которые должны
выполняться, когда некоторые конкретные данные будут отправлены, функции обработки
ошибок и другие функции с сигналами о завершении работы программы. Иногда последнее
два пункта можно опустить и сосредоточится на определении функций для обработки
значений. Слушать(listening) поток означает подписаться(subscribing) на него. То есть
функции, которые мы определили это наблюдатели(observers). А поток является субъектом
который наблюдают.
Критерии реактивного приложения:
Responsive. Разрабатываемая система должна отвечать быстро и за определенное
заранее заданное время. Кроме того система должна быть достаточно гибкой для
самодиагностики и починки.
Что это значит на практике? Традиционно при запросе некоторого сервиса мы идем в базу
данных, вынимаем необходимый объем информации и отдаем ее пользователю. Здесь все
хорошо, если наша система достаточно быстрая и база данных не очень большая. Но что,
если время формирования ответа гораздо больше ожидаемого? Кроме того, у пользователя
мог пропасть интернет на несколько миллисекунд. Тогда все усилия по выборке данных и
формированию ответа пропадают. Вспомните gmail или facebook. Когда у вас плохой
интернет, вы не получаете ошибку, а просто ждете результат больше обычного. Кроме того,
этот пункт говорит нам о том, что ответы и запросы должны быть упорядочены и
последовательны.
Resilient. Система остается в рабочем состоянии даже, если один из компонентов
отказал.
Другими словами, компоненты нашей системы должны быть достаточно гибкими и
изолированными друг от друга. Достигается это путем репликаций. Если, например, одна
реплика PostgreSQL отказала, необходимо сделать так, чтобы всегда была доступна другая.
Кроме того, наше приложение должно работать во множестве экземпляров.
Elastic. Система должна занимать оптимальное количество ресурсов в каждый
промежуток времени. Если у нас высокая нагрузка, то необходимо увеличить количество
экземпляров приложения. В случае малой нагрузки ресурсы свободных машин должны быть
очищены. Типичный инструменты реализации данного принципа: Kubernetes.
Message Driven. Общение между сервисами должно происходить через асинхронные
сообщения. Это значит, что каждый элемент системы запрашивает информацию из другого
элемента, но не ожидает получение результата сразу же. Вместо этого он продолжает
выполнять свои задачи. Это позволяет увеличить пользу от системных ресурсов и управлять
более гибко возникающими ошибками. Обычно такой результат достигается через реактивное
программирование.
Spring 5 WebFlux - поддерживает стек реактивного программирования.
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring
[Link]

29. WebSocket
WebSocket обеспечивает двустороннюю связь между клиентом и сервером, используя одно
TCP соединение.
[Link]

30. @Lookup
Используется для внедрения prototype bean в singleton bean.
ПРИМЕР - Обычно бины в приложении Spring являтся синглтонами, и для внедрения
зависимостей мы используем конструктор или сеттер. Но бывает и другая ситуация: имеется
бин Car – синглтон (singleton bean), и ему требуется каждый раз новый экземпляр бина
Passenger. То есть Car – синглтон, а Passenger – так называемый прототипный бин (prototype
bean). Жизненные циклы бинов разные. Бин Car создается контейнером только раз, а бин
Passenger создается каждый раз новый – допустим, это происходит каждый раз при вызове
какого-то метода бина Car.Вот здесь то и пригодится внедрение бина с помощью Lookup
метода. Оно происходит не при инициализации контейнера, а позднее: каждый раз, когда
вызывается метод.
Суть в том, что вы создаете метод-заглушку в бине Car и помечаете его специальным
образом – аннотацией @Lookup. Этот метод должен возвращать бин Passenger, каждый раз
новый. Контейнер Spring под капотом создаст подкласс и переопределит этот метод и будет
вам выдавать новый экземпляр бина Passenger при каждом вызове аннотированного метода.
Даже если в вашей заглушке он возвращает null (а так и надо делать, все равно этот метод
будет переопределен).
[Link]
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring

31. @Target и @Retention


@Retention - указываем, в какой момент жизни программного кода будет доступна аннотация
 SOURCE - аннотация доступна только в исходном коде и сбрасывается во время
создания .class файла;
 CLASS - аннотация хранится в .class файле, но недоступна во время выполнения
программы;
 RUNTIME - аннотация хранится в .class файле и доступна во время выполнения
программы.
@Target - указывается, какой элемент программы будет использоваться аннотацией
 PACKAGE - назначением является целый пакет (package);
 TYPE - класс, интерфейс, enum или другая аннотация:
 METHOD - метод класса, но не конструктор (для конструкторов есть отдельный тип
CONSTRUCTOR);
 PARAMETER - параметр метода;
 CONSTRUCTOR - конструктор;
 FIELD - поля-свойства класса;
 LOCAL_VARIABLE - локальная переменная (обратите внимание, что аннотация не
может быть прочитана во время выполнения программы, то есть, данный тип
аннотации может использоваться только на уровне компиляции как, например,
аннотация @SuppressWarnings);
 ANNOTATION_TYPE - другая аннотация.
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring

32. Bean
Бин (bean) — это не что иное, как самый обычный объект. Разница лишь в том, что
бинами принято называть те объекты, которые управляются Spring-ом и живут внутри его DI-
контейнера.
По умолчанию бин задается как синглтон в Spring. Таким образом все публичные
переменные класса могут быть изменены одновременно из разных мест, а значит бин - не
потокобезопасен. Однако поменяв область действия бина на request, prototype, session он
станет потокобезопасным, но это скажется на производительности.
Конфигурационный файл спринг определяет все бины, которые будут инициализированы в
Spring Context. При создании экземпляра Spring ApplicationContext будет прочитан
конфигурационный xml файл и выполнены указанные в нем необходимые инициализации.
Отдельно от базовой конфигурации, в файле могут содержаться описание перехватчиков
(interceptors), view resolvers, настройки локализации и др.
Определение бина содержит метаданные конфигурации, которые необходимы
управляющему контейнеру для получения следующей информации: как создать бин,
информацию о жизненном цикле бина, зависимости бина.
В Spring Framework существуют такие свойства, определяющие бины:
 class - Этот атрибут является обязательным и указывает конкретный класс Java-
приложения, который будет использоваться для создания бина.
 name - Уникальный идентификатор бина. В случае конфигурации с помощью xml-
файла, вы можете использовать свойство “id” и/или “name” для идентификации бина.
 scope - Это свойство определяет область видимости создаваемых объектов. singleton -
Определяет один единственный бин для каждого контейнера Spring IoC (используется
по умолчанию); prototype - контейнер Spring IoC создаёт новый экземпляр бина на
каждый полученный запрос т.е. иметь любое количество экземпляров бина; request -
Создается один экземпляр бина на каждый HTTP запрос. Касается исключительно
ApplicationContext; session - Создаётся один экземпляр бина на каждую HTTP сессию.
Касается исключительно ApplicationContext; websocket - Создаётся один экземпляр
бина для определенного сокета. application - Создается один экземпляр бина для
жизненного цикла бина. Похоже на синглтон, но когда бины ограничены областью
приложения, значения, однажды установленное в applicationScopedBean, будет
сохранено для всех последующих запросов, сеансов и даже для другого приложения
сервлета, которое будет обращаться к этому бину, при условии, что оно выполняется в
том же ServletContext. В то время как одноэлементные бины ограничены только одним
контекстом приложения.
 constructor-arg - определяет конструктор, использующийся для внедрения
зависимости.
 properties - Определяет свойства внедрения зависимости.
 initialization method - Здесь определяется метод инициализации бина
 destruction method - Метод уничтожения бина, который будет использоваться при
уничтожении контейнера, содержащего бин.
 autowiring mode - Определяет режим автоматического связывания при внедрении
зависимости.
 lazy-initialization mode - Режим ленивой инициализации даёт контейнеру IoC команду
создавать экземпляр бина при первом запросе, а не при запуске приложения.
[Link]
%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring

33. Прокси
Spring AOP использует динамические прокси JDK или CGLIB для создания прокси для
данного целевого объекта. (Динамические прокси JDK предпочтительны, когда у вас есть
выбор).
Если целевой объект для прокси реализует по крайней мере один интерфейс, то будет
использоваться динамический прокси JDK. Все интерфейсы, реализованные целевым типом,
будут проксированы. Если целевой объект не реализует никаких интерфейсов, будет создан
прокси-сервер CGLIB.
Если вы хотите принудительно использовать прокси-сервер CGLIB (например, прокси-
сервер для каждого метода, определенного для целевого объекта, а не только для тех,
которые реализованы его интерфейсами), вы можете сделать это. Тем не менее, есть
несколько вопросов для рассмотрения:
 final нельзя рекомендовать методы, так как они не могут быть переопределены.
 Вам понадобятся двоичные файлы CGLIB 2 на вашем пути к классам, в то время как
динамические прокси доступны с JDK. Spring автоматически предупредит вас, когда
ему нужен CGLIB, а классы библиотеки CGLIB не найдены в пути к классам.
 Конструктор вашего прокси-объекта будет вызываться дважды. Это естественное
следствие прокси-модели CGLIB, согласно которой для каждого прокси-объекта
создается подкласс. Для каждого экземпляра с прокси создаются два объекта:
фактический объект с прокси и экземпляр подкласса, который реализует
рекомендацию. Такое поведение не проявляется при использовании прокси-серверов
JDK. Обычно, вызов конструктора прокси-типа дважды не является проблемой, так как
обычно выполняются только присваивания, и в конструкторе не реализована реальная
логика.
[Link]

34. Spring MVC

Spring имеет собственную MVC-платформу веб-приложений, которая не была


первоначально запланирована. Spring MVC является фреймворком, ориентированным на
запросы. В нем определены стратегические интерфейсы для всех функций современной
запросно-ориентированной системы. Цель каждого интерфейса — быть простым и ясным,
чтобы пользователям было легко его заново имплементировать, если они того пожелают.
MVC прокладывает путь к более чистому front-end-коду. Все интерфейсы тесно связаны с
Servlet API. Эта связь рассматривается некоторыми как неспособность разработчиков Spring
предложить для веб-приложений абстракцию более высокого уровня. Однако эта связь
оставляет особенности Servlet API доступными для разработчиков, облегчая все же работу с
ним. Наиболее важные интерфейсы, определенные Spring MVC, перечислены ниже:
HandlerMapping: выбор класса и его метода, которые должны обработать данный
входящий запрос на основе любого внутреннего или внешнего для этого запроса атрибута
или состояния.
HandlerAdapter: вызов и выполнение выбранного метода обработки входящего
запроса.
Controller: включен между Моделью (Model) и Представлением (View). Управляет
процессом преобразования входящих запросов в адекватные ответы. Действует как ворота,
направляющие всю поступающую информацию. Переключает поток информации из модели в
представление и обратно.
Класс DispatcherServlet является главным контроллером, которые получает запросы и
распределяет их между другими контроллерами. @RequestsMapping указывает, какие именно
запросы будут обрабатываться в конкретном контроллере. Может быть несколько
экземпляров DispatcherServlet, отвечающих за разные задачи (обработка запросов
пользовательского интерфейса, REST служб и т.д.). Каждый экземпляр DispatcherServlet
имеет собственную конфигурацию WebApplicationContext, которая определяет характеристики
уровня сервлета, такие как контроллеры, поддерживающие сервлет, отображение
обработчиков, распознавание представлений, интернационализация, оформление темами,
проверка достоверности, преобразование типов и форматирование и т.п.
ContextLoaderListener - слушатель при старте и завершении корневого класса Spring
WebApplicationContext. Основным назначением является связывание жизненного цикла
ApplicationContext и ServletContext, а также автоматического создания ApplicationContext.
Можно использовать этот класс для доступа к бинам из различных контекстов спринг.
Настраивается в [Link]
Model: Этот блок инкапсулирует (объединяет) данные приложения. На практике это
POJO-классы.
View: ответственно за возвращение ответа клиенту в виде текстов и изображений.
Некоторые запросы могут идти прямо во View, не заходя в Model; другие проходят через все
три слоя.
ViewResolver: выбор, какое именно View должно быть показано клиенту. Поддерживает
распознавание представлений на основе логического имени, возвращаемого контроллером.
Для поддержки различных механизмов распознавания представлений предусмотрено
множество классов реализации. Например, класс UrlBasedViewResolver поддерживает прямое
преобразование логических имен в URL.
Класс ContentNegotiatingViewResolver поддерживает динамическое распознавание
представлений в зависимости от типа медиа, поддерживаемого клиентом (XML, PDF, JSON и
т.д.). Существует также несколько реализаций для интеграции с различными технологиями
представлений, такими как FreeMarkerrre (FreeMarkerViewResolver), Velocity
(VelocityViewResolver) и JasperReports (JasperReportsViewResolver).
HandlerInterceptor: перехват входящих запросов. Сопоставим, но не эквивалентен
сервлет-фильтрам (использование не является обязательным и не контролируется
DispatcherServlet-ом).
LocaleResolver: получение и, возможно, сохранение локальных настроек (язык, страна,
часовой пояс) пользователя.
MultipartResolver: обеспечивает Upload — загрузку на сервер локальных файлов
клиента. По умолчанию этот интерфейс не включается в приложении и необходимо указывать
его в файле конфигурации. После настройки любой запрос о загрузке будет отправляться
этому интерфейсу.
Spring MVC предоставляет разработчику следующие возможности:
 Ясное и прозрачное разделение между слоями в MVC и запросах.
 Стратегия интерфейсов — каждый интерфейс делает только свою часть работы.
 Интерфейс всегда может быть заменен альтернативной реализацией.
 Интерфейсы тесно связаны с Servlet API.
 Высокий уровень абстракции для веб-приложений.
 В веб-приложениях можно использовать различные части Spring, а не только Spring
MVC.
[Link]

35. Starter-pack Spring Boot - как создать?


[Link] (с 30мин)

36. SOAP vs REST


 SOAP Simple Object Access Protocol (простой протокол доступа к объектам) – это целое
семейство протоколов и стандартов, для обмена структурированными сообщениями
основанными на XML. Это более тяжеловесный и сложный вариант с точки зрения
машинной обработки. Поэтому REST работает быстрее.
 REST Representational State Transfer (передача состояния представления)- это не
протокол и не стандарт, а архитектурный стиль.
Специфика SOAP — это формат обмена данными. С SOAP это всегда SOAP-XML,
который представляет собой XML, включающий:
— Envelope (конверт) – корневой элемент, который определяет сообщение и
пространство имен, использованное в документе,
— Header (заголовок) – содержит атрибуты сообщения, например: информация о
безопасности или о сетевой маршрутизации,
— Body (тело) – содержит сообщение, которым обмениваются приложения,
— Fault – необязательный элемент, который предоставляет информацию об ошибках,
которые произошли при обработке сообщений. И запрос, и ответ должны
соответствовать структуре SOAP.
Специфика REST — использование HTTP в качестве транспортного протокола. Он
подразумевает наилучшее использование функций, предоставляемых HTTP —
методы запросов, заголовки запросов, ответы, заголовки ответов и т. д.
Особенности REST:
 Наличие Клиентов и Серверов.
 Отсутствие состояний. У клиентов и серверов нет необходимости отслеживать
состояния друг друга.
 Единообразие интерфейса. Это достигается через 4 ограничения:
идентификацию ресурсов, манипуляцию ресурсами через представления,
«самодостаточные» сообщения и гипермедиа.
 Кэширование,
 Система слоев
 Код по требованию
[Link]
[Link]
%D0%A7%D1%82%D0%BE-%D1%82%D0%B0%D0%BA%D0%BE%D0%B5-spring

37. Spring Data - что под +капотом

38. Spring Security - что под капотом

39. @Secured
@Secured используется для определения списка атрибутов конфигурации безопасности для
бизнес - методов. Эта аннотация может использоваться как альтернатива Java 5
конфигурации XML.
[Link]
security/access/annotation/[Link]

40. Как исключить класс, автоконфигурацию класса в Spring Boot


Отключить ненужные автоконфигурации можно при помощи свойств exclude и
excludeName аннотаций @EnableAutoConfiguration, @ImportAutoConfiguration и
@SpringBootApplication. Или в property задать SpringAutoconfiguration exclude и передать
имена классов.
Можно отказаться от использования механизма автоконфигурации, вместо этого
указывая необходимые автоконфигурации вручную. Для этого надо избавиться от аннотаций
@SpringBootApplication и @EnableAutoConfiguration в коде вашего проекта, а для указания
нужных конфигурационных классов использовать аннотации @SpringBootConfiguration и
@ImportAutoConfiguration. Однако стоит помнить, что используемые автоконфигурации всё
ещё могут содержать неиспользуемые компоненты.
[Link]
%D0%BD%D1%82%D0%B5%D0%B9%D0%BD%D0%B5%D1%80%D1%8B

41. Как заинжектить примитив


@Value над полем - внедряем примитив
[Link]

42. Как заинжектить коллекцию


[Link]

43. BeanPostProcessor vs BeanFactoryPostProcessor


[Link]

44. Часто используемые аннотации спринга


@Controller – класс фронт контроллера в проекте Spring MVC.
@RequestMapping – позволяет задать шаблон маппинга URI в методе обработчике
контроллера.
@ResponseBody – позволяет отправлять Object в ответе. Обычно используется для отправки
данных формата XML или JSON.
@PathVariable – задает динамический маппинг значений из URI внутри аргументов метода
обработчика.
@Autowired – используется для автоматического связывания зависимостей в spring beans.
@Qualifier – используется совместно с @Autowired для уточнения данных связывания, когда
возможны коллизии (например одинаковых имен\типов).
@Service – указывает что класс осуществляет сервисные функции.
@Scope – указывает scope у spring bean.
@Configuration, @ComponentScan и @Bean – для java based configurations.
AspectJ аннотации для настройки aspects и advices, @Aspect, @Before, @After,@Around,
@Pointcut и др.

45. @ExceptionHandler
[Link]

Паттерны

Паттерны
вопросы-ответы Хилькевич Игорь 04.04.2020

Вопросы
1. Что такое «шаблон проектирования»?
2. Назовите основные характеристики шаблонов.
3. Типы шаблонов проектирования.
4. Приведите примеры основных шаблонов проектирования.
5. Приведите примеры порождающих шаблонов проектирования.
6. Приведите примеры структурных шаблонов проектирования.
7. Приведите примеры поведенческих шаблонов проектирования.
8. Что такое «антипаттерн»? Какие антипаттерны вы знаете?
9. Что такое Dependency Injection?

Доп. вопросы
1. Паттерны в spring framework
2. Какие паттерны применяются в Hibernate?

Ответы на вопросы
1. Что такое «шаблон проектирования»?
Шаблон (паттерн) проектирования (design pattern) — это проверенное и готовое к
использованию решение. Это не класс и не библиотека, которую можно подключить к проекту,
это нечто большее - он не зависит от языка программирования, не является законченным
образцом, который может быть прямо преобразован в код и может быть реализован по
разному в разных языках программирования.
Плюсы использования шаблонов:
 снижение сложности разработки за счёт готовых абстракций для решения целого
класса проблем.
 облегчение коммуникации между разработчиками, позволяя ссылаться на известные
шаблоны.
 унификация деталей решений: модулей и элементов проекта.
 возможность отыскав удачное решение, пользоваться им снова и снова.
 помощь в выборе наиболее подходящего варианта проектирования.
Минусы:
 слепое следование некоторому выбранному шаблону может привести к усложнению
программы.
 желание попробовать некоторый шаблон в деле без особых на то оснований.
[Link]

2. Назовите основные характеристики шаблонов.


 Имя - все шаблоны имеют уникальное имя, служащее для их идентификации;
 Назначение - назначение данного шаблона;
 Задача - задача, которую шаблон позволяет решить;
 Способ решения - способ, предлагаемый в шаблоне для решения задачи в том
контексте, где этот шаблон был найден;
 Участники - сущности, принимающие участие в решении задачи;
 Следствия - последствия от использования шаблона как результат действий,
выполняемых в шаблоне;
 Реализация - возможный вариант реализации шаблона.
[Link]

3. Типы шаблонов проектирования.


 Основные (Fundamental) - основные строительные блоки других шаблонов.
Большинство других шаблонов использует эти шаблоны в той или иной форме.
 Порождающие шаблоны (Creational) — шаблоны проектирования, которые
абстрагируют процесс создание экземпляра. Они позволяют сделать систему
независимой от способа создания, композиции и представления объектов. Шаблон,
порождающий классы, использует наследование, чтобы изменять созданный объект, а
шаблон, порождающий объекты, делегирует создание объектов другому объекту.
 Структурные шаблоны (Structural) определяют различные сложные структуры,
которые изменяют интерфейс уже существующих объектов или его реализацию,
позволяя облегчить разработку и оптимизировать программу.
 Поведенческие шаблоны (Behavioral) определяют взаимодействие между объектами,
увеличивая таким образом его гибкость.
[Link]

4. Приведите примеры основных шаблонов проектирования.


 Делегирование (Delegation pattern) - Сущность внешне выражает некоторое
поведение, но в реальности передает ответственность за выполнение этого поведения
связанному объекту.
 Функциональный дизайн (Functional design) - Гарантирует, что каждая сущность
имеет только одну обязанность и исполняет ее с минимумом побочных эффектов на
другие.
 Неизменяемый интерфейс (Immutable interface) - Создание неизменяемого объекта.
 Интерфейс (Interface) - Общий метод структурирования сущностей облегчающий их
понимание.
 Интерфейс-маркер (Marker interface) - В качестве атрибута (как пометки объектной
сущности) применяется наличие или отсутствие реализации интерфейса-маркера. В
современных языках программирования вместо этого применяются атрибуты или
аннотации.
 Контейнер свойств (Property container) - Позволяет добавлять дополнительные
свойства сущности в контейнер внутри себя, вместо расширения новыми свойствами.
 Канал событий (Event channel) - Создает централизованный канал для событий.
Использует сущность-представитель для подписки и сущность-представитель для
публикации события в канале. Представитель существует отдельно от реального
издателя или подписчика. Подписчик может получать опубликованные события от
более чем одной сущности, даже если он зарегистрирован только на одном канале.
[Link]

5. Приведите примеры порождающих шаблонов проектирования.


 Абстрактная фабрика (Abstract factory) - Класс, который представляет собой
интерфейс для создания других классов.
Пример в java - newInstance() из [Link].
interface Lada {
long getLadaPrice();
}
interface Ferrari {
long getFerrariPrice();
}
interface Porshe {
long getPorshePrice();
}
interface InteAbsFactory {

Lada getLada();
Ferrari getFerrari();
Porshe getPorshe();
}
class UaLadaImpl implements Lada {// первая
public long getLadaPrice() {
return 1000;
}
}
class UaFerrariImpl implements Ferrari {
public long getFerrariPrice() {
return 3000;
}
}
class UaPorsheImpl implements Porshe {
public long getPorshePrice() {
return 2000;
}
}
class UaCarPriceAbsFactory implements InteAbsFactory {
public Lada getLada() {
return new UaLadaImpl();
}
public Ferrari getFerrari() {
return new UaFerrariImpl();
}
public Porshe getPorshe() {
return new UaPorsheImpl();
}
}// первая
class RuLadaImpl implements Lada {// вторая
public long getLadaPrice() {
return 10000;
}
}
class RuFerrariImpl implements Ferrari {
public long getFerrariPrice() {
return 30000;
}
}
class RuPorsheImpl implements Porshe {
public long getPorshePrice() {
return 20000;
}
}
class RuCarPriceAbsFactory implements InteAbsFactory {
public Lada getLada() {
return new RuLadaImpl();
}
public Ferrari getFerrari() {
return new RuFerrariImpl();
}
public Porshe getPorshe() {
return new RuPorsheImpl();
}
}// вторая

public class AbstractFactoryTest {//тест


public static void main(String[] args) {
String country = "UA";
InteAbsFactory ifactory = null;
if([Link]("UA")) {
ifactory = new UaCarPriceAbsFactory();
} else if([Link]("RU")) {
ifactory = new RuCarPriceAbsFactory();
}

Lada lada = [Link]();


[Link]([Link]());
}
}
[Link]
[Link]
v=cmyUI_ZezoU&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=5
[Link]
[Link]

 Строитель (Builder) - Класс, который представляет собой интерфейс для создания


сложного объекта.
Пример в java - StringBuilder.
class Car {
public void buildBase() {
print("Делаем корпус");
}
public void buildWheels() {
print("Ставим колесо");
}
public void buildEngine(Engine engine) {
print("Ставим движок: " + [Link]());
}
private void print(String msg){
[Link](msg);
}
}
interface Engine {
String getEngineType();
}
class OneEngine implements Engine {
public String getEngineType() {
return "Первый двигатель";
}
}
class TwoEngine implements Engine {
public String getEngineType() {
return "Второй двигатель";
}
}
abstract class Builder {
protected Car car;
public abstract Car buildCar();
}
class OneBuilderImpl extends Builder {
public OneBuilderImpl(){
car = new Car();
}
public Car buildCar() {
[Link]();
[Link]();
Engine engine = new OneEngine();
[Link](engine);
return car;
}
}
class TwoBuilderImpl extends Builder {
public TwoBuilderImpl(){
car = new Car();
}
public Car buildCar() {
[Link]();
[Link]();
Engine engine = new OneEngine();
[Link](engine);
[Link]();
engine = new TwoEngine();
[Link](engine);
return car;
}
}
class Build {
private Builder builder;
public Build(int i){
if(i == 1) {
builder = new OneBuilderImpl();
} else if(i == 2) {
builder = new TwoBuilderImpl();
}
}
public Car buildCar(){
return [Link]();
}
}

public class BuilderTest {//тест


public static void main(String[] args) {
Build build = new Build(1);
[Link]();
}
}
[Link]
[Link]
v=63_ExLjusac&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=7
[Link]
[Link]
 Фабричный метод (Factory method) - Делегирует создание объектов наследникам
родительского класса. Это позволяет использовать в коде программы не
специфические классы, а манипулировать абстрактными объектами на более высоком
уровне.
Пример в java - метод toString() у Object.
class Factory {
public OS getCurrentOS(String inputos) {
OS os = null;
if ([Link]("windows")) {
os = new windowsOS();
} else if ([Link]("linux")) {
os = new linuxOS();
} else if ([Link]("mac")) {
os = new macOS();
}
return os;
}
}
interface OS {
void getOS();
}
class windowsOS implements OS {
public void getOS () {
[Link]("применить для виндовс");
}
}
class linuxOS implements OS {
public void getOS () {
[Link]("применить для линукс");
}
}
class macOS implements OS {
public void getOS () {
[Link]("применить для мак");
}
}

public class FactoryTest {//тест


public static void main(String[] args){
String win = "linux";
Factory factory = new Factory();
OS os = [Link](win);
[Link]();
}
}
[Link]
[Link]
v=TwIjjTC5g7g&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=4
[Link]
[Link]

 Прототип (Prototype) - Определяет интерфейс создания объекта через клонирование


другого объекта вместо создания через конструктор.
Пример в java - метод clone() у Object.
interface Copyable {
Copyable copy();
}
class ComplicatedObject implements Copyable {
private Type type;
public enum Type {
ONE, TWO
}
public ComplicatedObject copy() {
ComplicatedObject complicatedobject = new
ComplicatedObject();
return complicatedobject;
}
public void setType(Type type) {
[Link] = type;
}
}

public class PrototypeTest {//тест


public static void main(String[] args) {
ComplicatedObject prototype = new ComplicatedObject();
ComplicatedObject clone = [Link]();
[Link]([Link]);
}
}
[Link]
[Link]
v=7X3eQZX5b9Y&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=8
[Link]
[Link]
libraries

 Одиночка (Singleton) - Класс, который может иметь только один экземпляр.


Пример в java - метод getDesktop() у Desktop.
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void setUp() {
[Link]("setUp");
}
}

public class SingletonTest {//тест


public static void main(String[] args){
Singleton singelton = [Link]();
[Link]();
}
}
[Link]
[Link]
v=vyr9GO7dLBQ&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=6
[Link]
[Link]
libraries

[Link]

6. Приведите примеры структурных шаблонов проектирования.


 Адаптер (Adapter) - Объект, обеспечивающий взаимодействие двух других объектов,
один из которых использует, а другой предоставляет несовместимый с первым
интерфейс.
Пример в java - метод asList() у Arrays.
class PBank {
private int balance;
public PBank() { balance = 100; }
public void getBalance() {
[Link]("PBank balance = " + balance);
}
}
class ABank {
private int balance;
public ABank() { balance = 200; }
public void getBalance() {
[Link]("ABank balance = " + balance);
}
}
class PBankAdapter extends PBank {
private ABank abank;
public PBankAdapter(ABank abank) {
[Link] = abank;
}
public void getBalance() {
[Link]();
}
}

public class AdapterTest {//тест


public static void main(String[] args) {
PBank pbank = new PBank();
[Link]();
PBankAdapter abank = new PBankAdapter(new ABank());
[Link]();
}
}
[Link]
[Link]
v=VunUuXyJw2U&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=10
[Link]
[Link]
libraries

 Мост (Bridge) - Разделяет реализацию и абстракцию, дает возможность изменять их


свободно друг от друга. Делает конкретные классы независимыми от классов
реализации интерфейса.
Пример в java - метод newSetFromMap() у Collections.
interface Engine {
void setEngine();
}
abstract class Car {
protected Engine engine;
public Car(Engine engine){
[Link] = engine;
}
abstract public void setEngine();
}
class SportCar extends Car {
public SportCar(Engine engine) {
super(engine);
}
public void setEngine() {
[Link]("SportCar engine: ");
[Link]();
}
}
class UnknownCar extends Car {
public UnknownCar(Engine engine) {
super(engine);
}
public void setEngine() {
[Link]("UnknownCar engine: ");
[Link]();
}
}
class SportEngine implements Engine {
public void setEngine(){
[Link]("sport");
}
}
class UnknownEngine implements Engine {
public void setEngine(){
[Link]("unknown");
}
}
public class BridgeTest {//тест
public static void main(String[] args) {
Car sportCar = new SportCar(new SportEngine());
[Link]();
[Link]();
Car unknownCar = new UnknownCar(new UnknownEngine());
[Link]();
}
}
[Link]
[Link]
B2qnM&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=11
[Link]
[Link]
libraries

 Компоновщик (Composite) - Объект, который объединяет в себе объекты, подобные


ему самому.
Пример в java - метод add(Component) у [Link].
interface Car {
void draw(String color);
}
class SportCar implements Car {
public void draw(String color) {
[Link]("SportCar color: " + color);
}
}
class UnknownCar implements Car {
public void draw(String color) {
[Link]("UnknownCar color: " + color);
}
}
class Drawing implements Car {
private List<Car> cars = new ArrayList<Car>();
public void draw(String color) {
for(Car car : cars) {
[Link](color);
}
}
public void add(Car s){
[Link](s);
}
public void clear(){
[Link]();
[Link]();
}
}

public class CompositeTest {//тест


public static void main(String[] args) {
Car sportCar = new SportCar();
Car unknownCar = new UnknownCar();
Drawing drawing = new Drawing();
[Link](sportCar);
[Link](unknownCar);
[Link]("green");
[Link]();
[Link](sportCar);
[Link](unknownCar);
[Link]("white");
}
}
[Link]
[Link]
v=eZx8eiTntAs&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=12
[Link]
[Link]
libraries

 Декоратор (Decorator) - Класс, расширяющий функциональность другого класса без


использования наследования.
Пример в java - [Link], OutputStream, Reader и Writer.
interface Car {
void draw();
}
class SportCar implements Car {
public void draw() {
[Link]("SportCar");
}
}
class UnknownCar implements Car {
public void draw() {
[Link]("UnknownCar");
}
}
abstract class CarDecorator implements Car {
protected Car decorated;
public CarDecorator(Car decorated){
[Link] = decorated;
}
public void draw(){
[Link]();
}
}
class BlueCarDecorator extends CarDecorator {
public BlueCarDecorator(Car decorated) {
super(decorated);
}
public void draw() {
[Link]();
setColor();
}
private void setColor(){
[Link]("Color: red");
}
}

public class DecoratorTest {//тест


public static void main(String[] args) {
Car sportCar = new SportCar();
Car blueUnknownCar = new BlueCarDecorator(new UnknownCar());
[Link]();
[Link]();
[Link]();
}
}
[Link]
[Link]
v=2aB2B3b3bQA&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=13
[Link]
[Link]
libraries

 Фасад (Facade) - Объект, который абстрагирует работу с несколькими классами,


объединяя их в единое целое.
Пример в java - [Link], который используется внутри
ServletContext, HttpSession, HttpServletRequest, HttpServletResponseи т.д.
interface Car {
void start();
void stop();
}
class Key implements Car {
public void start() {
[Link]("Вставить ключи");
}
public void stop() {
[Link]("Вытянуть ключи");
}
}
class Engine implements Car {
public void start() {
[Link]("Запустить двигатель");
}
public void stop() {
[Link]("Остановить двигатель");
}
}
class Facade {
private Key key;
private Engine engine;
public Facade() {
key = new Key();
engine = new Engine();
}
public void startCar() {
[Link]();
[Link]();
}
public void stoptCar() {
[Link]();
[Link]();
}
}

public class FacadeTest {//тест


public static void main(String[] args) {
Facade facade = new Facade();
[Link]();
[Link]();
[Link]();
}
}
[Link]
[Link]
_K9EU&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=14
[Link]
[Link]
libraries

 Приспособленец (Flyweight) - Вместо создания большого количества похожих


объектов, объекты используются повторно. Экономит память.
Пример в java - пул строк, а также метод valueOf(int) у [Link] (также на
Boolean, Byte, Character, Short, Long и BigDecimal)
class Flyweight {
private int row;
public Flyweight(int row) {
[Link] = row;
[Link]("ctor: " + [Link]);
}
void report(int col) {
[Link](" " + row + col);
}
}

class Factory {
private Flyweight[] pool;
public Factory(int maxRows) {
pool = new Flyweight[maxRows];
}
public Flyweight getFlyweight(int row) {
if (pool[row] == null) {
pool[row] = new Flyweight(row);
}
return pool[row];
}
}

public class FlyweightTest {//тест


public static void main(String[] args) {
int rows = 5;
Factory theFactory = new Factory(rows);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < rows; j++) {
[Link](i).report(j);
}
[Link]();
}
}
}
[Link]
[Link]
2LSGNA&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=15
[Link]
[Link]
libraries

 Заместитель (Proxy) - Объект, который является посредником между двумя другими


объектами, и который реализует/ограничивает доступ к объекту, к которому
обращаются через него.
Пример в java - [Link].
interface Image {
void display();
}
class RealImage implements Image {
private String file;
public RealImage(String file){
[Link] = file;
load(file);
}
private void load(String file){
[Link]("Загрузка " + file);
}
public void display() {
[Link]("Просмотр " + file);
}
}
class ProxyImage implements Image {
private String file;
private RealImage image;
public ProxyImage(String file){
[Link] = file;
}
public void display() {
if(image == null){
image = new RealImage(file);
}
[Link]();
}
}

public class ProxyTest {//тест


public static void main(String[] args) {
Image image = new ProxyImage("[Link]");
[Link]();
[Link]();
}
}
[Link]
[Link]
v=EzDkgArMO3U&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=16
[Link]
[Link]
libraries

[Link]

7. Приведите примеры поведенческих шаблонов проектирования.


 Цепочка обязанностей (Chain of responsibility) - Позволяет избежать жесткой
зависимости отправителя запроса от его получателя, при этом запрос может быть
обработан несколькими объектами.
Пример в java - метод log() у [Link].
interface Payment {
void setNext(Payment payment);
void pay();
}
class VisaPayment implements Payment {
private Payment payment;
public void setNext(Payment payment) {
[Link] = payment;
}
public void pay() {
[Link]("Visa Payment");
}
}
class PayPalPayment implements Payment {
private Payment payment;
public void setNext(Payment payment) {
[Link] = payment;
}
public void pay() {
[Link]("PayPal Payment");
}
}

public class ChainofResponsibilityTest {//тест


public static void main(String[] args) {
Payment visaPayment = new VisaPayment();
Payment payPalPayment = new PayPalPayment();
[Link](payPalPayment);
[Link]();
}
}
[Link]
[Link]
u8P5DQ&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=18
[Link]
[Link]
libraries

 Команда (Command) - Позволяет инкапсулировать различные операции в отдельные


объекты.
Пример в java - все реализации [Link].
interface Command {
void execute();
}
class Car {
public void startEngine() {
[Link]("запустить двигатель");
}
public void stopEngine() {
[Link]("остановить двигатель");
}
}
class StartCar implements Command {
Car car;
public StartCar(Car car) {
[Link] = car;
}
public void execute() {
[Link]();
}
}
class StopCar implements Command {
Car car;
public StopCar(Car car) {
[Link] = car;
}
public void execute() {
[Link]();
}
}
class CarInvoker {
public Command command;
public CarInvoker(Command command){
[Link] = command;
}
public void execute(){
[Link]();
}
}

public class CommandTest {//тест


public static void main(String[] args) {
Car car = new Car();
StartCar startCar = new StartCar(car);
StopCar stopCar = new StopCar(car);
CarInvoker carInvoker = new CarInvoker(startCar);
[Link]();
}
}
[Link]
[Link]
v=T3oXyVYmkyY&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=19
[Link]
[Link]
libraries

 Интерпретатор (Interpreter) - Решает часто встречающуюся, но подверженную


изменениям, задачу.
Пример в java - все подклассы [Link].
interface Expression {
String interpret(Context context);
}
class Context {
public String getLowerCase(String s){
return [Link]();
}
public String getUpperCase(String s){
return [Link]();
}
}
class LowerExpression implements Expression {
private String s;
public LoverExpression(String s) {
this.s = s;
}
public String interpret(Context context) {
return [Link](s);
}
}
class UpperExpression implements Expression {
private String s;
public UpperExpression(String s) {
this.s = s;
}
public String interpret(Context context) {
return [Link](s);
}
}

public class InterpreterTest {//тест


public static void main(String[] args) {
String str = "TesT";
Context context = new Context();
Expression loverExpression = new LoverExpression(str);
str = [Link](context);
[Link](str);
Expression upperExpression = new UpperExpression(str);
str = [Link](context);
[Link](str);
}
}
[Link]
v=8fRHz7_9pGI&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=20
[Link]
[Link]
libraries

 Итератор (Iterator) - Представляет собой объект, позволяющий получить


последовательный доступ к элементам объекта-агрегата без использования описаний
каждого из объектов, входящих в состав агрегации.
Пример в java - все реализации [Link].
interface Iterator {
boolean hasNext();
Object next();
}
class Numbers {
public int num[] = {1 , 2, 3};
public Iterator getIterator() {
return new NumbersIterator();
}
private class NumbersIterator implements Iterator {
int ind;
public boolean hasNext() {
if(ind < [Link]) return true;
return false;
}
public Object next() {
if([Link]()) return num[ind++];
return null;
}
}
}

public class IteratorTest {//тест


public static void main(String[] args) {
Numbers numbers = new Numbers();
Iterator iterator = [Link]();
while ([Link]()) {
[Link]([Link]());
}
}
}
[Link]
[Link]
v=yY6oy8xHLT8&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=21
[Link]
[Link]
libraries

 Посредник (Mediator) - Обеспечивает взаимодействие множества объектов, формируя


при этом слабую связанность и избавляя объекты от необходимости явно ссылаться
друг на друга.
Пример в java - метод execute() у [Link].
class Mediator {
public static void sendMessage(User user, String msg){
[Link]([Link]() + ": " + msg);
}
}
class User {
private String name;
public User(String name){
[Link] = name;
}
public String getName() {
return name;
}
public void sendMessage(String msg){
[Link](this, msg);
}
}

public class MediatorTest {//тест


public static void main(String[] args) {
User user1 = new User("user1");
User user2 = new User("user2");
[Link]("message1");
[Link]("message2");
}
}
[Link]
[Link]
v=ZnyNsrcLl2I&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=22
[Link]
[Link]
libraries

 Хранитель (Memento) - Позволяет не нарушая инкапсуляцию зафиксировать и


сохранить внутренние состояния объекта так, чтобы позднее восстановить его в этих
состояниях.
Пример в java - все реализации [Link].
class Memento {
private String name;
private int age;
public Memento(String name, int age){
[Link] = name;
[Link] = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class User {
private String name;
private int age;
public User(String name, int age) {
[Link] = name;
[Link] = age;
[Link]([Link]("create: name = %s, age =
%s", name, age));
}
public Memento save(){
[Link]([Link]("save: name = %s, age =
%s", name, age));
return new Memento(name, age);
}
public void restore(Memento memento){
name = [Link]();
age = [Link]();
[Link]([Link]("restore: name = %s, age =
%s", name, age));
}
}
class SaveUser {
private List<Memento> list = new ArrayList<Memento>();
public void add(Memento memento){
[Link](memento);
}
public Memento get(int ind){
return [Link](ind);
}
}

public class MementoTest { // Test


public static void main(String[] args) {
SaveUser saveUser = new SaveUser();
User user1 = new User("Peter", 17);
User user2 = new User("Ian", 19);
[Link]([Link]());
[Link]([Link](0));
}
}
[Link]
[Link]
v=nu2CN3r4Jp0&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=23
[Link]

 Наблюдатель (Observer) - Определяет зависимость типа «один ко многим» между


объектами таким образом, что при изменении состояния одного объекта все
зависящие от него оповещаются об этом событии.
Пример в java - все реализации [Link] (практически во всем Swing).
interface Observer {
void event(List<String> strings);
}
class University {
private List<Observer> observers = new ArrayList<Observer>();
private List<String> students = new ArrayList<String>();
public void addStudent(String name) {
[Link](name);
notifyObservers();
}
public void removeStudent(String name) {
[Link](name);
notifyObservers();
}
public void addObserver(Observer observer){
[Link](observer);
}
public void removeObserver(Observer observer) {
[Link](observer);
}
public void notifyObservers(){
for (Observer observer : observers) {
[Link](students);
}
}
}
class Director implements Observer {
public void event(List<String> strings) {
[Link]("The list of students has changed: " +
strings);
}
}

public class ObserverTest {//тест


public static void main(String[] args) {
University university = new University();
Director director = new Director();
[Link]("Vaska");
[Link](director);
[Link]("Anna");
[Link]("Vaska");
}
}
[Link]
[Link]
v=br5201sWOHM&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=24
[Link]
[Link]
libraries

 Состояние (State) - Используется в тех случаях, когда во время выполнения


программы объект должен менять свое поведение в зависимости от своего состояния.
Пример в java - метод execute() у [Link] (контролируется
FacesServlet, поведение зависит от текущей фазы (состояния) жизненного цикла
JSF).
interface State {
void doAction();
}
class StartPlay implements State {
public void doAction() {
[Link]("start play");
}
}
class StopPlay implements State {
public void doAction() {
[Link]("stop play");
}
}
class PlayContext implements State {
private State state;
public void setState(State state){
[Link] = state;
}
public void doAction() {
[Link]();
}
}

public class StateTest {//тест


public static void main(String[] args) {
PlayContext playContext = new PlayContext();
State startPlay = new StartPlay();
State stopPlay = new StopPlay();
[Link](startPlay);
[Link]();
[Link](stopPlay);
[Link]();
}
}
[Link]
[Link]
v=gpY98f7A_8M&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=25
[Link]
[Link]
libraries

 Стратегия (Strategy) - Определяет ряд алгоритмов позволяя взаимодействовать


между ними. Алгоритм стратегии может быть изменен во время выполнения
программы.
Пример в java - метод compare() [Link], выполненный среди других
методов sort() у Collections.
interface Strategy {
void download(String file);
}
class DownloadWindownsStrategy implements Strategy {
public void download(String file) {
[Link]("windows download: " + file);
}
}
class DownloadLinuxStrategy implements Strategy {
public void download(String file) {
[Link]("linux download: " + file);
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy){
[Link] = strategy;
}
public void download(String file){
[Link](file);
}
}

public class StrategyTest {//тест


public static void main(String[] args) {
Context context = new Context(new
DownloadWindownsStrategy());
[Link]("[Link]");
context = new Context(new DownloadLinuxStrategy());
[Link]("[Link]");
}
}
[Link]
[Link]
v=rsB2exGsR4I&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=26
[Link]
[Link]
libraries

 Шаблонный метод (Template method) - Определяет основу алгоритма и позволяет


наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в
целом.
Пример в java - все не абстрактные методы [Link], [Link],
[Link]и [Link].
abstract class Car {
abstract void startEngine();
abstract void stopEngine();

public final void start(){


startEngine();
stopEngine();
}
}
class OneCar extends Car {
public void startEngine() {
[Link]("Start engine.");
}
public void stopEngine() {
[Link]("Stop engine.");
}
}
class TwoCar extends Car {
public void startEngine() {
[Link]("Start engine.");
}
public void stopEngine() {
[Link]("Stop engine.");
}
}

public class TemplateTest {//тест


public static void main(String[] args) {
Car car1 = new OneCar();
[Link]();
[Link]();
Car car2 = new TwoCar();
[Link]();
}
}
[Link]
[Link]
0&list=PLlsMRoVt5sTPgGbinwOVnaF1mxNeLAD7P&index=27
[Link]
[Link]
libraries
 Посетитель (Visitor) - Описывает операцию, которая выполняется над объектами
других классов. При изменении класса Visitor нет необходимости изменять
обслуживаемые классы.
Пример в java - [Link] и SimpleFileVisitor.
interface Visitor {
void visit(SportCar sportCar);
void visit(Engine engine);
void visit(Whell whell);
}
interface Car {
void accept(Visitor visitor);
}
class Engine implements Car {
public void accept(Visitor visitor) {
[Link](this);
}
}
class Whell implements Car {
public void accept(Visitor visitor) {
[Link](this);
}
}
class SportCar implements Car {
Car[] cars;
public SportCar(){
cars = new Car[]{new Engine(), new Whell()};
}
public void accept(Visitor visitor) {
for (int i = 0; i < [Link]; i++) {
cars[i].accept(visitor);
}
[Link](this);
}
}
class CarVisitor implements Visitor {
public void visit(SportCar computer) {
print("car");
}
public void visit(Engine engine) {
print("engine");
}
public void visit(Whell whell) {
print("whell");
}
private void print(String string) {
[Link](string);
}
}

public class VisitorTest {//тест


public static void main(String[] args) {
Car computer = new SportCar();
[Link](new CarVisitor());
}
}
[Link]
[Link]
[Link]
[Link]
[Link]
libraries

[Link]

8. Что такое «антипаттерн»? Какие антипаттерны вы знаете?


Антипаттерн (anti-pattern) — это распространённый подход к решению класса часто
встречающихся проблем, являющийся неэффективным, рискованным или непродуктивным.
Poltergeists (полтергейсты) - это классы с ограниченной ответственностью и ролью в
системе, чьё единственное предназначение — передавать информацию в другие классы. Их
эффективный жизненный цикл непродолжителен. Полтергейсты нарушают стройность
архитектуры программного обеспечения, создавая избыточные (лишние) абстракции, они
чрезмерно запутаны, сложны для понимания и трудны в сопровождении. Обычно такие
классы задумываются как классы-контроллеры, которые существуют только для вызова
методов других классов, зачастую в предопределенной последовательности.
Признаки появления и последствия антипаттерна
 Избыточные межклассовые связи.
 Временные ассоциации.
 Классы без состояния (содержащие только методы и константы).
 Временные объекты и классы (с непродолжительным временем жизни).
 Классы с единственным методом, который предназначен только для создания или
вызова других классов посредством временной ассоциации.
 Классы с именами методов в стиле «управления», такие как startProcess.
Типичные причины
 Отсутствие объектно-ориентированной архитектуры (архитектор не понимает
объектно-ориентированной парадигмы).
 Неправильный выбор пути решения задачи.
 Предположения об архитектуре приложения на этапе анализа требований (до
объектно-ориентированного анализа) могут также вести к проблемам на подобии этого
антипаттерна.
Внесенная сложность (Introduced complexity): Необязательная сложность дизайна. Вместо
одного простого класса выстраивается целая иерархия интерфейсов и классов. Типичный
пример «Интерфейс - Абстрактный класс - Единственный класс реализующий интерфейс на
основе абстрактного».
Инверсия абстракции (Abstraction inversion): Сокрытие части функциональности от внешнего
использования, в надежде на то, что никто не будет его использовать.
Неопределенная точка зрения (Ambiguous viewpoint): Представление модели без
спецификации её точки рассмотрения.
Большой комок грязи (Big ball of mud): Система с нераспознаваемой структурой.
Божественный объект (God object): Концентрация слишком большого количества функций в
одной части системы (классе).
Затычка на ввод данных (Input kludge): Забывчивость в спецификации и выполнении
поддержки возможного неверного ввода.
Раздувание интерфейса (Interface bloat): Разработка интерфейса очень мощным и очень
сложным для реализации.
Волшебная кнопка (Magic pushbutton): Выполнение результатов действий пользователя в
виде неподходящего (недостаточно абстрактного) интерфейса. Например, написание
прикладной логики в обработчиках нажатий на кнопку.
Перестыковка (Re-Coupling): Процесс внедрения ненужной зависимости.
Дымоход (Stovepipe System): Редко поддерживаемая сборка плохо связанных компонентов.
Состояние гонки (Race hazard): непредвидение возможности наступления событий в
порядке, отличном от ожидаемого.
Членовредительство (Mutilation): Излишнее «затачивание» объекта под определенную
очень узкую задачу таким образом, что он не способен будет работать с никакими иными,
пусть и очень схожими задачами.
Сохранение или смерть (Save or die): Сохранение изменений лишь при завершении
приложения.
[Link]
9. Что такое Dependency Injection?
Dependency Injection (внедрение зависимости) - это набор паттернов и принципов
разработки программного обеспечения, которые позволяют писать слабосвязный код. В
полном соответствии с принципом единой обязанности объект отдаёт заботу о построении
требуемых ему зависимостей внешнему, специально предназначенному для этого общему
механизму.
[Link]

Ответы на доп. вопросы


1. Паттерны в spring framework
Singleton - Creating beans with default scope.
Factory - Bean Factory classes
Prototype - Bean scopes
Adapter - Spring Web and Spring MVC
Proxy - Spring Aspect Oriented Programming support
Template Method - JdbcTemplate, HibernateTemplate etc
Front Controller - Spring MVC DispatcherServlet
Data Access Object - Spring DAO support
Dependency Injection and Aspect Oriented Programming

2. Какие паттерны применяются в Hibernate?


Domain Model – объектная модель предметной области, включающая в себя как поведение
так и данные.
Data Mapper – слой мапперов (Mappers), который передает данные между объектами и базой
данных, сохраняя их независимыми друг от друга и себя.
Proxy — применяется для ленивой загрузки.
Factory — используется в SessionFactory

Алгоритмы

Алгоритмы
вопросы-ответы Хилькевич Игорь 06.04.2020

Вопросы
1. Big O
2. Временная сложность алгоритмов
3. ArrayList vs LinkedList
4. Устройство HashMap
5. Рекурсия
6. Рекурсия vs Iterator
7. Виды сортировок и их сравнение
8. Виды поиска и их сравнение
9. Жадный алгоритм
10. Queue, Deque, stack, Heap
11. EnumSet
12. Бинарное дерево
13. Красно-чёрное дерево
14. Мемоизация

Доп. инфа:
Книга “Грокаем алгоритмы”:
[Link]

Ответы на вопросы

1. Big O
«O» большое - математическое обозначение для сравнения асимптотического поведения
(асимптотики) функций.
Под асимптотикой понимается характер изменения функции при стремлении ее
аргумента к определённой точке.
Фраза «сложность алгоритма есть O(f(n)) означает, что с увеличением параметра n,
характеризующего количество входной информации алгоритма, время работы алгоритма
будет возрастать не быстрее, чем некоторая константа, умноженная на f(n).

«О» большое описывает, насколько быстро работает алгоритм. Время выполнения «О»
большое имеет вид О(n). Постойте, но где же секунды? А их здесь нет - «О» большое не
сообщает скорость в секундах, а позволяет сравнить количество операций. Оно указывает,
насколько быстро возрастает время выполнения алгоритма. Такая запись О(n) - сообщает
количество операций, которые придется выполнить алгоритму.
[Link]

2. Временная сложность алгоритмов


Временна́ я сложность алгоритма определяется как функция от длины строки,
представляющей входные данные, равная времени работы алгоритма на данном входе.
Временная сложность алгоритма обычно выражается с использованием нотации Big O,
которая учитывает только слагаемое самого высокого порядка, а также не учитывает
константные множители, то есть коэффициенты.
Временная сложность обычно оценивается путём подсчёта числа элементарных
операций, осуществляемых алгоритмом.
Время исполнения одной такой операции при этом берётся константой, то есть
асимптотически оценивается как O(1).
В таких обозначениях полное время исполнения и число элементарных операций,
выполненных алгоритмом, отличаются максимум на постоянный множитель, который не
учитывается в O-нотации.
В порядке
возрастания сложности:
1. O(1) - константная, чтение по индексу из массива
2. O(log(n)) - логарифмическая, бинарный поиск в отсортированном массиве
3. O(n) - линейная, перебор массива в цикле, два цикла подряд, линейный поиск наименьшего
или наибольшего элемента в неотсортированном массиве
4. O(n*log(n)) - квазилинейная, сортировка слиянием, сортировка кучей
5. O(n^2) - полиномиальная(квадратичная), вложенный цикл, перебор двумерного массива,
сортировка пузырьком, сортировка вставками
6. O(n^3) - кубическая сложность, обычное умножение двух n × n матриц
7. O(2^n) - экспоненциальная, алгоритмы разложения на множители целых чисел
8. O(n!) - факториальная, решение задачи коммивояжёра полным перебором

3. ArrayList vs LinkedList
LinkedList в подавляющем большинстве случаев проигрывает ArrayList, но в оставшемся
меньшинстве он вне конкуренции.
Когда использовать LinkedList:
1. Необходимо много данных добавлять в начало списка
2. Удалять с начала (index = 0) списка, т.е. элементы, которые были добавлены первыми.
3. .set в конце списка
Когда использовать ArrayList:
1. .get
2. .set (начало и середина)
3. .add
4. .remove (кроме начала списка)
Сравнение сложности:
Индекс Поиск Вставка Удаление
ArrayList O(1) O(n) O(n) O(n)
LinkedList O(n) O(n) O(1) O(1)
[Link]
[Link]

4. Устройство HashMap
HashMap - ассоциативный массив, позволяющий хранить пары "ключ-значение". Каждая
ячейка массива - бакет(корзина), хранящая в себе ссылки на списки элементов, узлов(Node).

В корзине может быть один или больше Nod'ов, хранящихся в виде двусвязного
списка. При добавлении новой пары «ключ-значение», вычисляется хэш-код ключа, на
основании которого вычисляется номер корзины (номер ячейки массива), в которую попадает
новый элемент.
Если корзина пустая, то в нее сохраняется ссылка на вновь добавляемый элемент,
если же там уже есть элемент, то происходит последовательный переход по ссылкам между
элементами в цепочке, в поисках последнего элемента, от которого и ставится ссылка на
вновь добавленный элемент.
Если в списке был найден элемент с таким же ключом, то он заменяется (одинаковый хэшкод
и equals)
Если в списке был найден элемент, ключ которого имеет такой же хэшкод, но разный
equals, значит произошла коллизия и в списке может быть больше одного узла(Node);
Если в списке все ключи имеют одинаковый хэшкод, но разный equals, все элементы
добавляются в одну корзину и HashMap теряет в*се свои преимущества, поскольку
вырождается в простой двусвязный список элементов.
[Link]

5. Рекурсия
Рекурсия - способ отображения какого-либо процесса внутри самого этого процесса, то есть
ситуация, когда процесс является частью самого себя.
Для того чтобы понять рекурсию, надо сначала понять рекурсию.
Рекурсия состоит из базового случая и шага рекурсии. Базовый случай представляет
собой самую простую задачу, которая решается за одну итерацию, например, if(n == 0) return
1.
В базовом случае обязательно присутствует условие выхода из рекурсии;
Смысл рекурсии в движении от исходной задачи к базовому случаю, пошагово уменьшая
размер исходной задачи на каждом шаге рекурсии.
После того, как будет найден базовый случай, срабатывает условие выхода из
рекурсии, и стек рекурсивных вызовов разворачивается в обратном порядке, пересчитывая
результат исходной задачи, который основан на результате, найденном в базовом случае.
Так работает рекурсивное вычисление факториала.
Пример на Java:
int factorial(int n) {
if(n == 0) return 1; // в этой строчке базовый случай с условием выхода
else return n * factorial(n-1)
// в этой строчке шаг рекурсии или рекурсивный вызов, который ищет
базовый случай, пошагово уменьшая размер исходной задачи
}
Пример на C из википедии:
int factorial (int n) {
return (n==0) ? 1 : n * factorial(n-1); //такой же if-else как в
примере выше, только в виде тернарного оператора;
}
Рекурсия имеет линейную сложность O(n);
6. Рекурсия vs Iterator
Циклы дают лучшую производительность, чем рекурсивные вызовы, поскольку вызовы
методов потребляют больше ресурсов, чем исполнение обычных операторов. Циклы
гарантируют отсутствие переполнения стека. В случае рекурсии стек вызовов разрастается, и
его необходимо просматривать для получения конечного ответа. При использовании головной
рекурсии также необходимо принимать во внимание размер стека.
[Link]

7. Виды сортировок и их сравнение


Сортировка пузырьком / Bubble sort O(n^2)
Будем идти по массиву слева направо. Если текущий элемент больше следующего, меняем
их местами. Делаем так, пока массив не будет отсортирован. Заметим, что после первой
итерации самый большой элемент будет находиться в конце массива, на правильном месте.
После двух итераций на правильном месте будут стоять два наибольших элемента, и так
далее. Очевидно, не более чем после n итераций массив будет отсортирован. Таким образом,
асимптотика в худшем и среднем случае – O(n^2), в лучшем случае – O(n).
public class BubbleSort {
public static void main(String[] args) {
int[] testData = {10, 4, 43, 5, 4, 67, 12, 0, 99, 19};
[Link]([Link](bubbleSort(testData)));
}

public static int[] bubbleSort(int[] array) {


boolean sorted = false;
int temp;
while(!sorted) {
sorted = true;
for (int i = 0; i < [Link] - 1; i++) {
if (array[i] > array[i+1]) {
temp = array[i];
array[i] = array[i+1];
array[i+1] = temp;
sorted = false;
}
}
}
return array;
}
}
[Link]

Шейкерная сортировка / Shaker sort O(n^2)


Также известна как сортировка перемешиванием и коктейльная сортировка. Заметим, что
сортировка пузырьком работает медленно на тестах, в которых маленькие элементы стоят в
конце (их еще называют «черепахами»). Такой элемент на каждом шаге алгоритма будет
сдвигаться всего на одну позицию влево. Поэтому будем идти не только слева направо, но и
справа налево. Будем поддерживать два указателя begin и end, обозначающих, какой отрезок
массива еще не отсортирован. На очередной итерации при достижении end вычитаем из него
единицу и движемся справа налево, аналогично, при достижении begin прибавляем единицу и
двигаемся слева направо. Асимптотика у алгоритма такая же, как и у сортировки пузырьком,
однако реальное время работы лучше.

Сортировка расчёской / Comb sort O(n^2)


Еще одна модификация сортировки пузырьком. Для того, чтобы избавиться от «черепах»,
будем переставлять элементы, стоящие на расстоянии. Зафиксируем его и будем идти слева
направо, сравнивая элементы, стоящие на этом расстоянии, переставляя их, если
необходимо. Очевидно, это позволит «черепахам» быстро добраться в начало массива.
Оптимально изначально взять расстояние равным длине массива, а далее делить его на
некоторый коэффициент, равный примерно 1.247. Когда расстояние станет равно единице,
выполняется сортировка пузырьком. В лучшем случае асимптотика равна O(n*logn), в худшем
– O(n^2). Какая асимптотика в среднем мне не очень понятно, на практике похоже на
O(n*logn).

Сортировка вставками / Insertion sort O(n^2)


Создадим массив, в котором после завершения алгоритма будет лежать ответ. Будем
поочередно вставлять элементы из исходного массива так, чтобы элементы в массиве-ответе
всегда были отсортированы. Асимптотика в среднем и худшем случае – O(n^2), в лучшем –
O(n). Реализовывать алгоритм удобнее по-другому (создавать новый массив и реально что-то
вставлять в него относительно сложно): просто сделаем так, чтобы отсортирован был
некоторый префикс исходного массива, вместо вставки будем менять текущий элемент с
предыдущим, пока они стоят в неправильном порядке.
public class InsertionSort {
public static void main(String[] args) {
int[] testData = {10, 4, 43, 5, 4, 67, 12,0, 99,19};
[Link]([Link](insertionSort(testData)));
}

public static int[] insertionSort(int[] array) {


for (int i = 1; i < [Link]; i++) {
int current = array[i];
int j = i - 1;
while(j >= 0 && current < array[j]) {
array[j+1] = array[j];
j--;
}
// в этой точке мы вышли, так что j также -1
// или в первом элементе, где текущий >= a[j]
array[j+1] = current;
}
return array;
}
}
[Link]

Сортировка Шелла / Shell sort O(n^2)


Используем ту же идею, что и сортировка с расческой, и применим к сортировке вставками.
Зафиксируем некоторое расстояние. Тогда элементы массива разобьются на классы – в один
класс попадают элементы, расстояние между которыми кратно зафиксированному
расстоянию. Отсортируем сортировкой вставками каждый класс. В отличие от сортировки
расческой, неизвестен оптимальный набор расстояний. Существует довольно много
последовательностей с разными оценками. Последовательность Шелла – первый элемент
равен длине массива, каждый следующий вдвое меньше предыдущего. Асимптотика в
худшем случае – O(n^2). Последовательность Хиббарда – 2n — 1, асимптотика в худшем
случае – O(n1,5), последовательность Седжвика (формула нетривиальна, можете ее
посмотреть по ссылке ниже) — O(n4/3), Пратта (все произведения степеней двойки и тройки)
— O(n*log2n). Отмечу, что все эти последовательности нужно рассчитать только до размера
массива и запускать от большего от меньшему (иначе получится просто сортировка
вставками). Также я провел дополнительное исследование и протестировал разные
последовательности вида si = a * si — 1 + k * si — 1 (отчасти это было навеяно эмпирической
последовательностью Циура – одной из лучших последовательностей расстояний для
небольшого количества элементов). Наилучшими оказались последовательности с
коэффициентами a = 3, k = 1/3; a = 4, k = 1/4 и a = 4, k = -1/5.

Сортировка деревом / Tree sort O(n*logn)


Будем вставлять элементы в двоичное дерево поиска. После того, как все элементы
вставлены достаточно обойти дерево в глубину и получить отсортированный массив. Если
использовать сбалансированное дерево, например красно-черное, асимптотика будет равна
O(n*logn) в худшем, среднем и лучшем случае. В реализации использован контейнер multiset.

Гномья сортировка / Gnome sort


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

Сортировка выбором / Selection sort O(n^2)


На очередной итерации будем находить минимум в массиве после текущего элемента и
менять его с ним, если надо. Таким образом, после i-ой итерации первые i элементов будут
стоять на своих местах. Асимптотика: O(n^2) в лучшем, среднем и худшем случае. Нужно
отметить, что эту сортировку можно реализовать двумя способами - сохраняя минимум и его
индекс или просто переставляя текущий элемент с рассматриваемым, если они стоят в
неправильном порядке. Первый способ оказался немного быстрее.
public class SelectionSort {
public static void main(String[] args) {
int[] testData = {10, 4, 43, 5, 4, 67, 12, 0, 99, 19};
[Link]([Link](selectionSort(testData)));
}

public static int[] selectionSort(int[] array) {


for (int i = 0; i < [Link]; i++) {
int min = array[i];
int minId = i;
for (int j = i + 1; j < [Link]; j++) {
if (array[j] < min) {
min = array[j];
minId = j;
}
}
//замена
int temp = array[i];
array[i] = min;
array[minId] = temp;
}
return array;
}
}
httpsЦ://[Link]/p/java-sorting-algorithms

Пирамидальная сортировка / Heapsort O(n*logn)


Развитие идеи сортировки выбором. Воспользуемся структурой данных «куча» (или
«пирамида», откуда и название алгоритма). Она позволяет получать минимум за O(1),
добавляя элементы и извлекая минимум за O(logn). Таким образом, асимптотика O(n*logn) в
худшем, среднем и лучшем случае.
public class HeapSort {
public static void main(String args[]) {
int[] testData = {8, 0, -3, 5, 6, 9, 8, -4, 2, -99, 43};

HeapSort ob = new HeapSort();


[Link](testData);

[Link]("Sorted array is");


printArray(testData);
}

public void sort(int[] array) {


int n = [Link];
//Построение кучи (перегруппируем массив)
for (int i = n / 2 - 1; i >= 0; i--)
heapify(array, n, i);

//Один за другим извлекаем элементы из кучи


for (int i=n-1; i>=0; i--)
{
//Перемещаем текущий корень в конец
int temp = array[0];
array[0] = array[i];
array[i] = temp;

//Вызываем процедуру heapify на уменьшенной куче


heapify(array, i, 0);
}
}

//Процедура для преобразования в двоичную кучу поддерева с корневым


узлом i, что является
//индексом в arr[]. n - размер кучи
void heapify(int[] arr, int n, int i) {
int largest = i; //Инициализируем наибольший элемент как корень
int l = 2*i + 1; //левый = 2*i + 1
int r = 2*i + 2; //правый = 2*i + 2

//Если левый дочерний элемент больше корня


if (l < n && arr[l] > arr[largest])
largest = l;

//Если правый дочерний элемент больше, чем самый большой элемент


на данный момент
if (r < n && arr[r] > arr[largest])
largest = r;
//Если самый большой элемент не корень
if (largest != i) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;

//Рекурсивно преобразуем в двоичную кучу затронутое поддерево


heapify(arr, n, largest);
}
}

/*Вспомогательная функция для вывода на экран массива размера n*/


static void printArray(int arr[]) {
int n = [Link];
for (int i=0; i < n; ++i)
[Link](arr[i] + " ");
[Link]();
}
}
[Link]

Быстрая сортировка / Quicksort O(n^2)


Выберем некоторый опорный элемент. После этого перекинем все элементы, меньшие его,
налево, а большие – направо. Рекурсивно вызовемся от каждой из частей. В итоге получим
отсортированный массив, так как каждый элемент меньше опорного стоял раньше каждого
большего опорного. Асимптотика: O(n*logn) в среднем и лучшем случае, O(n^2). Наихудшая
оценка достигается при неудачном выборе опорного элемента.
public class QuickSort {
public static void main(String[] args) {
int[] testData = {8, 0, -3, 5, 6, 9, 8, -4, 2, -99, 43};

int low = 0;
int high = [Link] - 1;

quickSort(testData, low, high);


[Link]([Link](testData));
}

public static void ЫйquickSort(int[] array, int low, int high) {


if ([Link] == 0)
return;//завершить выполнение, если длина массива равна 0

if (low >= high)


return;//завершить выполнение если уже нечего делить

//выбрать опорный элемент


int middle = low + (high - low) / 2;
int opora = array[middle];

//разделить на подмассивы, который больше и меньше опорного


элемента
int i = low, j = high;
while (i <= j) {
while (array[i] < opora) {
i++;
}

while (array[j] > opora) {


j--;
}

if (i <= j) {//меняем местами


int temp = array[i];
array[i] = array[j];
array[j] = temp;
i++;
j--;
}
}

//вызов рекурсии для сортировки левой и правой части


if (low < j)
quickSort(array, low, j);

if (high > i)
quickSort(array, i, high);
}
}
[Link]

Сортировка слиянием / Merge sort O(n*logn)


Сортировка, основанная на парадигме «разделяй и властвуй». Разделим массив пополам,
рекурсивно отсортируем части, после чего выполним процедуру слияния: поддерживаем два
указателя, один на текущий элемент первой части, второй – на текущий элемент второй
части. Из этих двух элементов выбираем минимальный, вставляем в ответ и сдвигаем
указатель, соответствующий минимуму. Слияние работает за O(n), уровней всего logn,
поэтому асимптотика O(n*logn). Эффективно заранее создать временный массив и передать
его в качестве аргумента функции. Эта сортировка рекурсивна, как и быстрая, а потому
возможен переход на квадратичную при небольшом числе элементов.
public class MergeSort {
public static void main(String[] args) {
int[] array1 = {8, 0, -3, 5, 6, 9, 8, -4, 2, -99, 43};
int[] result = mergesort(array1);
[Link]([Link](result));
}
public static int[] mergesort(int[] array1) {
int[] buffer1 = [Link](array1, [Link]);
int[] buffer2 = new int[[Link]];
int[] result = mergesortInner(buffer1, buffer2, 0, [Link]);
return result;
}

/**
* @param buffer1 Массив для сортировки.
* @param buffer2 Буфер. Размер должен быть равен размеру buffer1.
* @param startIndex Начальный индекс в buffer1 для сортировки.
* @param endIndex Конечный индекс в buffer1 для сортировки.
* @return
*/
public static int[] mergesortInner(int[] buffer1, int[] buffer2,
int startIndex, int endIndex) {
if (startIndex >= endIndex - 1) {
return buffer1;
}

// уже отсортирован.
int middle = startIndex + (endIndex - startIndex) / 2;
int[] sorted1 = mergesortInner(buffer1, buffer2, startIndex,
middle);
int[] sorted2 = mergesortInner(buffer1, buffer2, middle,
endIndex);

// Слияние
int index1 = startIndex;
int index2 = middle;
int destIndex = startIndex;
int[] result = sorted1 == buffer1 ? buffer2 : buffer1;
while (index1 < middle && index2 < endIndex) {
result[destIndex++] = sorted1[index1] < sorted2[index2]
? sorted1[index1++] : sorted2[index2++];
}
while (index1 < middle) {
result[destIndex++] = sorted1[index1++];
}
while (index2 < endIndex) {
result[destIndex++] = sorted2[index2++];
}
return result;
}
}
[Link]
%D1%80%D0%B8%D1%82%D0%BC-%D1%81%D0%BE
%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-%D1%81%D0%BB
%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC-%D0%BD%D0%B0-java/

Сортировка подсчетом / Counting sort


Создадим массив размера r – l, где l – минимальный, а r – максимальный элемент массива.
После этого пройдем по массиву и подсчитаем количество вхождений каждого элемента.
Теперь можно пройти по массиву значений и выписать каждое число столько раз, сколько
нужно. Асимптотика – O(n + r — l). Можно модифицировать этот алгоритм, чтобы он стал
стабильным: для этого определим место, где должно стоять очередное число (это просто
префиксные суммы в массиве значений) и будем идти по исходному массиву слева направо,
ставя элемент на правильное место и увеличивая позицию на 1. Эта сортировка не
тестировалась, поскольку большинство тестов содержало достаточно большие числа, не
позволяющие создать массив требуемого размера. Однако она, тем не менее, пригодилась.

Поразрядная сортировка / Radix sort


Также известна как цифровая сортировка. Существует две версии этой сортировки, в которых,
мало общего, кроме идеи воспользоваться представлением числа в какой-либо системе
счисления (например, двоичной).
LSD (least significant digit): Представим каждое число в двоичном виде. На каждом шаге
алгоритма будем сортировать числа таким образом, чтобы они были отсортированы по
первым k * i битам, где k – некоторая константа. Из данного определения следует, что на
каждом шаге достаточно стабильно сортировать элементы по новым k битам. Для этого
идеально подходит сортировка подсчетом (необходимо 2k памяти и времени, что немного при
удачном выборе константы). Асимптотика: O(n), если считать, что числа фиксированного
размера (а в противном случае нельзя было бы считать, что сравнение двух чисел
выполняется за единицу времени).
MSD (most significant digit): На самом деле, некоторая разновидность блочной сортировки. В
один блок будут попадать числа с равными k битами. Асимптотика такая же, как и у LSD
версии. Реализация очень похожа на блочную сортировку, но проще. В ней используется
функция digit, определенная в реализации LSD версии.

Битонная сортировка / Bitonic sort


Идея данного алгоритма заключается в том, что исходный массив преобразуется в битонную
последовательность – последовательность, которая сначала возрастает, а потом убывает. Ее
можно эффективно отсортировать следующим образом: разобьем массив на две части,
создадим два массива, в первый добавим все элементы, равные минимуму из
соответственных элементов каждой из двух частей, а во второй – равные максимуму.
Утверждается, что получатся две битонные последовательности, каждую из которых можно
рекурсивно отсортировать тем же образом, после чего можно склеить два массива (так как
любой элемент первого меньше или равен любого элемента второго). Для того, чтобы
преобразовать исходный массив в битонную последовательность, сделаем следующее: если
массив состоит из двух элементов, можно просто завершиться, иначе разделим массив
пополам, рекурсивно вызовем от половинок алгоритм, после чего отсортируем первую часть
по порядку, вторую в обратном порядке и склеим. Очевидно, получится битонная
последовательность. Асимптотика: O(n*log2n), поскольку при построении битонной
последовательности мы использовали сортировку, работающую за O(n*logn), а всего уровней
было logn. Также заметим, что размер массива должен быть равен степени двойки, так что,
возможно, придется его дополнять фиктивными элементами (что не влияет на асимптотику).

Timsort Гибридная сортировка, совмещающая сортировку вставками и сортировку


слиянием. Разобьем элементы массива на несколько подмассивов небольшого размера, при
этом будем расширять подмассив, пока элементы в нем отсортированы. Отсортируем
подмассивы сортировкой вставками, пользуясь тем, что она эффективно работает на
отсортированных массивах. Далее будем сливать подмассивы как в сортировке слиянием,
беря их примерно равного размера (иначе время работы приблизится к квадратичному). Для
этого удобного хранить подмассивы в стеке, поддерживая инвариант - чем дальше от
вершины, тем больше размер, и сливать подмассивы на верхушке только тогда, когда размер
третьего по отдаленности от вершины подмассива больше или равен сумме их размеров.
Асимптотика: O(n) в лучшем случае и O(n*logn) в среднем и худшем случае.
[Link]

8. Виды поиска и их сравнение


Линейный поиск - O(n);
Бинарный поиск - O(log(n)); сложность ниже, но массив должен быть отсортирован
Поиск в глубину и в ширину в деревьях;
9. Жадный алгоритм
Жадный алгоритм (greedy algorithm) - это алгоритм, который на каждом шагу делает
локально наилучший выбор в надежде, что итоговое решение будет оптимальным. К примеру,
алгоритм Дейкстры нахождения кратчайшего пути в графе вполне себе жадный, потому что
мы на каждом шагу ищем вершину с наименьшим весом, в которой мы еще не бывали, после
чего обновляем значения других вершин. При этом можно доказать, что кратчайшие пути,
найденные в вершинах, являются оптимальными.
К слову, алгоритм Флойда, который тоже ищет кратчайшие пути в графе (правда,
между всеми вершинами), не является примером жадного алгоритма.
Флойд демонстрирует другой метод — метод динамического программирования.
Есть область применимости жадных алгоритмов.
Общих рецептов тут нет, но есть довольно мощный инструмент, с помощью которого в
большинстве случаев можно определить, даст ли жадина оптимальное решение. Этот
инструмент - матроид.
Матроид — это пара (X, I), где X — конечное множество, называемое носителем
матроида, а I — некоторое множество подмножеств X, называемое семейством независимых
множеств. При этом должны выполняться следующие условия: - Множество I непусто. Даже
если исходное множество X было пусто — X = Ø, то I будет состоять из одного элемента —
множества, содержащего пустое. I = {{Ø}} - Любое подмножество любого элемента множества
I также будет элементом этого множества. - Если множества A и B принадлежат множеству I,
а также известно, что размер А меньше B, то существует какой-нибудь элемент x из B, не
принадлежащий А, такое что объединение x и A будет принадлежать множеству I. Это
свойство является не совсем тривиальным, но чаще всего наиважшейшим из всех остальных.

10. Queue, Deque, stack, Heap


Queue - это очередь, которая обычно (но необязательно) строится по принципу FIFO
(First-In-First-Out) - соответственно извлечение элемента осуществляется с начала очереди,
вставка элемента - в конец очереди.
Хотя этот принцип нарушает, к примеру PriorityQueue, использующая «natural ordering»
или переданный Comparator при вставке нового элемента.
Deque (Double Ended Queue) расширяет Queue и согласно документации это линейная
коллекция, поддерживающая вставку/извлечение элементов с обоих концов. Помимо этого
реализации интерфейса Deque могут строится по принципу FIFO, либо LIFO.
Реализации и Deque, и Queue обычно не переопределяют методы equals() и
hashCode(), вместо этого используются унаследованные методы класса Object, основанные
на сравнении ссылок.
Heap (куча) используется Java Runtime для выделения памяти под объекты и классы.
Создание нового объекта также происходит в куче. Это же является областью работы
сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут
ссылаться из любой части приложения.
Stack (стек) это область хранения данных также находящееся в общей оперативной
памяти (RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок,
который содержит примитивы и ссылки на другие объекты в методе. Как только метод
заканчивает работу, блок также перестает использоваться, тем самым предоставляя доступ
для следующего метода. Размер стековой памяти намного меньше объема памяти в куче.
Стек в Java работает по схеме LIFO (Последний зашел - Первый вышел)
Различия между Heap и Stack памятью:
- Куча используется всеми частями приложения в то время как стек используется
только одним потоком исполнения программы.
- Всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека
содержится лишь ссылка на него.
- Память стека содержит только локальные переменные примитивных типов и ссылки
на объекты в куче.
- Объекты в куче доступны с любой точке программы, в то время как стековая память
не может быть доступна для других потоков.
- Стековая память существует лишь какое-то время работы программы, а память в
куче живет с самого начала до конца работы программы.
- Если память стека полностью занята, то Java Runtime бросает исключение
[Link].
- Если заполнена память кучи, то бросается исключение [Link]:
Java Heap Space.
- Размер памяти стека намного меньше памяти в куче.
- Из-за простоты распределения памяти, стековая память работает намного быстрее
кучи.

11. EnumSet
EnumSet - это специализированная коллекция Set для работы с enum классами.
Он реализует интерфейс Set и расширяется от AbstractSet. Хотя AbstractSet и
AbstractCollection предоставляют реализации почти для всех методов интерфейсов Set и
Collection, EnumSet переопределяет большинство из них.
Когда мы планируем использовать EnumSet, мы должны принять во внимание некоторые
важные моменты:
- Может содержать только enum значения и все значения должны принадлежать к тому
же enum.
- Не позволяет добавлять нулевые значения, выбрасывая NullPointerException в попытке
это сделать.
- EnumSet не потоко-безопасный, поэтому нам нужно его синхронизировать.
- Элементы хранятся в том порядке, в котором они объявлены в enum.
- EnumSet использует отказоустойчивый итератор, который работает с копией, поэтому
он не выбрасывает ConcurrentModificationException, если коллекция изменяется при итерации.
Всегда следует отдавать предпочтение использованию EnumSet перед любой другой
реализацией Set, когда мы храним значения enum.
Все методы в EnumSet реализованы с использованием арифметических побитовых
операций. Эти вычисления очень быстрые, и поэтому все основные операции выполняются за
константное время O(1).

12. Бинарное дерево


Бинарное дерево - иерархическая структура данных, в которой каждый узел имеет не более
двух потомков (детей). Как правило, первый называется родительским узлом, а дети
называются левым и правым наследниками. Каждый узел в дереве задаёт поддерево, корнем
которого он является. Оба поддерева — левое и правое — являются двоичными деревьями.
У всех узлов левого поддерева произвольного узла X значения ключей данных меньше,
нежели значение ключа данных самого узла X. У всех узлов правого поддерева
произвольного узла X значения ключей данных больше либо равны, нежели значение ключа
данных самого узла X.
[Link]

13. Красно-чёрное дерево


Красно-черное дерево - один из видов самобалансирующихся двоичных деревьев поиска,
гарантирующих логарифмический рост высоты дерева от числа узлов и позволяющее быстро
выполнять основные операции дерева поиска: добавление, удаление и поиск узла.
Сбалансированность достигается за счёт введения дополнительного атрибута узла
дерева — «цвета».
Этот атрибут может принимать одно из двух возможных значений — «чёрный» или
«красный».
1. Узел может быть либо красным, либо черным и имеет двух потомков.
2. Корень — как правило чёрный. Это правило слабо влияет на работоспособность модели,
так как цвет корня всегда можно изменить с красного на чёрный.
3. Все листья — черные и не содержат данных.
4. Оба потомка каждого красного узла — черные.
5. Любой простой путь от узла-предка до листового узла-потомка содержит одинаковое число
черных узлов.
Благодаря этим ограничениям, путь от корня до самого дальнего листа не более чем
вдвое длиннее, чем до самого ближнего и дерево примерно сбалансировано.
Операции вставки, удаления и поиска требуют в худшем случае времени,
пропорционального длине дерева, что позволяет красно-черным деревьям быть более
эффективными в худшем случае, чем обычные двоичные деревья поиска.
[Link]
[Link]

14. Мемоизация
Мемоизация - один из способов оптимизации, для увеличения скорости выполнения
программ - сохранение результатов выполнения функций для предотвращения повторных
вычислений.
Перед вызовом функции проверяется, вызывалась ли функция ранее:
- если не вызывалась, то функция вызывается, и результат её выполнения
сохраняется.
- если вызывалась, то используется сохраненный результат. Может применяться,
чтобы сократить количество дублирующих рекурсивных вызовов
[Link]

Вам также может понравиться