You are on page 1of 39

Questions

16 марта 2021 г.
18:45
1. Особливості Java
2. Способи створення String
3. String Pool і навіщо він потрібен
4. Відмінність процесу та потоку
5. Мінімальна к-сть потоків
6. Стани потоків
7. Поток демона
8. Разница runnable/callable
9. Разница между синхронизированным методом и блоком
10. Средства синхронизации
11. CyclicBarrier, CountDownLatch
12. Hibernate vs JPA
13. Select n+1 проблема
14. Этапы жизненного цикла бина
15. Динамічний і статичний байдінг (зв'язування)
16. Кап теорема
17. Чому в джава заборонене множинне наслідування
18. Тернарний оператор
19. Для чого потрібні колекції
20. Інтерфейси колекцій
21. Як знайти всі ключі з мапи
22. Типи операторів в стрімах
23. Для чого потрібні стріми
24. Для чого потрібні ексепшени
25. Що використовували до трай з ресурсами
26. Які є бд
27. На чому базуються реляційні бази даних
28. Рівні ізоляції транзакцій
29. JDBC
30. Spring
31. Чи можна передавати в GET тіло
32. Чому паролі передаються в тілі запиту
 
 
 
1. Принципи ООП
Поліморфізм
Інкапсуляція
Наслідування
2. SOLID
Як принципи виглядають в Java
3. Типи даних
Для чого байти в Java
4. Ключові слова static, final
5. Модифікатори доступу
6. Методи класу Object(8)
hashCode()
Контракт між hashCode() & equals()
Правила перевизначення equals() - 6
7. Immutable
Як створити immutable
Як зробити поля, які є об'єктами immutable(створити клон об'єкту або deepCopy())
8. Collections
Ієрархія колекцій
Як працюють всередині ArrayList & LinkedList
Що краще: ArrayList & LinkedList? Плюси і мінуси
9. Map
Як працює HashMap
Швидкість get() & put() HashMap - O(n)
Як позбутись колізії в HashMap
BlackTree - O(logN)
Що краще використовувати для ключа в HashMap
10. Інтерфейс і абстрактний клас
Яка різниця між ними
Що з'явилось в інтерфейсах в різних версіях Java
Diamond Problem в інтерфейсах(ромб смерті)
11. Exception
Ієрархія виключень
На що діляться Exceptions
Checked & Unchecked exceptions
Для чого обробляти виключення
Чи можна обробляти unchecked exceptions
try with resources
12. Generics
Для чого вони потрібні
PECS
13. Spring
Що, для чого, основні фішки, плюси
dependency injection, inversion of control
Bean
@Autowired
Життєвий цикл Bean
10 анотацій Spring
Scope Beans
14. Паттерни
Які є паттерни
Де вони використовуються
15. ACID
16. Join, leftJoin/rightJoin, orderBy, where, groupBy, having, distinct
17. Індекси
18. Які області пам'яті є в Java
String Pool
19. Багатопоточність
Процес
Поток
Чим загрожує багатопоточність
synchronized
volatile
Монітор
Потокобезпечні колекції
20. Тестування
Види тестування
Mock
21. Git
Git Rebase
Git Commit — amend
Git Merge
22. REST
Ресурси
HTTP методи
Безпечні та ідемпотентні методи
PUT vs PATCH
23. Алгоритми
 

1. Коли використовувати абстрактний клас/інтерфейс


2. Які методи є в інтерфейсах(статичні, дефолтні, звичайні приватні)
3. Дефолтна реалізація хешкоду
4. Складність пошуку по хешмапі - О(logN), коли перетворюється в дерево
5. TreeMap, що потрібно, щоб об'єкт попав в неї - comparator
6. Чи перебудовується хешмап в дерево, якщо не перевизначений Comparator
7. ConcurrentHashMap
8. Циклічний inject
9. Singletone, чи потокобезпечний
10. Особливість анотацій Repository, Controller, Service
11. Spring Boot особливість
12. Нормалізація бд
13. Как остановить поток?
14. Что такое fail-fast и fail-safe итераторы?
15. Когда нужно использовать raw types?
16. Чем отличается CountDownLatch от CyclicBarrier?
17. Какая разница между @Controller и @RestController?
18. Чем анонимный внутренний класс отличается от лямбды?
19. Зачем выбирать ReentrantLock вместо synchronized?
20. Что происходит внутри HashMap.put()?
21. Что происходит внутри TreeMap.put()?
22. Spring Actuator
23. Утечки памяти

Answers Part 3
10 марта 2022 г.
22:20
 
1. Особливості Java
 
1. Простота
Простой в изучении и эффективный в употреблении профессиональными программистами.
2. Безопасность
Java обеспечивает несколько уровней зашиты от вредоносного ПО.
3. Объектная ориентированность
В Java все является объектом.
4. Надежность
Большое внимание в языке Java уделено раннему обнаружению возможных ошибок, динамической
проверке (во время выполнения программы), а также исключению ситуаций, которые могут привести
к ошибкам.
5. Многопоточность
Возможность реализации программ, которые выполняют множество задач одновременно.
6. Архитектурная нейтральность и переносимость
Написано однажды, выполняется везде, в любое время года и всегда. В то время, когда язык Java
разрабатывался, в среде программирования существовала проблема - если программа была написана
под одну ОС, ее было тяжело или же просто невозможно запустить под другой ОС. Разработчики
языка попытались решить эту проблему, и в принципе у них это получилось - программа, написанная
на Java выполняется одинаково (ну, почти одинаково) на всех OC и компьютерах с разной
архитектурой.
 
2. Способи створення String
 
New + String(),
String(String str),
String(byte[] asciichar),
String(char[] unicodechar),
String(StringBuffer sbuf),
String(StringBuilder sbuild) и др.
Сразу инициализировать
 
3. String Pool і навіщо він потрібен
 
Строковый пул или String pool — это особое место в heap’е, куда попадают объекты типа String после
их создания. Он выполняет функцию кеша строк. Каждый раз, когда Вы создаёте строку, она попадает
в строковый пул. Если же на момент создания новой строки пул уже содержит такое же значение, то
вместо создания нового объекта возвращается тот, что уже лежит в пуле.
 
4. Відмінність процесу та потоку
 
Разница между процессом и потоком заключается в том, что процесс - это исполняемая программа, а
поток - это небольшая исполнительная единица в процессе. Создание процесса сложно, но создание
потока экономично. Кроме того, процессы требуют значительных ресурсов, а потоки требуют
минимальных ресурсов.
 
5. Мінімальна к-сть потоків - 1
6. Стани потоків
 
Поток может находиться в одном из следующих состояний:
 
New - объект класса Thread создан, но еще не запущен. Он еще не является потоком выполнения и
естественно не выполняется.
Runnable - поток готов к выполнению, но планировщик еще не выбрал его.
Running – поток выполняется.
Waiting/blocked/sleeping - поток блокирован или поток ждет окончания работы другого потока.
Dead - поток завершен. Будет выброшено исключение при попытке вызвать метод start() для dead
потока.
Существуют перечисление Thread.State, содержащее значения возможных состояний потока: NEW,
RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.
 
Получить текущее значение состояния потока можно вызовом метода getState() класса Thread.
 
7. Поток демона
 
Потоками-демонами называются потоки, работающие в фоновом режиме для нашей программы.
 
В Java процесс завершается тогда, когда завершается последний его поток. Даже если метод main()
уже завершился, но еще выполняются порожденные им потоки, система будет ждать их завершения.
Однако это правило не относится к особому виду потоков – демонам. Если завершился последний
обычный поток процесса, и остались только потоки-демоны, то они будут принудительно завершены и
выполнение процесса закончится. Чаще всего потоки-демоны используются для выполнения фоновых
задач, обслуживающих процесс в течение его жизни.
Объявить поток демоном достаточно просто — нужно перед запуском потока вызвать его метод
setDaemon(true);
Проверить, является ли поток демоном, можно вызвав его метод boolean isDaemon();
 
 
8. Разница runnable/callable
 
Интерфейс Runnable определяет метод run() без какого-либо возвращаемого значения, тогда как
интерфейс Callable позволяет call() метода call() возвращать значение и генерировать исключение.
 
9. Разница между синхронизированным методом и блоком
 
 синхронизированный блок уменьшает область блокировки, но область действия
синхронизированного метода — это целый метод.
 синхронизированный блок имеет лучшую производительность, так как блокируется только
критическая секция, но синхронизированный метод имеет низкую производительность, чем
блок.
 синхронизированный блок обеспечивает детальный контроль над блокировкой, но
синхронизированный метод блокирует либо текущий объект, представленный этим, либо
блокировку уровня класса.
 Синхронизированный блок может генерировать исключение NullPointerException, но
синхронизированный метод не генерирует.
 синхронизированный блок: синхронизированный (это) {}
 синхронизированный метод: public синхронизированный void fun () {}
 
 Средства синхронизации
 
Ниже приведены некоторые способы синхронизации в Java:
 
Системная синхронизация с использованием wait/notify. Поток, который ждет выполнения каких-либо
условий, вызывает у этого объекта метод wait, предварительно захватив его монитор. На этом его
работа приостанавливается. Другой поток может вызвать на этом же самом объекте метод notify
(опять же, предварительно захватив монитор объекта), в результате чего, ждущий на объекте поток
"просыпается" и продолжает свое выполнение.
Системная синхронизация с использованием join. Метод join, вызванный у экземпляра класса Thread,
позволяет текущему потоку остановиться до того момента, как поток, связаный с этим экземпляром,
закончит работу.
Использование классов из пакета java.util.concurrent, который предоставляет набор классов для
организации межпоточного взаимодействия. Примеры таких классов - Lock, семафор (Semaphore), etc.
Концепция данного подхода заключается в использовании атомарных операций и переменных.
 
 
11. CyclicBarrier, CountDownLatch
 
Класс SDK CountDownLatch предоставляет вспомогательное средство синхронизации, которое можно
использовать для реализации сценариев, в которых потокам приходится ждать, пока некоторые
другие потоки не достигнут того же состояния, чтобы все потоки могли запускаться. Это делается
путем предоставления синхронизированного счетчика, который уменьшается до тех пор, пока не
достигнет нулевого значения. Достигнув нуля, экземпляр CountDownLatch позволяет всем потокам
продолжаться. Это можно использовать для запуска всех потоков в определенный момент времени с
помощью значения 1 для счетчика или для ожидания завершения нескольких потоков. В последнем
случае счетчик инициализируется числом потоков, и каждый завершивший работу поток считает
защелку на единицу.
 
Оба класса SDK поддерживают внутренний счетчик, который уменьшается различными потоками.
Потоки ждут, пока внутренний счетчик не достигнет нулевого значения, и продолжат с этого момента.
Но в отличие от CountDownLatch класс CyclicBarrier сбрасывает внутреннее значение обратно к
начальному значению, как только значение достигает нуля. Поскольку имя указывает, что экземпляры
CyclicBarrier могут поэтому использоваться для реализации случаев использования, когда потокам
приходится ждать друг друга снова и снова.
 
12. Hibernate vs JPA
 

 
JPA (Java Persistence API) это спецификация Java EE и Java SE, описывающая систему управления
сохранением java объектов в таблицы реляционных баз данных в удобном виде. Сама Java не
содержит реализации JPA, однако есть существует много реализаций данной спецификации от разных
компаний (открытых и нет). Это не единственный способ сохранения java объектов в базы данных
(ORM систем), но один из самых популярных в Java мире.
 
Hibernate одна из самых популярных открытых реализаций последней версии спецификации (JPA 2.1).
Даже скорее самая популярная, почти стандарт де-факто. То есть JPA только описывает правила и
API( API (Application Programming Interface — программный интерфейс приложения, или интерфейс
программирования приложений) — специальный протокол для взаимодействия компьютерных
программ, который позволяет использовать функции одного приложения внутри другого. ), а
Hibernate реализует эти описания, впрочем у Hibernate (как и у многих других реализаций JPA) есть
дополнительные возможности, не описанные в JPA (и не переносимые на другие реализации JPA).
 
13. Select n+1 проблема
 
N+1 проблема в Hibernate состоит в том, в некоторых ситуациях один HQL select преобразуется N+1
SQL select-ов. Это отрицательно влияет на производительность, поэтому такого поведения нужно
избегать.
Эти дополнительные SQL select-ы нужны для заполнения поля, ссылающегося на другую сущность(и).
Здесь N — количество объектов, возвращаемых первым явным select-ом. Для каждого из них надо
заполнить поле, вот и получается еще N select-ов.
Для того, чтобы избежать N+1 проблемы, рекомендуется выбирать объекты, ссылающиеся на другую
сущность с помощью join fetch, а не с помощью обычного select.
 
14. Этапы жизненного цикла бина
 
1. Инстанцирование объекта. Техническое начало жизни бина, работа конструктора его класса;
2. Установка свойств из конфигурации бина, внедрение зависимостей;
3. Нотификация aware-интерфейсов. BeanNameAware, BeanFactoryAware и другие. Мы уже писали о
таких интерфейсах ранее. Технически, выполняется системными подтипами BeanPostProcessor, и
совпадает с шагом 4;
4. Пре-инициализация – метод postProcessBeforeInitialization() интерфейса BeanPostProcessor;
5. Инициализация. Разные способы применяются в таком порядке:
• Метод бина с аннотацией @PostConstruct из стандарта JSR-250 (рекомендуемый способ);
• Метод afterPropertiesSet() бина под интерфейсом InitializingBean;
• Init-метод. Для отдельного бина его имя устанавливается в параметре определения initMethod. В
xml-конфигурации можно установить для всех бинов сразу, с помощью default-init-method;
6. Пост-инициализация – метод postProcessAfterInitialization() интерфейса BeanPostProcessor.
Когда IoC-контейнер завершает свою работу, мы можем кастомизировать этап штатного уничтожения
бина. Как со всеми способами финализации в Java, при жестком выключении (kill -9) гарантии вызова
этого этапа нет. Три альтернативных способа «деинициализации» вызываются в том же порядке, что
симметричные им методы инициализации:
1. Метод с аннотацией @PreDestroy;
2. Метод с именем, которое указано в свойстве destroyMethod определния бина (или в глобальном
default-destroy-method);
3. Метод destroy() интерфейса DisposableBean.
 
15. Динамічний і статичний байдінг (зв'язування)
 
Связывание означает наличие связи между ссылкой и кодом. Существует два типа связывания
методов в языке Java: ранее связывание (его ещё называют статическим) и позднее (соответственно,
динамическое) связывание.
Теперь, когда вы разобрались и понимаете, как в языке Java связываются вызовы методов и как
функционирует статическое и динамическое связывание, давайте еще раз перечислим ключевые
различия между ранним и поздним связыванием в языке Java:
Статическое связывание происходит во время компиляции, а динамическое – во время выполнения.
 
Поскольку статическое связывание происходит на ранней стадии жизненного цикла программы, его
называют ранним связыванием. Аналогично, динамическое связывание называют также поздним
связыванием, поскольку оно происходит позже, во время работы программы.
 
Статическое связывание используется в языке Java для разрешения перегруженных методов, в то
время как динамическое связывание используется в языке Java для разрешения переопределенных
методов.
 
Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического
связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при
помощи динамического связывания.
 
В случае статического связывания используются не конкретные объекты, а информация о типе, то есть
для обнаружения нужного метода используется тип ссылочной переменной. С другой стороны, при
динамическом связывании для нахождения нужного метода в Java используется конкретный объект.
 
16. Кап теорема
 
В CAP говорится, что в распределенной системе возможно выбрать только 2 из 3-х свойств:
 
C (consistency) — согласованность. Каждое чтение даст вам самую последнюю запись.
A (availability) — доступность. Каждый узел (не упавший) всегда успешно выполняет запросы (на
чтение и запись).
P (partition tolerance) — устойчивость к распределению. Даже если между узлами нет связи, они
продолжают работать независимо друг от друга.
 
17. Чому в джава заборонене множинне наслідування
 
Java не поддерживает множественное наследование классов потому, что это может привести к
ромбовидной проблеме.
 
18. Тернарний оператор
 
 
19. Для чого потрібні колекції
 
Коллекция - это объект, способный хранить группу одинаковых элементов. Она содержит методы для
операций с однородными данными.
 
20. Як знайти всі ключі з мапи
 
keySet() — возвращает множество(Set) ключей;
values() — возвращает коллекцию(Collection) значений;
entrySet() — возвращает множество(Set) наборов “ключ-значение”
 
21. Типи операторів в стрімах
 
Операции над стримами делятся на конвеерные и терминальные. Конвеерные операции выполняют
какое-либо действие и возвращают стрим. Терминальные операции возвращают результат обработки,
не являющийся стримом.
 
22. Для чого потрібні стріми
 
Stream API это новый способ работать со структурами данных в функциональном стиле. Чаще всего с
помощью stream в Java 8 работают с коллекциями, но на самом деле этот механизм может
использоваться для самых различных данных.
 
23. Для чого потрібні ексепшени
 
Использование исключений в Java позволяет повысить отказоустойчивость программы за счет
использования «запасных» путей, отделить логику основного кода от кода обработки исключительных
ситуаций за счет использования блоков catch, а также дает нам возможность переложить обработку
исключений на пользователя нашего кода с помощью throws.
 
24. Що використовували до трай з ресурсами
 
До Java 7 мы закрывали объект в finally block, чтобы он безопасно освободил объект в случае
возникновения какого-либо исключения. Теперь мы можем заменить finally с оператором try-with-
resource, который автоматически закрывает объект.
 
25. Які є бд
 
По модели данных СУБД бывают:
Иерархические. В этой модели данных используется представление БД в виде древовидной
структуры, состоящей из данных разных уровней.
Сетевые. Данная модель является расширением иерархического подхода. Иерархическая модель
подразумевает, что запись-потомок может иметь строго одного предка, в то время как в сетевой
структуре потомок может иметь любое количество предков.
Реляционные. СУБД, ориентированные на организацию данных как набор связанных записей и
атрибутов в двумерной таблице.
Объектно-ориентированные. Для управления БД, основанными на объектной модели данных. Как
правило основываются на объектно-ориентированных языках программирования.
Объектно-реляционные. Объединяет в себе концепции реляционной модели с дополнительными
объектно-ориентированными возможностями.
 
26. На чому базуються реляційні бази даних
 
Реляционные. СУБД, ориентированные на организацию данных как набор связанных записей и
атрибутов в двумерной таблице.
 
27. Рівні ізоляції транзакцій
 
Read uncommitted
Уровень, имеющий самую плохую согласованность данных, но самую высокую скорость выполнения
транзакций. Название уровня говорит само за себя — каждая транзакция видит незафиксированные
изменения другой транзакции (феномен грязного чтения). Посмотрим какое влияние оказывают друг
на друга такие транзакции.
 
Read committed
Для этого уровня параллельно исполняющиеся транзакции видят только зафиксированные изменения
из других транзакций. Таким образом, данный уровень обеспечивает защиту от грязного чтения.
 
Repeatable read
Уровень, позволяющий предотвратить феномен неповторяющегося чтения. Уровень, при котором
читающая транзакция «не видит» изменения данных, которые были ею ранее прочитаны. При этом
никакая другая транзакция не может изменять данные, читаемые текущей транзакцией, пока та не
окончена.
 
Serializable
Уровень, при котором транзакции ведут себя как будто ничего более не существует, никакого влияния
друг на друга нет. В классическом представлении этот уровень избавляет от эффекта чтения фантомов.
 
28. JDBC
 
JDBC – это стандарт взаимодействия приложения с различными СУБД. JDBC основан на концепции
драйверов, позволяющей получать соединение с БД по специальному url. JDBC API находятся в
пакетах java.sql и javax.sql. С помощью JDBC API можно создавать соединения с БД, выполнять SQL
запросы, хранимые процедуры и обрабатывать результаты. JDBC API упрощает работу с базами
данных из Java программ.
 
29. Spring
 
 
30. Чи можна передавати в GET тіло - Нет
31. Чому паролі передаються в тілі запиту
 
URL хранятся в логах веб сервера вместе со всеми параметрами. Это значит, что любые секретные
данные будут сохранены в этих самых логах.

Answers Part 1
10 марта 2022 г.
21:20
 
1. Принципи ООП
 
Полиморфизм - возможность применения одноименных методов с одинаковыми или
различными наборами параметров в одном классе или в группе классов, связанных отношением
наследования.
 
Инкапсуляция – это принцип, согласно которому любой класс и в более широком смысле –
любая часть системы должны рассматриваться как «черный ящик»: пользователь класса или
подсистемы должен видеть только интерфейс (т.е. список декларируемых свойств и методов) и
не вникать во внутреннюю реализацию. Создавая новый класс, Вы должны думать не только о
функциональности, но и о безопасности - кто и при каких условиях может получать доступ к
внутренностям Вашего класса.
 
Наследование — это возможность порождать один класс от другого с сохранением всех свойств
и методов класса-предка (суперкласса), добавляя при необходимости новые свойства и методы.
 
Абстракция означает выделение главных, наиболее значимых характеристик предмета и
наоборот — отбрасывание второстепенных, незначительных.
 
2. SOLID
 
S - Single Responsibility
Класс должен иметь одну причину для изменения(отвечать за что-то одно) — это означает, что
изменения должны исходить от одной группы/роли людей, например модуль должен меняться
только по запросам бизнес-аналитика, дизайнера, DBA специалиста, бухгалтера или юриста.
 
O - Open Closed
Классы должны быть открыты для расширения, но закрыты для модификации.
 
L - Liskov Substitution
Этот принцип требует, что бы объекты "подтипа" можно было подставить в любую программу
вместо объектов родительского типа и поведение программы не должно поменяться. Обычно
имеется в виду не полная идентичность поведения, а то, что ничего не сломается.
 
I - Interface Segregation
Этот принцип говорит о том, что интерфейсы следует делать настолько минимальными,
насколько это возможно, разделяя большие интерфейсы на мелкие по способам использования.
 
D - Dependency Inversion
Этот принцип часто путают с Dependency Injection, но этот принцип о другом — он
требует использования абстракций где это возможно, причем абстракций любого
рода:
Int first(ArrayList<Integer> xs)// ArrayList это деталь реализации ->
Int first(Collection<Integer> xs)// Collection это абстракция ->
<T> T first(Collection<T> xs)// но и тип элемента коллекции это только деталь реализации
 
3. Типы данных
 
Примитивные типы:
 byte (целые числа, 1 байт)
 short (целые числа, 2 байта)
 int (целые числа, 4 байта)
 long (целые числа, 8 байтов)
 float (вещественные числа, 4 байта)
 double (вещественные числа, 8 байтов)
 char (символ Unicode, 2 байта)
 boolean (значение истина/ложь, 1 бит)
 
Для чего используют byte? Его используют при работе с файлами
 
 Static, Final
 
Static
Мы можем использовать это ключевое слово в четырех контекстах:
 статические методы - также называются методами класса, потому что статический метод
принадлежит классу, а не его объекту. Кроме того, статические методы можно вызывать
напрямую через имя класса.
 статические переменные - если мы объявим переменную статической, все объекты класса
будут использовать одну и ту же статическую переменную.
 статические вложенные классы - в Java можно объявить класс внутри другого класса. Такие
классы называются вложенными классами. Они бывают двух типов: статические и
нестатические.
Вложенный класс является членом заключающего его класса. Нестатические вложенные классы
(внутренние классы) имеют доступ к другим членам заключающего класса, даже если они
объявлены приватными. Статические вложенные классы не имеют доступа к другим членам
заключающего класса.
Внутренний класс по умолчанию содержит неявную ссылку на объект внешнего класса. Если вы
создадите экземпляр этого объекта из кода внешнего класса, все будет сделано за вас. Если вы
поступите иначе, вам необходимо предоставить объект самостоятельно.
Со статическим внутренним классом все иначе. Можно сказать, что если у внутреннего класса
нет причин для доступа к внешнему, вы должны сделать его статическим по умолчанию.
 статические блоки - их применяют для инициализации статических переменных. Статический
блок выполняется только один раз, когда класс загружается в память. Это происходит, если в
коде запрашивается либо объект класса, либо статические члены этого класса. Класс может
содержать несколько статических блоков, а каждый из них выполняется в той же
последовательности, в которой они написаны в коде.
 
Где в памяти Java хранятся статические классы и переменные?
Вплоть до 8-й версии Java статические методы и переменные хранились в пространстве permgen.
Но потом было введено новое пространство памяти, называемое метапространством — в нем
хранятся все эти имена и поля класса, методы класса с байт-кодом методов, пул констант, JIT-
оптимизации и т. д. Причина удаления permgen в Java 8.0 в том, что очень сложно предсказать
необходимый размер permgen.
 
Final
В Java при объявлении сущности можно воспользоваться ключевым словом final. Оно
предполагает, что значение не может быть изменено в будущем. Ключевое слово final может
применяться для переменной, метода и класса.
 Конечная переменная предназначена для создания постоянных значений.
 Конечный метод предотвращает переопределение метода.
 Конечный класс предотвращает наследование.
 
Что такое конечная переменная и когда ей стоит воспользоваться?
Когда переменная объявляется с помощью ключевого слова final, ее значение не может быть
изменено. По сути, это константа. Это также означает, что конечную переменную необходимо
инициализировать. Если конечная переменная является ссылкой, то ее нельзя будет повторно
привязать к другому объекту, но внутреннее состояние объекта, на которое указывает эта
ссылочная переменная, может быть изменено, т.е. вы можете добавлять или удалять элементы
из конечного массива или конечной коллекции. Рекомендуется именовать конечные
переменные целиком в верхнем регистре и с подчеркиванием для разделения слов.
Существует три способа инициализации конечной переменной.
 Инициализировать конечную переменную, когда она будет объявлена. Это самый
распространенный подход. Если конечная переменная не инициализирована при объявлении,
она называется пустой конечной переменной. Ниже приведены два способа инициализации
пустой конечной переменной.
 Пустая конечная переменная может быть инициализирована внутри блока инициализатора
экземпляра или внутри конструктора. Если в классе несколько конструкторов, то переменную
необходимо инициализировать во всех из них, в противном случае во время компиляции
появится ошибка.
 Пустая конечная статическая переменная может быть инициализирована в статическом
блоке.
 
Где использовать конечные классы
Когда класс объявляется с ключевым словом final, он называется конечным. Конечный класс не
может быть расширен (унаследован). У него есть два применения.
Первое, безусловно, заключается в предотвращении наследования, так как конечные классы не
могут быть расширены. Например, все классы-оболочки, такие как Integer, Float и т. д. —
конечные классы.
Другое применение final с классами заключается в создании неизменяемого класса, подобного
предопределенному классу String. Нельзя сделать класс неизменяемым, не сделав его
конечным.
 
Когда использовать конечные методы
Когда метод объявляется с ключевым словом final, он называется конечным методом. Такой
метод не может быть переопределен. Это присутствует в классе Object — ряд его методов
конечные. С ключевым словом final объявляются методы, для которых необходимо следовать
одной и той же реализации во всех производных классах.
 
 Модификаторы доступа
 
В Java существуют следующие модификаторы доступа:
 private: члены класса доступны только внутри класса;
 default (package-private) (модификатор, по-умолчанию): члены класса видны внутри пакета
(если класс будет так объявлен он будет доступен только внутри пакета);
 protected: члены класса доступны внутри пакета и в наследниках;
 public: члены класс доступны всем;
Последовательность модификаторов по убыванию уровня закрытости: private, default ,protected,
public).
 
Во время наследования возможно изменения модификаторов доступа в сторону большей
видимости. Так сделано для того, чтобы не нарушался принцип LSP для наследуемого класса.
 
 Методы класса Object
 
 protected Object clone() - создает новый объект, не отличающийся от клонируемого.
 public boolean equals(Object obj) - определяет, равен ли один объект другому.
 protected void finalize() - вызывается перед удалением неиспользуемого объекта.
 public final Class<?> getClass() - получает класс объекта во время выполнения.
 public int hashCode() - возвращает хэш-код, связанный с вызывающим объектом.
 public final void notify() - возобновляет исполнение потока, ожидающего вызывающего объекта.
 public final void notifyAll() - возобновляет исполнение всех потоков, ожидающих вызывающего
объекта.
 public String toString() - возвращает символьную строку, описывающую объект.
 public final void wait() - ожидает другого потока исполнения.
 public final void wait(long timeout) - ожидает другого потока исполнения.
 public final void wait(long timeout, int nanos) - ожидает другого потока исполнения.
 
Контракт между equals() и hashCode()
Если hashCode возвращает разные числа для двух объектов, то объекты гарантированно разные.
Если хеш-коды равные, тогда вызывается медленный метод equals, так как в таком случае
равность хеш-кодов не гарантирует того, что объекты равны (коллизия, из-за того, что
комбинаций этих объектов может быть бесконечное множество, а количество комбинаций хеш-
кодов ограничено диапазоном int).
Соответственно equals гарантированно корректно сравнит объекты, даже если hashCode у двух
объектов совпадают.
 
Правила переопределения equals()
 Рефлексивность: Объект должен равняться себе самому.
 Симметричность: если a.equals(b) возвращает true, то b.equals(a) должен тоже вернуть true.
 Транзитивность: если a.equals(b) возвращает true и b.equals(c) тоже возвращает true, то
c.equals(a) тоже должен возвращать true.
 Согласованность: повторный вызов метода equals() должен возвращать одно и тоже значение
до тех пор, пока какое-либо значение свойств объекта не будет изменено. То есть, если два
объекта равны в Java, то они будут равны пока их свойства остаются неизменными.
 Сравнение null: объект должны быть проверен на null. Если объект равен null, то метод должен
вернуть false, а не NullPointerException. Например, a.equals(null) должен вернуть false.
 
 Immutable
 
Иммутабельный (неизменяемый, immutable) класс — это класс, который после инициализации
не может изменить свое состояние. То есть если в коде есть ссылка на экземпляр
иммутабельного класса, то любые изменения в нем приводят к созданию нового экземпляра.
 
Чтобы класс был иммутабельным, он должен соответствовать следующим требованиям:
 Должен быть объявлен как final, чтобы от него нельзя было наследоваться. Иначе дочерние
классы могут нарушить иммутабельность.
 Все поля класса должны быть приватными и финальными в соответствии с принципами
инкапсуляции.
 Для корректного создания экземпляра в нем должны быть параметризованные конструкторы,
через которые осуществляется первоначальная инициализация полей класса.
 Для исключения возможности изменения состояния после инстанцирования, в классе не
должно быть сеттеров.
 Для полей-коллекций необходимо делать глубокие копии, чтобы гарантировать их
неизменность.
 
Иммутабельность строк в Java
Класс String, представляющий набор символов, вероятно, самый популярный класс в Java. Его
назначение — упростить работу со строками, предоставляя различные методы для их обработки.
Например, в классе String есть методы для получения символов, выделения подстрок, поиска,
замены и многие другие. Как и другие классы-обертки в Java (Integer, Boolean и т.д.), класс String
является иммутабельным.
Иммутабельность строк дает следующие преимущества:
 Строки потокобезопасны.
 Для строк можно использовать специальную область памяти, называемую "пул строк".
Благодаря которой две разные переменные типа String с одинаковым значением будут
указывать на одну и ту же область памяти.
 Строки отличный кандидат для ключей в коллекциях, поскольку они не могут быть изменены
по ошибке.
 Класс String кэширует хэш-код, что улучшает производительность хеш-коллекций,
использующих String.
 Чувствительные данные, такие как имена пользователей и пароли, нельзя изменить по ошибке
во время выполнения, даже при передаче ссылок на них между разными методами.
 
 Collections
 
Иерархия коллекций
 
Как работают внутри ArrayList & LinkedList
ArrayList — является реализацией динамического массива объектов. Позволяет хранить любые
данные, включая null в качестве элемента. Как можно догадаться из названия, его реализация
основана на обычном массиве. Данную реализацию следует применять, если в процессе работы
с коллекцией предплагается частое обращение к элементам по индексу. Из-за особенностей
реализации поиндексное обращение к элементам выполняется за константное время O(1). Но
данную коллекцию рекомендуется избегать, если требуется частое удаление/добавление
элементов в середину коллекции.
 
LinkedList — ещё одна реализация List. Позволяет хранить любые данные, включая null.
Особенностью реализации данной коллекции является то, что в её основе лежит
двунаправленный связный список (каждый элемент имеет ссылку на предыдущий и
следующий). Благодаря этому, добавление и удаление из середины, доступ по индексу,
значению происходит за линейное время O(n), а из начала и конца за константное O(1).
 
Что лучше: ArrayList или LinkedList?
Преимущества ArrayList: в возможности доступа к произвольному элементу по индексу за
постоянное время (так как это массив), минимум накладных расходов при хранении такого
списка, вставка в конец списка в среднем производится так же за постоянное время. В среднем
потому, что массив имеет определенный начальный размер n (в коде это параметр capacity), по
умолчанию n = 10, при записи n+1 элемента, будет создан новый массив размером (n * 3) / 2 + 1,
в него будут помещены все элементы из старого массива + новый, добавляемый элемент. В
итоге получаем, что при добавлении элемента при необходимости расширения массива, время
добавления будет значительно больше, нежели при записи элемента в готовую пустую ячейку.
Тем не менее, в среднем время вставки элемента в конец списка является постоянным.
Удаление последнего элемента происходит за константное время. Недостатки ArrayList
проявляются при вставке/удалении элемента в середине списка — это взывает перезапись всех
элементов размещенных «правее» в списке на одну позицию влево, кроме того, при удалении
элементов размер массива не уменьшается, до явного вызова метода trimToSize().
 
LinkedList наоборот, за постоянное время может выполнять вставку/удаление элементов в
списке (именно вставку и удаление, поиск позиции вставки и удаления сюда не входит). Доступ к
произвольному элементу осуществляется за линейное время (но доступ к первому и последнему
элементу списка всегда осуществляется за константное время — ссылки постоянно хранятся на
первый и последний, так что добавление элемента в конец списка вовсе не значит, что придется
перебирать весь список в поисках последнего элемента). В целом же, LinkedList в абсолютных
величинах проигрывает ArrayList и по потребляемой памяти и по скорости выполнения
операций. LinkedList предпочтительно применять, когда происходит активная работа
(вставка/удаление) с серединой списка или в случаях, когда необходимо гарантированное время
добавления элемента в список.
 
9. Map
 
Как работает HashMap?
https://habr.com/ru/post/128017/
 
HashMap содержит массив Node и Node может представлять класс, содержащий следующие
объекты:
 
int — хэш
K — ключ
V — значение
Node — следующий элемент
 
Bucket -это единственный элемент массива HashMap. Он используется для хранения узлов
(Nodes). Два или более узла могут иметь один и тот -же bucket. В этом случае для связи узлов
используется структура данных связанный список. Bucket -ы различаются по ёмкости (свойство
capacity). Отношение между bucket и capacity выглядит следующим образом:
 
capacity = number of buckets * load factor
 
Один bucket может иметь более, чем один узел, это зависит от реализации метода hashCode().
Чем лучше реализованн ваш метод hashCode(), тем лучше будут использоваться ваши bucket -ы.
 
Вычисление индекса в HashMap
Хэш код ключа может быть достаточно большим для создания массива. Сгенерированный хэш
код может быть в диапазоне целочисленного типа и если мы создадим массив такого размера,
то легко получим исключение outOfMemoryException. Потому мы генерируем индекс для
минимизации размера массива. По сути для вычисления индекса выполняется следующая
операция:
 
index = hashCode(key) & (n-1).
 
где n равна числу bucket или значению длины массива. В нашем примере я рассматриваю n, как
значение по умолчанию равное 16.
 
Изменения в Java 8
Как мы уже знаем в случае возникновения коллизий объект node сохраняется в структуре
данных "связанный список" и метод equals() используется для сравнения ключей. Это сравнения
для поиска верного ключа в связанном списке -линейная операция и в худшем случае сложность
равнa O(n).
 
Для исправления этой проблемы в Java 8 после достижения определенного порога вместо
связанных списков используются сбалансированные деревья. Это означает, что HashMap в
начале сохраняет объекты в связанном списке, но после того, как колличество элементов в хэше
достигает определенного порога происходит переход к сбалансированным деревьям. Что
улучшает производительность в худшем случае с O(n) до O(log n).
 
Важный момент
Сложность операций get() и put() практически константна до тех пор, пока не будет проведенно
повторное хэширование.
В случае коллизий, если индексы двух и более объектов node одинаковые, объекты node
соединяются с помощью связанного списка, т.е. ссылка на второй объект node хранится в
первом, на третий во втором и т.д.
Если данный ключ уже существует в HashMap, значение перезаписывается.
Хэш код null равен 0.
Когда объект получается по ключу происходят переходы по связанному списку до тех пор, пока
объект не будет найден или ссылка на следующий объект не будет равна null.
 
Нюансы которые стоит повторить и запомнить:
🔘 Общий принцип: внутренний массив table, содержащий бакеты (корзины) – списки элементов
с одинаковыми пересчитанными хэш-суммами;
🔘 Пересчет хэш-суммы для умещения int индексов в capacity ячейках table;
🔘 rehash – удвоение размера table при достижении threshold (capacity*loadFactor) занятых
бакетов;
🔘 Невозможность сжать однажды раздувшийся table;
🔘 Два способа разрешения коллизий: используемый в HashMap метод цепочек и альтернатива
– открытая адресация;
🔘 Варианты для многопоточного использования: пересинхронизированная Hashtable и умная
ConcurrentHashMap;
🔘 Оптимизация Java 8: превращение списка в бакете в дерево при достижении 8 элементов –
при большом количестве коллизий скорость доступа растет с O(n) до O(log(n));
🔘 Явное использование бакета 0 для ключа null;
🔘 Связь с HashSet – HashMap, в котором используются только ключи;
🔘 Нет гарантий порядка элементов;
 
Скорость get() & put() HashMap - O(n)
 
HashMap обладает некоторыми отличиями: например, позволяет хранить один null ключ и
множество null значений.
Ключи и значения могут быть любых типов, в том числе и null. Для хранения примитивных типов
используются соответствующие классы-обертки.
 
10. Интерфейс и абстрактный класс
 
Абстрактный класс
Кроме обычных классов в Java есть абстрактные классы. Абстрактный класс похож на обычный
класс. В абстрактном классе также можно определить поля и методы, но в то же время нельзя
создать объект или экземпляр абстрактного класса. Абстрактные классы призваны
предоставлять базовый функционал для классов-наследников. А производные классы уже
реализуют этот функционал.
При определении абстрактных классов используется ключевое слово abstract.Но главное
отличие состоит в том, что мы не можем использовать конструктор абстрактного класса для
создания его объекта.Кроме обычных методов абстрактный класс может содержать абстрактные
методы. Такие методы определяются с помощью ключевого слова abstract и не имеют никакой
реализации. Производный класс обязан переопределить и реализовать все абстрактные методы,
которые имеются в базовом абстрактном классе. Также следует учитывать, что если класс имеет
хотя бы один абстрактный метод, то данный класс должен быть определен как абстрактный.
 
Интерфейс
Интерфейсы определяют некоторый функционал, не имеющий конкретной реализации, который
затем реализуют классы, применяющие эти интерфейсы. И один класс может применить
множество интерфейсов. Чтобы определить интерфейс, используется ключевое слово interface.
Ранее до JDK 8 при реализации интерфейса мы должны были обязательно реализовать все его
методы в классе. А сам интерфейс мог содержать только определения методов без конкретной
реализации. В JDK 8 была добавлена такая функциональность как методы по умолчанию. И
теперь интерфейсы кроме определения методов могут иметь их реализацию по умолчанию,
которая используется, если класс, реализующий данный интерфейс, не реализует метод.Метод
по умолчанию - это обычный метод без модификаторов, который помечается ключевым словом
default.
Начиная с JDK 8 в интерфейсах доступны статические методы - они аналогичны методам класса.
Чтобы обратиться к статическому методу интерфейса также, как и в случае с классами, пишут
название интерфейса и метод.
По умолчанию все методы в интерфейсе фактически имеют модификатор public. Однако начиная
с Java 9 мы также можем определять в интерфейсе методы с модификатором private. Они могут
быть статическими и нестатическими, но они не могут иметь реализации по
умолчанию.Подобные методы могут использоваться только внутри самого интерфейса, в
котором они определены. То есть к примеру нам надо выполнять в интерфейсе некоторые
повторяющиеся действия, и в этом случае такие действия можно выделить в приватные методы.
Кроме методов в интерфейсах могут быть определены статические константы. Хотя такие
константы также не имеют модификаторов, но по умолчанию они имеют модификатор доступа
public static final, и поэтому их значение доступно из любого места программы.
 
 
Разница между интерфейсом и абстрактным классом
 Интерфейс описывает только поведение. У него нет состояния. А у абстрактного класса
состояние есть: он описывает и то, и другое.
 Абстрактный класс связывает между собой и объединяет классы, имеющие очень близкую
связь. В то же время, один и тот же интерфейс могут реализовать классы, у которых вообще
нет ничего общего.
 Классы могут реализовывать сколько угодно интерфейсов, но наследоваться можно только от
одного класса.
 
Что появилось в интерфейсах в разных версиях Java?
Java 1.1 - Вложенные классы, Вложенные интерфейсы
Java 5 - Обобщенные типы, Вложенные перечисления, Вложенные аннотации
Java 8 - Методы по умолчанию, Статические методы
Java 9 - Приватные методы
 
Алмазная проблема
«Алмазная проблема » (иногда называемая «Смертельный алмаз смерти») - это возникающая
двусмысленность когда два класса B и C наследуются от A, а класс D наследуется от B и C. Если в
A есть метод, в котором B и C имеют переопределение , а D не переопределяет его, то какая
версия метода, который наследует D: метод B или метод C?
 
 Exceptions
 
Иерархия исключений
Базовым классом для всех исключений является класс Throwable. От него уже наследуются два
класса: Error и Exception. Все остальные классы являются производными от этих двух классов.
 
Класс Error описывает внутренние ошибки в исполняющей среде Java. Программист имеет очень
ограниченные возможности для обработки подобных ошибок.
 
Собственно исключения наследуются от класса Exception. Среди этих исключений следует
выделить класс RuntimeException. RuntimeException является базовым классом для так
называемой группы непроверяемых исключений (unchecked exceptions) - компилятор не
проверяет факт обработки таких исключений и их можно не указывать вместе с оператором
throws в объявлении метода. Такие исключения являются следствием ошибок разработчика,
например, неверное преобразование типов или выход за пределы массива.
 
Некоторые из классов непроверяемых исключений:
 ArithmeticException: исключение, возникающее при делении на ноль
 IndexOutOfBoundException: индекс вне границ массива
 IllegalArgumentException: использование неверного аргумента при вызове метода
 NullPointerException: использование пустой ссылки
 NumberFormatException: ошибка преобразования строки в число
 
Все остальные классы, образованные от класса Exception, называются проверяемыми
исключениями (checked exceptions).
Некоторые из классов проверяемых исключений:
 CloneNotSupportedException: класс, для объекта которого вызывается клонирование, не
реализует интерфейс Cloneable
 InterruptedException: поток прерван другим потоком
 ClassNotFoundException: невозможно найти класс
 
Подобные исключения обрабатываются с помощью конструкции try..catch. Либо можно
передать обработку методу, который будет вызывать данный метод, указав исключения после
оператора throws.

Поскольку все классы исключений наследуются от класса Exception, то все они наследуют ряд его
методов, которые позволяют получить информацию о характере исключения. Среди этих
методов отметим наиболее важные:
 Метод getMessage() возвращает сообщение об исключении
 Метод getStackTrace() возвращает массив, содержащий трассировку стека исключения
 Метод printStackTrace() отображает трассировку стека
 
Для чего обрабатывать исключения?
Иногда метод, в котором может генерироваться исключение, сам не обрабатывает это
исключение. В этом случае в объявлении метода используется оператор throws, который надо
обработать при вызове этого метода. С помощью оператора throw по условию выбрасывается
исключение. В то же время метод сам это исключение не обрабатывает с помощью try..catch,
поэтому в определении метода используется выражение throws Exception. Без обработки
исключение у нас возникнет ошибка компиляции, и мы не сможем скомпилировать программу.
 
Можно ли обрабатывать непроверяемые исключения?
Неконтролируемые исключения не требуют обязательной обработки, однако, при желании,
можно обрабатывать исключения класса RuntimeException.
 
try-with-resources
Оператор try-c-ресурсами реализует принцип автоматического управления ресурсами, целью
которого является избежать, например, утечек памяти, в случаях когда ресурс по каким-то
причинам не освобождается, если он больше не нужен.Оператор try-c-ресурсами позволяет
объявить и проинициализировать ресурс (в круглых скобках после оператора try), создав
переменной ресурса локальный контекст в блоке try. По завершении этого блока переменная
удаляется, а значит и ресурс автоматически закрывается. Отсюда отпадает необходимость
явного закрытия ресурса методом close() в блоке оператора finally.
 
 Generics
 
Для чего они нужны?
Дженерики (обобщения) — это особые средства языка Java для реализации обобщённого
программирования: особого подхода к описанию данных и алгоритмов, позволяющего работать
с различными типами данных без изменения их описания. Обобщения или generics
(обобщенные типы и методы) позволяют нам уйти от жесткого определения используемых
типов.
Говоря о дженериках мы всегда имеем две категории: типизированные типы (Generic Types) и
"сырые" типы (Raw Types).
Сырые типы — это типы без указания "уточненения" в фигурных скобках (angle brackets): List list =
new ArrayList();
Типизированные типы — наоборот, с указанием "уточнения": List<String> list = new ArrayList<>();
Дженерики позволяют типизировать методы. Из данного tutorial важно запомнить про
синтаксис:
 включает список типизированных параметров внутри угловых скобок;
 список типизированных параметров идёт до возвращаемого метода.
 
Ещё у дженериков есть понятие Wildcard. Они в свою очередь делятся на три типа:
 Upper Bounded Wildcards - <? extends Number>
 Unbounded Wildcards - <?>
 Lower Bounded Wildcards - <? super Integer>

Данный принцип ещё называют принципом PECS (Producer Extends Consumer Super).
На первый взгляд кажется, что принцип PECS достаточно прост. Все, кто встречался с ним, знают,
что это акроним, означающий «Producer Extends Consumer Super». Как объясняется в
многочисленных статьях, если у нас есть некая коллекция, типизированная wildcard с верхней
границей (extends) – то это, «продюсер». «Он только «продюсирует», предоставляет элемент из
контейнера, а сам ничего не принимает». Если же у нас коллекция, типизированная wildcard по
нижней границе (super) – то это, «потребитель», который «только принимает, а предоставить
ничего не может».
https://habr.com/ru/post/559268/

Answers Part 2
10 марта 2022 г.
22:11
 
13. Spring
 
Что, для чего, основные фишки, плюсы
По сути Spring Framework представляет собой просто контейнер внедрения зависимостей, с
несколькими удобными слоями (например: доступ к базе данных, прокси, аспектно-
ориентированное программирование, RPC, веб-инфраструктура MVC). Это все позволяет вам
быстрее и удобнее создавать Java-приложения.
 
Основное преимущество Spring'а - возможность разработки приложения как набора
слабосвязанных (loose-coupled) компонентов. Чем меньше компоненты приложения знают друг
о друге, тем проще разрабатывать новый и поддерживать существующий функционал
приложения. Классический пример - управление транзакциями. Spring позволяет вам управлять
транзакциями совершенно независимо от основной логики взаимодействия с БД. Изменение
этой логики не порушит транзакционность, равно как изменение логики управления
транзакциями не сломает логику программы*. Spring поощряет модульность. Компоненты
можно добавлять и удалять (почти) независимо друг от друга. В принципе, приложение можно
разработать таким образом, что оно даже не будет знать, что управляется Spring'ом*. Также
Spring заметно упрощает модульное тестирование (unit-testing): в компонент, разработанный
для работы в IoC контейнере очень легко инжектировать фейковые зависимости и проверить
работу только этого компонента. Ну, и в качестве приятного дополнения, Spring сильно облегчает
инициализацию и настройку компонентов приложения, позволяя гибко настраивать приложение
без существенных изменений Java-кода*.
 
Ещё раз, кратко:
 
Поощрение слабой связанности компонентов, и, как следствие...
... упрощение инициализации и настройки компонентов,
... упрощение модульного тестирования,
... упрощение разработки и поддержки приложения в целом.
 
 
https://habr.com/ru/post/490586/
Евгений Борисов — Spring-потрошитель, часть 1
Евгений Борисов — Spring-потрошитель, часть 2
https://javarush.ru/groups/posts/spring-framework-java-1
https://javarush.ru/groups/posts/477-spring-dlja-lenivihkh-osnovih-bazovihe-koncepcii-i-primerih-s-
kodom-chastjh-2
https://habr.com/ru/post/470305/
 
Bean
Бин — объект класса, представляющий собой завершенный программный элемент с
определенной бизнес-функцией либо внутренней функцией Spring'а, жизненным циклом
которого управляет контейнер бинов.
Бин — создаваемый Spring-ом объект класса, который можно внедрить в качестве значения поля
в другой объект.
 
Dependency injection, inversion of control
IoC (Inversion of Control) — инверсия управления. Об этом я уже вскользь упоминал, когда писал,
что при использовании библиотеки вы сами прописываете в своем коде какой метод какого
объекта вызвать, а в случает с фреймворками — чаще всего уже фреймворк будет вызывать в
нужный ему момент тот код, который вы написали. То есть, тут уже не вы управляете процессом
выполнения кода/программы, а фреймворк это делает за вас. Вы передали ему управление
(инверсия управления).
 
Под DI понимают то Dependency Inversion (инверсию зависимостей, то есть попытки не делать
жестких связей между вашими модулями/классами, где один класс напрямую завязан на
другой), то Dependency Injection (внедрение зависимостей, это когда объекты котиков создаете
не вы в main-е и потом передаете их в свои методы, а за вас их создает спринг, а вы ему просто
говорите что-то типа "хочу сюда получить котика" и он вам его передает в ваш метод).
 
@Autowired
Аннотация @Autowired отмечает конструктор, поле или метод как требующий автозаполнения
инъекцией зависимости Spring. Инъекция в приватные поля сильно затрудняет юнит
тестирование.
Приходится применять ReflectionTestUtils и другие хаки. Лучше все-же использовать инъекцию
через конструктор.
 
Жизненный цикл бина
1. Загрузка описаний бинов, создание графа зависимостей(между бинами)
2. Создание и запуск BeanFactoryPostProcessors
3. Создание бинов
4. Spring внедряет значения и зависимости в свойства бина
5. Если бин реализует метод setBeanName() из интерфейса NameBeanAware, то ID бина
передается в метод
6. Если бин реализует BeanFactoryAware, то Spring устанавливает ссылку на bean factory через
setBeanFactory() из этого интерфейса.
7. Если бин реализует интерфейс ApplicationContextAware, то Spring устанавливает ссылку на
ApplicationContext через setApplicationContext().
8. BeanPostProcessor это специальный интерфейс(о нем ниже), и Spring позволяет бинам
имплементировать этот интерфейс. Реализуя метод postProcessBeforeInitialization(), можно
изменить экземпляр бина перед его(бина) инициализацией(установка свойств и т.п.)
9. Если определены методы обратного вызова, то Spring вызывает их. Например, это метод,
аннотированный @PostConstruct или метод initMethod из аннотации @Bean.
10. Теперь бин готов к использованию. Его можно получить с помощью метода
ApplicationContext#getBean().
11. После того как контекст будет закрыт(метод close() из ApplicationContext), бин уничтожается.
12. Если в бине есть метод, аннотированный @PreDestroy, то перед уничтожением вызовется этот
метод. Если бин имплементирует DisposibleBean, то Spring вызовет метод destroy(), чтобы
очистить ресурсы или убить процессы в приложении. Если в аннотации @Bean определен
метод destroyMethod, то вызовется и он.
 
Scope Beans

 
 
14. Паттерны
 
 

 
Порождающие:
Singleton (Одиночка) - ограничивает создание одного экземпляра класса, обеспечивает доступ к
его единственному объекту.
Factory (Фабрика) - используется, когда у нас есть суперкласс с несколькими подклассами и на
основе ввода, нам нужно вернуть один из подкласса.
Abstract Factory (Абстрактная фабрика) - используем супер фабрику для создания фабрики, затем
используем созданную фабрику для создания объектов.
Builder (Строитель) - используется для создания сложного объекта с использованием простых
объектов. Постепенно он создает больший объект от малого и простого объекта.
Prototype (Прототип) - помогает создать дублированный объект с лучшей производительностью,
вместо нового создается возвращаемый клон существующего объекта.
 
Структурные:
Adapter (Адаптер) - это конвертер между двумя несовместимыми объектами. Используя паттерн
адаптера, мы можем объединить два несовместимых интерфейса.
Composite (Компоновщик) - использует один класс для представления древовидной структуры.
Proxy (Заместитель) - представляет функциональность другого класса.
Flyweight (Легковес) - вместо создания большого количества похожих объектов, объекты
используются повторно.
Facade (Фасад) - беспечивает простой интерфейс для клиента, и клиент использует интерфейс
для взаимодействия с системой.
Bridge (Мост) - делает конкретные классы независимыми от классов реализации интерфейса.
Decorator (Декоратор) - добавляет новые функциональные возможности существующего объекта
без привязки его структуры.
 
Поведенческие:
Template Method (Шаблонный метод) - определяющий основу алгоритма и позволяющий
наследникам переопределять некоторые шаги алгоритма, не изменяя его структуру в целом.
Mediator (Посредник) - предоставляет класс посредника, который обрабатывает все
коммуникации между различными классами.
Chain of Responsibility (Цепочка обязанностей) - позволяет избежать жесткой зависимости
отправителя запроса от его получателя, при этом запрос может быть обработан несколькими
объектами.
Observer (Наблюдатель) - позволяет одним обьектам следить и реагировать на события,
происходящие в других объектах.
Strategy (Стратегия) - алгоритм стратегии может быть изменен во время выполнения программы.
Command (Команда) - интерфейс команды объявляет метод для выполнения определенного
действия.
State (Состояние) - объект может изменять свое поведение в зависимости от его состояния.
Visitor (Посетитель) - используется для упрощения операций над группировками связанных
объектов.
Interpreter (Интерпретатор) - определяет грамматику простого языка для проблемной области.
Iterator (Итератор) - последовательно осуществляет доступ к элементам объекта коллекции, не
зная его основного представления.
Memento (Хранитель) - используется для хранения состояния объекта, позже это состояние
можно восстановить.
 
15. ACID
 
Требования ACID — набор требований, которые обеспечивают сохранность ваших данных. Что
особенно важно для финансовых операций.
 
1. Atomicity — Атомарность
2. Consistency — Согласованность
3. Isolation — Изолированность
4. Durability — Надёжность
 
Atomicity — Атомарность
Атомарность гарантирует, что каждая транзакция будет выполнена полностью или не будет
выполнена совсем. Не допускаются промежуточные состояния.
 
Consistency — Согласованность
Транзакция, достигающая своего нормального завершения (EOT — end of transaction,
завершение транзакции) и, тем самым, фиксирующая свои результаты, сохраняет
согласованность базы данных. Другими словами, каждая успешная транзакция по определению
фиксирует только допустимые результаты.
 
Isolation — Изолированность
Во время выполнения транзакции параллельные транзакции не должны оказывать влияния на
её результат.
 
Durability — Надёжность
Если пользователь получил подтверждение от системы, что транзакция выполнена, он может
быть уверен, что сделанные им изменения не будут отменены из-за какого-либо сбоя.
Обесточилась система, произошел сбой в оборудовании? На выполненную транзакцию это не
повлияет.
 
16. Join, leftJoin/rightJoin, orderBy, where, groupBy, having, distinct
 
Инструкция Join позволяет объединить колонки из нескольких таблиц в одну. Объединение
происходит временное и целостность таблиц не нарушается. Существует три типа join-
выражений:
inner join;
1  
2 SELECT id_person, name, id_pos, title
3 FROM `persons`
4 INNER JOIN `positions` ON id_pos =
5 position_ref
outer join;
SELECT id_person, name, id_pos, title
FROM `persons`
LEFT OUTER JOIN `positions` ON id_pos = position_ref
 
1  
2 (SELECT id_person, name, id_pos, title
3 FROM persons
4 LEFT OUTER JOIN positions ON id_pos =
5 position_ref)
6  
7 UNION
8  
9 (SELECT id_person, name, id_pos, title
10 FROM persons
11 RIGHT OUTER JOIN positions ON id_pos =
position_ref)
cross join;
 
В свою очередь, outer join может быть left, right и full (слово outer обычно опускается).
 
ORDER BY изменяет порядок возврата элементов.
GROUP BY будет собирать записи по указанным столбцам, что позволяет выполнять функции
агрегации для негрупповых столбцов (например, SUM, COUNT, AVG и т.д.).
 
17. Индексы бд
 
Индекс - это способ реляционной базы данных предварительно сортировать данные в
разнообразных порядках, реализуемых одновременно. Это выполняется посредством хранения
в индексе значений индексируемых полей таблицы, а также указателей на фактическое
местоположение данных.
 
18. Области памяти
 
Стек (Stack)
Стековая память отвечает за хранение ссылок на объекты кучи и за хранение типов значений
(также известных в Java как примитивные типы), которые содержат само значение, а не ссылку
на объект из кучи.
 
Каждый поток, работающий в виртуальной машине Java, имеет свой собственный стек. Стек
содержит информацию о том, какие методы вызвал поток. Я буду называть это «стеком
вызовов». Как только поток выполняет свой код, стек вызовов изменяется.
 
Стек потока содержит все локальные переменные для каждого выполняемого метода. Поток
может получить доступ только к своему стеку. Локальные переменные, невидимы для всех
других потоков, кроме потока, который их создал. Даже если два потока выполняют один и тот
же код, они всё равно будут создавать локальные переменные этого кода в своих собственных
стеках. Таким образом, каждый поток имеет свою версию каждой локальной переменной.
 
Все локальные переменные примитивных типов (boolean, byte, short, char, int, long, float, double)
полностью хранятся в стеке потоков и не видны другим потокам. Один поток может передать
копию примитивной переменной другому потоку, но не может совместно использовать
примитивную локальную переменную.
 
Куча (Heap)
Куча содержит все объекты, созданные в вашем приложении, независимо от того, какой поток
создал объект. К этому относятся и версии объектов примитивных типов (например, Byte,
Integer, Long и т.д.). Неважно, был ли объект создан и присвоен локальной переменной или
создан как переменная-член другого объекта, он хранится в куче.
 
Локальная переменная может быть примитивного типа, в этом случае она полностью хранится в
стеке потока.
 
Локальная переменная также может быть ссылкой на объект. В этом случае ссылка (локальная
переменная) хранится в стеке потоков, но сам объект хранится в куче.
 
Переменные-члены объекта хранятся в куче вместе с самим объектом. Это верно как в случае,
когда переменная-член имеет примитивный тип, так и в том случае, если она является ссылкой
на объект.
 
Статические переменные класса также хранятся в куче вместе с определением класса.
 
К объектам в куче могут обращаться все потоки, имеющие ссылку на объект. Когда поток имеет
доступ к объекту, он также может получить доступ к переменным-членам этого объекта. Если
два потока вызывают метод для одного и того же объекта одновременно, они оба будут иметь
доступ к переменным-членам объекта, но каждый поток будет иметь свою собственную копию
локальных переменных.
 
 
Типы ссылок
Если вы внимательно посмотрите на изображение структуры памяти, вы, вероятно, заметите, что
стрелки, представляющие ссылки на объекты из кучи, на самом деле относятся к разным типам.
Это потому, что в языке программирования Java используются разные типы ссылок: сильные,
слабые, мягкие и фантомные ссылки. Разница между типами ссылок заключается в том, что
объекты в куче, на которые они ссылаются, имеют право на сборку мусора по различным
критериям. Рассмотрим подробнее каждую из них.
 
1. Сильная ссылка
Это самые популярные ссылочные типы, к которым мы все привыкли. В приведенном выше
примере со StringBuilder мы фактически храним сильную ссылку на объект из кучи. Объект в куче
не удаляется сборщиком мусора, пока на него указывает сильная ссылка или если он явно
доступен через цепочку сильных ссылок.
2. Слабая ссылка
Попросту говоря, слабая ссылка на объект из кучи, скорее всего, не сохранится после
следующего процесса сборки мусора. Слабая ссылка создается следующим образом:
WeakReference<StringBuilder> reference = new WeakReference<>(new StringBuilder());
Хорошим вариантом использования слабых ссылок являются сценарии кеширования.
Представьте, что вы извлекаете некоторые данные и хотите, чтобы они также были сохранены в
памяти - те же данные могут быть запрошены снова. С другой стороны, вы не уверены, когда и
будут ли эти данные запрашиваться снова. Таким образом, вы можете сохранить слабую ссылку
на него, и в случае запуска сборщика мусора, возможно, он уничтожит ваш объект в куче.
Следовательно, через некоторое время, если вы захотите получить объект, на который вы
ссылаетесь, вы можете внезапно получить null значение.
3. Мягкая ссылка
Эти типы ссылок используются для более чувствительных к памяти сценариев, поскольку они
будут собираться сборщиком мусора только тогда, когда вашему приложению не хватает
памяти. Следовательно, пока нет критической необходимости в освобождении некоторого
места, сборщик мусора не будет касаться легко доступных объектов. Java гарантирует, что все
объекты, на которые имеются мягкие ссылки, будут очищены до того, как будет выдано
исключение OutOfMemoryError. В документации Javadocs говорится, что «все мягкие ссылки на
мягко достижимые объекты гарантированно очищены до того, как виртуальная машина выдаст
OutOfMemoryError».
Подобно слабым ссылкам, мягкая ссылка создается следующим образом:
SoftReference<StringBuilder> reference = new SoftReference<>(new StringBuilder());
4. Фантомная ссылка
Используется для планирования посмертных действий по очистке, поскольку мы точно знаем,
что объекты больше не живы. Используется только с очередью ссылок, поскольку .get()метод
таких ссылок всегда будет возвращаться null. Эти типы ссылок считаются предпочтительными
для финализаторов.
 
Ссылки на String
Ссылки на тип String в Java обрабатываются немного по- другому. Строки неизменяемы, что
означает, что каждый раз, когда вы делаете что-то со строкой, в куче фактически создается
другой объект. Для строк Java управляет пулом строк в памяти. Это означает, что Java сохраняет и
повторно использует строки, когда это возможно. В основном это верно для строковых
литералов.
 
Как подсказывает название, пул строк – это набор строк, который хранится в памяти Java heap.
Мы знаем, что String это специальный класс в Java, и мы можем создавать объекты этого класса,
используя оператор new точно так же, как и создавать объекты, предоставляя значение строки в
двойных кавычках.
Диаграмма ниже объясняет, как пул строк размещается в памяти Java heap и что происходит,
когда мы используем различные способы создания строк.

Пул строк возможен исключительно благодаря неизменяемости строк в Java и реализации идеи
интернирования строк. Пул строк также является примером паттерна Приспособленец
(Flyweight).
Пул строк помогает экономить большой объем памяти, но с другой стороны создание строки
занимает больше времени.
Когда мы используем двойные кавычки для создания строки, сначала ищется строка в пуле с
таким же значением, если находится, то просто возвращается ссылка, иначе создается новая
строка в пуле, а затем возвращается ссылка. Тем не менее, когда мы используем оператор new,
мы принуждаем класс String создать новый объект строки, а затем мы можем использовать
метод intern() для того, чтобы поместить строку в пул, или получить из пула ссылку на другой
объектString с таким же значением.
 
19. Многопоточность
 
Процесс
Process (процесс) – выполняющийся экземпляр программы, которому Операционная Система
(ОС) выделила память, процессорное время/ядра и прочие ресурсы. Важно, что память
выделяется отдельно, адресные пространства различных процессов недоступны друг другу. Если
процессам необходимо обмениваться данными, они могут это сделать с помощью файлов,
каналов и иных способов межпроцессного взаимодействия.
 
Поток
Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток
последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с
другими потоками этого процесса.
 
Поток - это подмножество процесса. Для одного процесса может быть запущено несколько
потоков. Потоки могут запускать любую часть процесса. У процесса есть собственный адрес, в
котором он хранится, а поток разделяет адрес процесса, который его создал. Создание процесса
- беспокойный процесс, в то время как потоки могут быть созданы легко. Поток обычно
называют легким процессом. Межпроцессное взаимодействие затруднено, в то время как
потоки могут легко общаться, используя Java-методы wait () и notify (). Процессы независимы,
поэтому любое изменение, внесенное в процесс, не влияет на дочерние процессы. Напротив,
если изменения сделаны в потоке, есть вероятность, что другие потоки могут быть затронуты.
 
Чем угрожает многопоточность
Есть две конкретные проблемы, которые может вызвать использование многопоточности —
взаимная блокировка (deadlock) и состояние гонки (race condition).
Deadlock — ситуация, при которой несколько потоков находятся в состоянии ожидания ресурсов,
занятых друг другом, и ни один из них не может продолжать выполнение.
Состояние гонки — ошибка проектирования многопоточной системы или приложения, при
которой работа системы или приложения зависит от того, в каком порядке выполняются части
кода.
 
Synchronized
Synchronized (с англ. "синхронизированный") - это ключевое слово, которое позволяет
заблокировать доступ к методу или части кода, если его уже использует другой поток.
Существует два применения synchronized - для метода и для блока кода.
При создании синхронизированного блока кода после оператора synchronized идет объект-
заглушка: synchronized(res). Причем в качестве объекта может использоваться только объект
какого-нибудь класса, но не примитивного типа. Каждый объект в Java имеет ассоциированный с
ним монитор. Монитор представляет своего рода инструмент для управления доступа к объекту.
Когда выполнение кода доходит до оператора synchronized, монитор объекта res блокируется, и
на время его блокировки монопольный доступ к блоку кода имеет только один поток, который и
произвел блокировку. После окончания работы блока кода, монитор объекта res освобождается
и становится доступным для других потоков.
После освобождения монитора его захватывает другой поток, а все остальные потоки
продолжают ожидать его освобождения.
Недостатком использования synchronized является как раз то, что другие потоки вынуждены
ждать, пока нужный объект или метод освободится. Это создает так называемый "bottle neck"
("узкое место") в программе - и скорость работы может пострадать. Поэтому, используйте
synchronized с умом по мере необходимости.
 
Volatile
volatile – ключевое слово для работы с многопоточностью. Не то же самое, что volatile в C++, не
обязано делать что-либо с кэшем процессора. Оказывает на поле объекта ровно два эффекта.
Во-первых, чтение/запись такого поля становятся атомарными. Это применение актуально
только для long и double, и не на всех платформах. Для остальных типов полей это верно и так.
Второй и самый интересный эффект – пара событий запись-чтение для такого поля являются
synchronization actions. Значит, между ними существует отношение happens-before. Это значит,
что существует гарантия, что произошедшее в памяти до записи будет видно после чтения. То
есть будут успешно прочитаны значения, записанные в другие переменные.
 
Это означает, что значение переменной будет "всегда читаться". Например, в многопоточных
приложениях один поток прочёл значение a=1, передал управление другому потоку, который
изменил значение на a=2, потом управление вернулось. Так вот, без volatile значение a у первого
потока будет 1, т.к. первый поток "помнит", что a=1, с volatile - 2, т.к. первый поток снова прочтет
значение и получит уже измененное.
 
Монитор
Мьютекс — это специальный объект для синхронизации потоков. Название «мьютекс»
происходит от английского «MUTual EXclusion» — «взаимное исключение», и это отлично
отражает его предназначение. Задача мьютекса — обеспечить такой механизм, чтобы доступ к
объекту в определенное время был только у одного потока.
У мьютекса есть несколько важных особенностей.
Во-первых, возможны только два состояния — «свободен» и «занят».
Это упрощает понимание принципа работы: можно провести параллели с булевыми
переменными true/false или двоичной системой счисления 1/0.
Во-вторых, состояниями нельзя управлять напрямую. В Java нет механизмов, которые позволили
бы явно взять объект, получить его мьютекс и присвоить ему нужный статус.
 
Монитор — это дополнительная «надстройка» над мьютексом. Фактически монитор — это
«невидимый» для программиста кусок кода. По сути, монитор в Java выражен с помощью слова
synchronized.
 
Семафор — это средство для синхронизации доступа к какому-то ресурсу.
Его особенность заключается в том, что при создании механизма синхронизации он использует
счетчик. Счетчик указывает нам, сколько потоков одновременно могут получать доступ к общему
ресурсу. Семафоры в Java представлены классом Semaphore.
 
Потокобезопасные коллекции
Если несколько потоков в программе могут обращаться к этим коллекциям, вы можете
использовать методы класса, предоставляемые Collections, для упаковки этих коллекций в
поточно-ориентированные коллекции. Коллекции предоставляет следующие статические
методы.
Начиная с Java5, в пакете java.util.concurrent предоставляется большое количество интерфейсов
коллекций и классов реализации, поддерживающих эффективный параллельный доступ
Классы коллекции, начинающиеся с Concurrent: такие как ConcurrentHashMap,
ConcurrentLinkedQueue, ConcurrentLinkedDeque, ConcurrentSkipListMap и ConcurrentSkipListSet
 
20. Тестирование
 
Виды тестирования
Тестирование бывает:
Блочное (Unit testing) — тестирование одного модуля в изоляции.
Интеграционное (Integration Testing) — тестирование группы взаимодействующих модулей.
Системное (System Testing) — тестирование системы в целом.
 
Блочное (модульное, unit testing) тестирование наиболее понятное для программиста.
Фактически это тестирование методов какого-то класса программы в изоляции от остальной
программы.
Интеграционное тестирование, на мой взгляд, наиболее сложное для понимания. Есть
определение — это тестирование взаимодействия нескольких классов, выполняющих вместе
какую-то работу.
Системное — это тестирование программы в целом. Для небольших проектов это, как правило,
ручное тестирование — запустил, пощелкал, убедился, что (не) работает. Можно
автоматизировать.
 
Mock
Моки помогают имитировать и изучать исходящие (outcoming) взаимодействия. То есть вызовы,
совершаемые тестируемой системой (SUT) к ее зависимостям для изменения их состояния.
 
Согласно Жерару Месарошу, существует 5 видов тестовых двойников:
Пустышка (dummy)
Стаб (stub)
Шпион (spy)
Мок (mock)
Фейк (fake)
 
Разница между этими двумя типами сводится к следующему:
Моки помогают имитировать и изучать исходящие (outcoming) взаимодействия. То есть вызовы,
совершаемые тестируемой системой (SUT) к ее зависимостям для изменения их состояния.
Стабы помогают имитировать входящие (incoming) взаимодействия. То есть вызовы,
совершаемые SUT к ее зависимостям для получения входных данных.
Например, отправка электронной почты является исходящим (outcoming) взаимодействием: это
взаимодействие приводит к побочному эффекту на SMTP-сервере. Тестовый двойник,
имитирующий такое взаимодействие, - это мок.
Извлечение данных из БД является входящим (incoming) взаимодействием — оно не приводит к
побочному эффекту. Соответствующий тестовый двойник является стабом.
Стабы предназначены для получения нужного состояния тестируемого объекта, а моки
применяются для проверки ожидаемого поведения тестируемого объекта.
 
21. Git
 
Git Rebase - способ перенести изменения из одной ветки в другую
Git Commit — amend - это удобный способ изменить последний коммит.
Git Merge - используется для слияния одной или нескольких веток в текущую.
 
22. REST
 
Ресурсы
Ресурс — это ключевая абстракция, на которой концентрируется протокол HTTP. Ресурс — это
все, что вы хотите показать внешнему миру через ваше приложение. Например, если мы пишем
приложение для управления задачами, экземпляры ресурсов будут следующие:
 Конкретный пользователь
 Конкретная задача
 Список задач
 
HTTP методы
GET: этот метод является безопасным и идемпотентным. Обычно используется для извлечения
информации и не имеет побочных эффектов.
POST: этот метод не является ни безопасным, ни идемпотентным. Этот метод наиболее широко
используется для создания ресурсов.
PUT: этот метод является идемпотентным. Вот почему лучше использовать этот метод вместо
POST для обновления ресурсов. Избегайте использования POST для обновления ресурсов.
DELETE: как следует из названия, этот метод используется для удаления ресурсов. Но этот метод
не является идемпотентным для всех запросов.
PATCH: используется для частичного изменения ресурса, не является идемпотентным.
 
 Алгоритмы
 
https://www.examclouds.com/ru/java/java-core-russian/sorting-algoritms
 
Answers Part 4
19 апреля 2022 г.
17:36
 
 
1. Коли використовувати абстрактний клас/інтерфейс
 
Абстрактный класс уместно использовать, если вы собираетесь использовать наследование, которое
будет обеспечивать общую структуру. Абстрактный класс также уместно использовать, если вы хотите
объявить приватные экземпляры. В интерфейсах, все методы должны быть публичными.
 
Когда следует использовать интерфейсы: Если нам надо определить функционал для группы
разрозненных объектов, которые могут быть никак не связаны между собой. Если мы проектируем
небольшой функциональный тип.
 
2. Які методи є в інтерфейсах(статичні, дефолтні, звичайні)
3. Дефолтна реалізація хешкоду
4. Складність пошуку по хешмапі - О(logN), коли перетворюється в дерево
5. TreeMap, що потрібно, щоб об'єкт попав в неї - comparator
6. Чи перебудовується хешмап в дерево, якщо не перевизначений Comparator
7. ConcurrentHashMap
8. Циклічний inject
 
Использовать ленивую инициализацию бинов или использовать сеттеры вместо конструкторов
 
9. Prototype bean
 
prototype всегда возвращает ссылки на разные объекты. Он не потокбезопасный, т.к. он не
гарантирует что один и тот же экземпляр будет вызываться только в 1 потоке.
 
10. Singletone, чи потокобезпечний
 
singleton всегда возвращает ссылку на один и тот же объект. Он потокбезопасен;
 
11. Особливість анотацій Repository, Controller, Service
 
@Component используется для указания класса в качестве компонента спринг. При использовании
поиска аннотаций, такой класс будет сконфигурирован как spring bean.
 
@Controller специальный тип класса, применяемый в MVC приложениях. Обрабатывает запросы и
часто используется с аннотацией @RequestMapping. Контроллер — отвечает за получение входных и
поток исходных данных. В его функции входит отслеживание действий пользователя.
 
@Repository указывает, что класс используется для работы с поиском, получением и хранением
данных. Аннотация может использоваться для реализации шаблона DAO.
 
@Service указывает, что класс является сервисом для реализации бизнес логики (на самом деле не
отличается от Component, но просто помогает разработчику указать смысловую нагрузку класса).
 
12. Spring Boot особливість
 
Основные сущности фреймворка Spring Boot – это стартеры. Зависимости с названиями вида spring-
boot-starter-xxx выполняют две основных задачи. Во-первых, они добавляют набор типичных
сторонних библиотек-зависимостей; во-вторых, регистрируют типичные бины и их конфигурации.
Кроме того, со Spring Boot в проекте появляется ряд таких полезностей, как embedded-сервер,
конфигурация web-приложения без web.xml, метрики, properties вынесенные из кода во внешние
файлы.
 
Отличительные особенности Spring Boot:
 
Обеспечивает радикально более быстрый и широко доступный стартовый опыт для всей разработки
Spring.
Предоставляет стандартную настройку "из коробки", но может быть быстро изменен, когда
требования начинают отличаться от значений по умолчанию.
Предоставляет ряд нефункциональных возможностей, которые являются общими для крупных
классов проектов (такие как встроенные серверы, безопасность, метрики, проверки
работоспособности и внешняя конфигурация).
Абсолютно нет генерации кода и не требуется настройка XML.
 
Например, spring-boot-starter-data-jpa даст вам готовый комплект всего необходимого для
использования JPA: драйвер, совместимую с ним версию Hibernate, библиотеки Persistence API и
Spring Data. В контексте приложения появятся все нужные для JPA репозиториев бины.
 
Таким образом Spring Boot ускоряет и упрощает разработку, дает возможность избавиться от
boilerplate-кода в проекте и сфокусироваться на бизнес-задачах. Это бывает особенно важно в
микросервисной архитектуре, когда создается большое количество приложений.
 
С другой стороны, такая избыточность естественно приводит к большей тяжеловесности и
медлительности приложения.
 
13. Нормалізація бд
 
При создании базы нужно учитывать некоторые правила. Исходя из вышесказанного, можно привести
следующую формулировку: нормализация БД — это процесс организации данных определенным
образом и рекомендации по проектированию. То есть таблицы и связи между ними (отношения)
создаются в соответствии с правилами. В результате обеспечивается нужный уровень безопасности
данных, а сама база становится более гибкой. Также устраняются несогласованные зависимости и
избыточность.
 
Первая нормальная форма (1НФ)
Согласно правилам, все атрибуты в такой таблице должны быть простыми, все сохраняемые данные
на пересечении столбцов и строк — содержать лишь скалярные значения. Также не должно быть
повторяющихся строк.
 
Вторая нормальная форма (2НФ)
Отношения будут соответствовать 2НФ, если сама БД находится в 1НФ, а каждый столбец, который не
является ключом, зависит от первичного ключа.
 
Третья нормальная форма (3НФ)
Таблица должна находиться во 2НФ, плюс любой столбец, который не является ключом, должен
зависеть лишь от первичного ключа.
 
14. Как остановить поток?
 
В Java поток представлен классом Thread. В нём есть метод stop(), но пользоваться им нельзя, метод
помечен как deprecated. Такая жесткая остановка моментально возвращает все захваченные потоком
мониторы, и защищенные ими данные могут оказаться в неконсистентном состоянии.
 
Разработчики рекомендуют вместо этого использовать флаг, который будет показывать о намерении
остановить поток. Флаг выставляется извне потока, а внутри проверяется в подходящий момент. Если
нужно остановиться, поток просто выходит из метода run(). В качестве такого флага подойдет
переменная типа AtomicBoolean.
 
Когда в потоке используются блокирующие операции, обычно для определенного типа операции
существует свой способ её прервать. Например, можно закрыть сокет, на котором поток ожидает. Для
большинства блокирующих операций сработает метод Thread.interrupt(). С его помощью можно
прервать Object.wait() и операции из NIO.
 
Останется только правильно обработать такое прерывание. Прерванный wait() выбросит
InterruptedException, Selector.select() вернет результат. Чтобы отличить осознанное прерывание с
целью завершить тред от какого-либо другого, его обработку всё ещё необходимо снабдить
проверкой флага.
 
15. Что такое fail-fast и fail-safe итераторы?
 
Это не какие-то отдельные типы, а характеристики разных реализаций интерфейса Iterator. Они
определяют, как поведет себя итератор при изменении перебираемой последовательности.
 
Fail-fast – «быстрый» итератор. Когда после его создания коллекция как-либо изменилась, он падает с
ошибкой без лишних разбирательств. Так работает итератор класса ArrayList, при изменении он
выбрасывает ConcurrentModificationException. Рекомендуется не основывать логику программы на fail-
fast отказах, и использовать их только как признак ошибки реализации.
 
Fail-safe – «умный» итератор. Обычно плата за отказоустойчивость – возможная неконсистентность
данных («слабая консистентность»). Итератор класса ConcurrentHashMap работает с копией данных,
он не выбросит исключение при изменении коллекции, но может не увидеть часть свежих изменений.
Плата за отсутствие ошибок других fail-safe итераторов может отличаться, детали всегда можно найти
в документации коллекций.
 
16. Когда нужно использовать raw types?
 
Сначала вспомним, что такое raw type. В Java так называют generic-типы без указания типа-параметра.
Такая языковая конструкция валидна, но в большинстве случаев приводит к предупреждению
компилятора.
 
Предупреждение связано с риском получения проблемы heap pollution. Ей мы уже посвящали
публикации ранее. Использование raw types никогда не оправдано – спецификация языка явно
говорит: их поддержка остается только для обратной совместимости.
 
Есть всего три случая, когда использовать обобщенный тип без параметра правильно:
• Целевая версия Java < 5.0 (2002 год и ранее – вряд ли это ваш случай);
• В литерале класса. List<String>.class не сработает, нужно писать List.class;
• В операторе instanceof. Вместо instanceof Set<Integer> должно быть instanceof Set.
 
17. Чем отличается CountDownLatch от CyclicBarrier?
 
CountDownLatch, дословно «Запор с обратным отсчетом», – примитив синхронизации из стандартной
библиотеки Java. Он останавливает пришедшие потоки, пока внутренний счетчик не достигнет нуля.
Чтобы поставить поток на ожидание, нужно вызвать из него метод await().
 
Начальное значение счетчика задается параметром конструктора, затем уменьшается на 1 методом
countDown(). Узнать текущее значение можно с помощью getCount(). Изменение значения счетчика
никак не связано с потоками, его можно вызывать откуда и когда угодно.
 
CyclicBarrier – барьер для потоков, который ломается при достижении критической массы ожидающих.
Это тоже класс из Java Concurrency Framework. Поток также встает на ожидание методом await().
Ожидающие потоки называются parties, их лимит также устанавливается в конструкторе.
 
Технически, parties барьера и count латча – одно и то же, await барьера – это await+countDown латча. В
барьере тоже доступна информация о текущем состоянии барьера (методы isBroken, getParties и
getNumberWaiting).
 
Помимо этого, CyclicBarrier дает две дополнительных возможности. Во-первых, в конструктор кроме
parties можно передать коллбэк с действием, которое выполнится в момент прорыва барьера. Во-
вторых, этот примитив переиспользуется: метод reset() насильно прорывает текущий барьер и
устанавливает новый.
 
Оба этих примитива помогают решить задачу о гарантированных дедлоках. Противоположность латча
и барьера – семафор. В нём потоки блокируются при достижении счетчиком нуля.
 
18. Какая разница между @Controller и @RestController?
 
Controller – это один из стереотипов Spring Framework. Компоненты такого типа обычно занимаются
обработкой сетевых запросов. Контроллер состоит из набора методов-обработчиков, помеченных
аннотацией @RequestMapping.
 
Ответ на запрос можно сформировать разными способами: например просто вернуть из обработчика
строку с именем jsp-файла, или же вернуть ResponseBodyEmitter, который будет асинхронно
заполняться данными позже. Все возможные варианты перечислены в документации.
 
Большинство современных API реализуется по архитектуре REST. В ней каждая сущность доступна под
собственным URI. В методе-обработчике возвращается экземпляр класса этой сущности, который
преобразуется в ответ сервера одним из HttpMessageConverter-ов. Например, в JSON его превратит
MappingJackson2HttpMessageConverter. Чтобы использовать этот способ ответа, метод, или весь
контроллер, должен иметь аннотацию @ResponseBody.
 
@RestController – это просто сокращенная запись для @Controller + @ResponseBody.
 
19. Чем анонимный внутренний класс отличается от лямбды?
 
Лямбда-выражение имеет более легковесный синтаксис. Не нужно явно указывать тип
функционального интерфейса, который лямбда реализует – он автоматически выведется из контекста.
Лямбда-выражения добавлены в язык в первую очередь как синтаксический сахар.
 
С другой стороны, у класса есть поля. Экземпляр анонимного класса сохраняет свое состояние между
вызовами, и меняет его при необходимости. Для лямбды доступен лишь захват и effectively final
использование внешних переменных.
 
Лямбдой реализуется только функциональный интерфейс. Функциональный интерфейс – это тип с
единственным абстрактным методом. Анонимным классом же можно расширить любой
расширяемый класс или реализовать интерфейс с любым количеством абстрактных методов.
 
Анонимный класс создает новый скоуп, лямбда работает в текущем. Это значит, что объявление
переменной с именем, которое уже используется снаружи, в лямбде вызовет ошибку компиляции
«variable is already defined», в анонимном классе скроет (shadowing) внешнюю переменную.
 
С точки зрения реализации JVM, для лямбды не создается дополнительного .class файла, как это
происходит для анонимного класса. Соответственно, не происходит и обычной загрузки и
верификации класса. Вместо этого используется механизм invokedynamic и класс генерируется на лету
с помощью LambdaMetafactory. Так что лямбда-выражения обычно работают быстрее.
 
20. Зачем выбирать ReentrantLock вместо synchronized?
 
Объект класса ReentrantLock решает те же задачи, что и блок synchronized. Поток висит на вызове
метода lock() в ожидании своей очереди занять этот объект. Владеть локом, как и находиться внутри
блока synchronized может только один поток одновременно. unlock(), подобно выходу из блока
синхронизации, освобождает объект-монитор для других потоков.
 
В отличие от блока синхронизации, ReentrantLock дает расширенный интерфейс для получения
информации о состоянии блокировки. Методы лока позволяют еще до блокировки узнать, занят ли он
сейчас, сколько потоков ждут его в очереди, сколько раз подряд текущий поток завладел им.
 
Шире и возможные режимы блокировки. Кроме обычного ожидающего lock(), вариант tryLock() с
параметром ожидает своей очереди только заданное время, а без параметра – вообще не ждет, а
только захватывает свободный лок.
 
Еще одно отличие – свойство fair. Лок с этим свойством обеспечивает «справедливость» очереди:
пришедший раньше поток захватывает объект раньше. Блок synchronized не дает никаких гарантий
порядка.
 
21. Что происходит внутри HashMap.put()?
 
1. Вычисляется хэш ключа. Если ключ null, хэш считается равным 0. Чтобы достичь лучшего
распределения, результат вызова hashCode() «перемешивается»: его старшие биты XOR-ятся на
младшие.
 
2. Значения внутри хэш-таблицы хранятся в специальных структурах данных – нодах, в массиве. Из
хэша высчитывается номер бакета – индекс для значения в этом массиве. Полученный хэш обрезается
по текущей длине массива. Длина – всегда степень двойки, так что для скорости используется битовая
операция &.
 
3. В бакете ищется нода. В ячейке массива лежит не просто одна нода, а связка всех нод, которые туда
попали. Исполнение проходит по этой связке (цепочке или дереву), и ищет ноду с таким же ключом.
Ключ сравнивается с имеющимися сначала на ==, затем на equals.
 
4. Если нода найдена – её значение просто заменяется новым. Работа метода на этом завершается.
 
5. Если ноды с таким же ключом в бакете пока нет – добавляемая пара ключ-значение запаковывается
в новый объект типа Node, и прикрепляется к структуре существующих нод бакета. Ноды составляют
структуру за счет того, что в ноде хранится ссылка на следующий элемент (для дерева – следующие
элементы). Кроме самой пары и ссылок, чтобы потом не считать заново, записывается и хэш ключа.
6. В случае, когда структурой была цепочка а не дерево, и длина цепочки превысила 7 элементов –
происходит процедура treeification – превращение списка в самобалансирующееся дерево. В случае
коллизии это ускоряет доступ к элементам на чтение с O(n) до O(log(n)). У comparable-ключей для
балансировки используется их естественный порядок. Другие ключи балансируются по порядку имен
их классов и значениям identityHashCode-ов. Для маленьких хэш-таблиц (< 64 бакетов)
«одеревенение» заменяется увеличением (см. п.8).
 
7. Если новая нода попала в пустую ячейку, заняла новый бакет – увеличивается счетчик структурных
модификаций. Изменение этого счетчика сообщит всем итераторам контейнера, что при следующем
обращении они должны выбросить ConcurrentModificationException.
 
8. Когда количество занятых бакетов массива превысило пороговое (capacity * load factor), внутренний
массив увеличивается вдвое, а для всего содержимого выполняется рехэш – все имеющиеся ноды
перераспределяются по бакетам по тем же правилам, но уже с учетом нового размера.
 
22. Что происходит внутри TreeMap.put()?
 
TreeMap требует либо задать порядок ключей вручную (передать в конструктор Comparator), либо
чтобы они имели собственный естественный порядок (были Comparable).
 
Подобно нодам в хэш-таблице, внутренняя структура дерева строится из объектов внутреннего класса
узла – Entry. В каждом узле хранится информация о данных (пара key-value), и о положении в
структуре (ссылки на родительский узел, левую и правую ветви).
 
Сама структура представляет из себя красно-чёрное дерево относительно ключей. Не будем здесь
углубляться в детали его реализации. О нем важно знать два факта:
 
1. Это бинарное дерево поиска. Значит, каждый новый элемент начинает искать свое место в дереве,
сравниваясь с узлами начиная с корневого. Меньшие элементы движутся влево, большие – вправо.
Для этого и требуется наличие метода compare. Дойдя до конца, пара ключ-значение «повисает»
новым узлом.
 
2. Это самобалансирующееся дерево. Если какая-то ветка начинает становиться слишком длинной (а
её эффективность вырождаться в эффективность связного списка), происходит балансировка. В
результате этой операции правило из пунтка 1 остается в силе, но нагрузка на ветки
перераспределяется. Самое длинное поддерево становится выше самого короткого максимум на
один элемент.
 
23. Spring Actuator
 
Spring Actuator, а если быть точным то Spring Boot Actuator — это подпроект Spring Boot. Он позволяет
разработчику следить за состоянием своего приложение с минимальным вложением со стороны
разработчика. В спринг актуатор входит множество ендпоинтов, которые Вы как разработчик можете
вызвать и посмотреть различную статистику по своему веб приложению в реальном времени. От Вас
ничего, кроме как подключить библиотеку не требуется.
 
24. Функциональный интерфейс
 
Функциональный интерфейс в Java – это интерфейс, который содержит только 1 абстрактный метод.
Основное назначение – использование в лямбда выражениях и method reference.
Наличие 1 абстрактного метода - это единственное условие, таким образом функциональный
интерфейс может содержать так же default и static методы.
К функциональному интерфейсу можно добавить аннотацию @FunctionalInterface. Это не
обязательно, но при наличии данной аннотации код не скомпилируется, если будет больше или
меньше, чем 1 абстрактный метод.
Рекомендуется добавлять @FunctionalInterface. Это позволит использовать интерфейс в лямбда
выражениях, не остерегаясь того, что кто-то добавит в интерфейс новый абстрактный метод и он
перестанет быть функциональным.
В Java есть встроенные функциональные интерфейсы, размещенные в пакете java.util.function.
Но оказывается есть один тонкий момент, описанный в Java Language Specification: “interfaces do not
inherit from Object, but rather implicitly declare many of the same methods as Object.”
Это означает, что функциональные интерфейсы могут содержать дополнительно абстрактные методы,
определенные в классе Object.
 
25. Утечка памяти
 
Утечка памяти — это ситуация, когда в куче присутствуют объекты, которые больше не используются,
но сборщик мусора не может удалить их из памяти и, таким образом, они сохраняются там без
необходимости.
Утечка памяти плоха тем, что она блокирует ресурсы памяти и со временем снижает
производительность системы. Если с ней не бороться, приложение в конечном итоге исчерпает свои
ресурсы и завершится с фатальной ошибкой java.lang.OutOfMemoryError.
Существует два различных типа объектов, которые находятся в Heap-памяти (куче) — со ссылками и
без них. Объекты со ссылками — это те, на которые имеются активные ссылки внутри приложения, в
то время как на другие нет таких ссылок.
Сборщик мусора периодически удаляет объекты без ссылок, но он никогда не собирает объекты, на
которые все еще ссылаются. В таких случаях могут возникать утечки памяти:
Признаки утечки памяти
 Серьезное снижение производительности при длительной непрерывной работе приложения
 Ошибка кучи OutOfMemoryError в приложении
 Спонтанные и странные сбои приложения
 В приложении время от времени заканчиваются объекты подключения
Типы утечек памяти:
 Утечка памяти через статические поля
Первый сценарий, который может привести к потенциальной утечке памяти, — это интенсивное
использование статических переменных.
В Java статические поля имеют срок жизни, который обычно соответствует полному жизненному циклу
запущенного приложения (за исключением случаев, когда ClassLoader получает право на сборку
мусора).
 Через незакрытые ресурсы
Всякий раз, когда мы создаем новое соединение или открываем поток, JVM выделяет память для этих
ресурсов. В качестве примера можно привести соединения с базой данных, входные потоки и объекты
сессий.
Забыв закрыть эти ресурсы, можно заблокировать память, что сделает их недоступными для GC. Это
может произойти даже в случае исключения, которое не позволяет программному процессу достичь
оператора, выполняющего код для закрытия этих ресурсов.
В любом случае, открытые соединения, оставшиеся от ресурсов, потребляют память, и если с ними не
разобраться, они могут ухудшить производительность и даже привести к ошибке OutOfMemoryError.
 Неправильная имплементация equals() и hashCode()
При определении новых классов очень распространенной ошибкой является отсутствие надлежащих
переопределенных методов для equals() и hashCode().
HashSet и HashMap используют эти методы во многих операциях, и если они переопределены
неправильно, то могут стать источником потенциальных проблем с утечкой памяти.
 Singleton
Как только экземпляр-синглтон был инициализирован, он остаётся в памяти на всё время жизни
приложения. Как следствие, данный экземпляр не сможет быть собран сборщиком. Данный паттерн
стоит применять лишь тогда, когда это обосновано реальными требованию к постоянному хранению в
памяти.
А теперь для закрепления несколько советов, как избежать проблемы с утечками памяти:
1. Используйте профайлеры. Профайлер помогает увидеть, какие объекты располагаются в куче (а
также просмотреть их прямо по экземплярам), что позволит на ранних стадиях отловить утечки
2. Осторожнее используйте строковые операции, особенно в случаях, когда программа работает над
обработкой множества текстовых данных.
3. Всегда внимательно следите, нужны ли вам нестатичные внутренние классы, статичные
переменные
4. Очищайте коллекции объектов после того, как данные были обработаны и не нуждаются в
дальнейшем использовании
 
 
 
 

You might also like