Uploaded by Илья Тринько

Answers to the questions 1

advertisement
Ссылки:
Java Interview Review – подготовка к собеседованиям Java,
http://java-course.ru/begin/incapsulation,
Техническое интервью Java Developer - Михаил Кончиц
ООП, Инкапсуляция, Абстракция, Полиморфизм, Наследование,
Композиция
ООП - Объектно-ориентированная идеология разрабатывалась как попытка связать поведение
сущности с ее данными и проецировать объекты реального мира и бизнес-процессов в программный
код. Задумывалось, что такой код проще читать и понимать человеком, т. к. людям свойственно
воспринимать окружающий мир как множество взаимодействующих между собой объектов,
поддающихся определенной классификации.
Не следует думать, что ООП каким-то чудным образом ускорит написание программ, и ожидать
ситуацию, когда жители Вилларибо уже выкатили ООП-проект в работу, а жители Виллабаджо все
еще отмывают жирный спагетти-код. В большинстве случаев это не так, и время экономится не на
стадии разработки, а на этапах поддержки (расширение, модификация, отладка и тестирование), то
бишь в долгосрочной перспективе.
Однако, значительную часть жизненного цикла большинства современных проектов составляют
именно поддержка и расширение. Само по себе наличие ООП не делает вашу архитектуру
безупречной, и может наоборот привести к излишним усложнениям.
Инкапсуляция - Это способность класса объединять данные и методы (состояние и поведение) в
единый объект через сокрытие реализации и выставления только тех методов которыми безопасно
пользоваться (некий контракт). Для пресечения случайного доступа и сокращение зависимостей на
полях и данных в других частях программы(low coupling).
Применяется для того чтобы нельзя было завязаться на полях или методах которые могут
измениться (и удалиться), случайно изменить данные в классе нарушив логику работы во всех
местах где были применены.
Полиморфизм - Это возможность работать с несколькими типами данных как с одним(являются
производными его базового типа), при этом поведение каждого типа будет уникальным в
зависимости от его реализации. Полиморфизм требует наследования или реализации интерфейса.
Пример: С разными классами работают как с одним.
Наследование - Это механизм, который позволяет создавать классы на основе других классов.
Необходимо для того, что бы не дублировать ранее написанный код, а использовать его же, но с
расширенными возможностями.
Все классы в мире java, косвенно или прямо, являются наследниками класса Object.
Наследоваться можно только от одного класса.
Связь между объектами в ООП:
● Наследование (IS A / является) - Лев является Животным
● Ассоциация, частные случаи - Композиция и Агрегация (HAS A / является частью) означает,
что объекты двух классов могут ссылаться один на другой, иметь некоторую связь между друг
другом. Ассоциация и есть описание связи между двумя объектами. Студент учится у
Преподавателя.
● Агрегация — отношение когда один объект является частью другого. Например Студент
входит в разные группы, может быть частью других объектов.
● Композиция — более «жесткое отношение, когда объект является частью другого объекта и
не может принадлежать еще кому-то. Двигатель может быть и без машины, но не сможет
быть в двух машинах одновременно.
Абстракция - Понятие которое оборачивает все 3 остальных, Абстракция - это выделение значимых
свойств и отбрасывание второстепенных. Также сокрытие деталей реализации и предоставление
простого интерфейса(точек доступа) взаимодействия со сложным механизмом.
Например Абстрактный класс используется как раз для выделения общей логики для классов
наследников (общее поведение), а реализацию второстепенных деталей оставляем классам
наследникам.
Пример автомобиль - много брендов и различий но у нас в голове всплывает какой-то общий образ с
набором характеристик которым должны следовать все автомобили чтобы являться автомобилями.
Пример инкапсуляции для абстракции чтобы управлять сложной коробкой передач нам не нужно
знать как она устроена внутри, достаточно иметь один рычаг(интерфейс, точку доступа) чтобы все
работало, иначе можно что-то сломать если переключать передачи не через рычаг и делать это
дольше.
DateTime
В пакете java.util расположены старые классы стандартной библиотеки Java: Date Эти классы
обладали рядом известных проблем. Экземпляры были изменяемыми, что делало их потоконебезопасными.
Постепенно стандартом де-факто стала сторонняя библиотека Joda-Time.
В Java 8 был добавлен пакет java.time, который взял решения из Joda-Time в стандарт, создатель
библиотеки участвовал в разработке. Ключевые классы пакета:
• LocalDate, LocalTime и LocalDateTime – локальные для пользователя дата/время.
• ZonedDateTime – дата/время в определенной часовой зоне.
• Period и Duration – периоды дат и времени соответственно.
IO vs NIO
IO
Потокоориентированный
Блокирующий (синхронный) ввод/вывод
Потокоориентированный и буфер-ориентированный
ввод/вывод
Основное отличие между двумя подходами
NIO
Буфер-ориентированный
Неблокирующий (асинхронный) ввод/вывод
Селекторы
в организации ввода/вывода
Потокоориентированный ввод/вывод подразумевает чтение/запись из потока/в поток одного или
нескольких байт в единицу времени поочередно. Данная информация нигде не кэшируются. Таким
образом, невозможно произвольно двигаться по потоку данных вперед или назад(В NIO МОЖНО).
Если вы хотите произвести подобные манипуляции, вам придется сначала кэшировать данные в
буфере.
Потоки ввода/вывода (streams) в Java IO являются блокирующими. Это значит, что когда в потоке
выполнения (tread) вызывается read() или write() метод любого класса из пакета java.io.*, происходит
блокировка до тех пор, пока данные не будут считаны или записаны. Поток выполнения в данный
момент не может делать ничего другого.
Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала (channel) и
получать только то, что доступно на данный момент, или вообще ничего, если доступных данных
пока нет. Вместо того, чтобы оставаться заблокированным пока данные не станут доступными для
считывания, поток выполнения может заняться чем-то другим.
Селекторы в Java NIO позволяют одному потоку выполнения мониторить несколько каналов ввода.
Class
ClassLoaders
В JVM встроено как минимум три стандартных загрузчика:
Bootstrap – встроенная в JVM нативная реализация, родитель для всех остальных загрузчиков.
Загружает часть стандартных классов java.*;
Platform – отвечает за загрузку стандартных классов Java-рантайма. До Java 9 назывался Extension и
занимался загрузкой расширений. Гарантируется, что ему будут видны (но не факт что загружены
непосредственно им) все стандартные классы Java SE и JDK;
System (Application) – загружает классы из classpath конкретного приложения;
Перед тем как загрузить класс, ClassLoader проверит, не может ли это сделать его родитель. Если
класс уже загружен, то загрузка не потребуется.
Процесс загрузки класса
Сначала класс и цепочка его предков должны быть загружены, сверху вниз. Класс загружается
только один раз, при первом к нему обращении в рамках одного класслоадера.
После загрузки каждого класса выделяется память под его статические поля и выполняются
статические блоки инициализации.
Далее инстанцируется сам экземпляр. Как и с загрузкой классов, процесс выполняется для всей
цепочки наследования, с самого дальнего родителя:
1. Выделяется память в куче для экземпляра, получается ссылка на этот экземпляр;
2. Выполняются инициализации нестатических полей и блоков инициализации в порядке
объявления;
3. Вызывается конструктор;
Классы в Java
В качестве типа переменной в Java может использоваться одна из пяти сущностей:
1. примитивный тип;
2. class – сложный тип, с поведением (API), возможно его реализацией и внутренним состоянием;
3. enum – перечисление. Тип с заданным конечным набором возможных значений;
4. interface – только описание поведения, без состояния. С Java 8 может иметь дефолтную
реализацию. Возможно множественное наследование интерфейсов;
5. @interface – аннотация. Пассивная метаинформация уровня класса: само ее наличие, параметры
и поведение. Обычно используется для описания особых свойств (@Deprecated), для использования
фреймворками (@Test) или инструментами разработки (@NotNull);
Остальные типы называются ссылочными – переменная несет не значение, а ссылку на экземпляр.
Для каждого ссылочного типа при компиляции создается отдельный .class файл. Его содержимое
рассмотрим позднее.
Класс бывает:
1. Абстрактный – помеченный ключевым словом abstract. Не может иметь экземпляры, может иметь
нереализованные абстрактные методы, также с модификатором abstract и без тела.
2. Внутренний (inner, non-static nested) – объявленный внутри другого класса. Не может иметь
статических объявлений. Имеет доступ ко всем внутренностям экземпляра внешнего класса. Если
член внешнего класса foo перекрыт членом внутреннего (shadowing), обратиться к внешнему можно
с помощью конструкции OuterClassname.this.foo, без перекрытия сработает просто foo.
Инстанциируется только от экземпляра внешнего класса: outer.new Inner();
3. Вложенные (nested, inner static) – имеет доступ ко всем статическим членам внешнего класса. В
остальном ничем не отличается от обычного класса;
4. Локальный – объявленный внутри метода. Является внутренним классом, в случае объявления в
статическом методе без доступа к экземпляру внешнего класса. Не имеет модификаторов доступа;
5. Анонимный – локальный класс, объявленный без имени, непосредственно при инстанциировании,
расширением другого класса или интерфейса. В отличие от других вложенных классов, анонимный
может расширять только один класс или интерфейс. Не может быть абстрактным или финальным.
Лямбда-выражение является сокращенной записью создания объекта анонимного наследника
функционального интерфейса;
6. Финальный – с модификатором final, нерасширяемый;
Внутренние и вложенные классы могут иметь несколько уровней вложенности. Модификаторы
abstract и final несовместимы, но по отдельности применимы к различным внутренним классам
(кроме анонимного).
Reflection, рефлексия
это средства манипуляции данными на основе знания о структуре классов этих данных,
инструменты метапрограммирования.
Использование Reflection API медленное и небезопасное. Оно позволяет ломать инвариантность
состояний экземпляра, нарушать инкапсуляцию, и даже менять финальные поля.
Использовать рефлексию естественно в тестовом коде, в инструментах разработки, в фреймворках
(особенно в связке с runtime-аннотациями).
Один из самых близких для backend-разработчика примеров – инициализация классовконфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности таких
классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как объявления
элементов экосистемы фреймворка.
Абстрактный класс vs интерфейс
Интерфейс есть средство наследования API, абстрактный класс — средство наследования
реализации
1 - Интерфейс описывает только поведение. А абстрактный класс описывает состояние и поведение.
2 - Абстрактный класс - строит иерархию наследования с отношением IS A(близкая связь).
Интерфейс - устанавливает контракт (классы, у которых вообще нет ничего общего).
3 - Классы могут extends только один раз, implements много.
Класс Object, методы 7 методов
Object - superclass всех классов в джава которые неявно наследуются от него.
● public final native Class<?> getClass()
Возвращает класс этого экземпляра. То есть результатом вызова .getClass() переменной типа
Foo может быть как Foo.class, так и .class любого из его подклассов. Компилятор страхуется
от ClassCastException в рантайме подменой возвращаемого типа метода на Class<? extends
Foo>.
● public native int hashCode() и public boolean equals(Object obj) Collection
hashCode = Для более эффективного поиска , quals - для разрешения коллизий.
Эти два метода придуманы для использования в Collections и связаны общим контрактом, для
соблюдения которого переопределять их необходимо вместе. Методы обязательно нужно
переопределить чтобы эффективно использовать экземпляры как ключи в HashMap или HashSet.
HashMap работает тем эффективнее, чем «лучше» распределение хэшей.
Контракт:
Если объекты equals, у них должны быть
одинаковые hashCode, но если hashCode
одинаковый объекты не всегда равны
(Одинаковые поля для хэш кода или
полученно предельное значение int) тогда это коллизия.
equals должен быть отношением
эквивалентности:Рефлексивность
a.equals(a) - true;Симметрия a.equals(b),
то b.equals(a);Транзитивность a.equals(b)
и a.equals(c), то b.equals(c)
Согласованность equals и hashCode
должны возвращать одни и те же
значения для одного и того же объекта
при каждом последующем вызове, даже если состояние объекта изменилось. Это делает
реализацию для изменяемых (mutable) объектов крайне сложной.Ничто не может быть equals(null).
По умолчанию equals сравнивает на == (по ссылке). Дефолтным hashCode зависит от реализации
JVM, но обычно случайное число.
●
protected native Object clone() throws CloneNotSupportedException
По умолчанию protected – потому что универсальной реализации нет, а вызов приведет к
CloneNotSupportedException. Нужно писать свою реализацию, делать при этом ее public и
добавлять классу маркерный интерфейс Cloneable. По умолчанию делает «поверхностное
копирование», то есть поля-ссылки копи будут вести на копии полей оригинала.
Поэтому нужно делать “Глубокие копии” где изменяемые данные с вложенной структурой
тоже копируются, а не только их ссылки.
Это диктуется соглашением, по которому клон не должен зависеть от оригинала.
По контракту клон должен быть другим объектом (!= оригиналу). Рекомендуется, чтобы все
классы иерархии реализовывали Cloneable, реализация метода начиналась с super.clone()
(если родитель не Object), а результат был equals и имел тот же класс что и оригинал.
Альтернативы (многие считают что более удобные) метода clone - конструктор копирования и
паттерн factory method.
●
●
●
public String toString()
По дефолту возвращает string представление класса:
getClass().getName() + '@' + Integer.toHexString(hashCode())
public final native void notify(), final native void notifyAll() MultiThreading
public final void wait() throws InterruptedException MultiThreading
Монитор в java - Это любой объект по тому что у него есть методы унаследованные от Object:
wait, notify и когда только один поток может выполнять код в синхронном блоке или методе.
1. wait() — переводит вызывающий поток в состояние ожидания. В этом случае вызывающий
поток освобождает монитор, который имеет доступ к ресурсу. Ожидание продолжается до тех
пор, пока другой поток, который вошел в монитор, не вызовет метод
2. notify() — возобновляет выполнение потока, из которого был вызван метод wait() для того
же объекта.
3. notifyAll() — возобновляет выполнение всех потоков, из которых был вызван метод wait()
для того же объекта. Управление передается одному из этих потоков.
Поток, вызывающий эти методы на любом объекте, должен иметь так называемый монитор
(механизм многопоточного доступа к объекту). Если же его нет, то будет брошено исключение
java.lang.IllegalMonitorStateException.
protected void finalize() throws Throwable - deprecated не используется, раньше вызывался при
сборке мусора(но не всегда) был ненадежен.
Модель памяти java
Stack – место под примитивы и ссылки на объекты (но не сами объекты). Хранит локальные
переменные и возвращаемые значения функций. Здесь же хранятся ссылки на объекты пока те
конструируются. Все данные в стеке – GC roots. Освобождается сразу на выходе из функции.
PermGen – В этой области хранятся загруженные классы (экземпляры класса Class<T>). Здесь же с
Java 7 хранится пул строк. Изначально размера -XX:PermSize, растет динамически до XX:MaxPermSize. Не считается частью кучи.
Metaspace – с Java 8 заменяет permanent generation. Отличие в том, что по умолчанию metaspace
ограничен только размерами доступной на машине памяти, но так же как PermGen может быть
ограничен, параметром -XX:MaxMetaspaceSize.
Heap – куча, вся managed-память, в которой хранятся все пользовательские объекты. Все
следующие разделы – части кучи. Параметры -Xms, -Xmn и -Xmx устанавливают начальный,
минимальный и максимальный размеры хипа соответственно.
Eden, New Generation, Old Generation и другие – специфичные для сборщика мусора части кучи,
поколения. Могут быть разные, но общий подход сохраняется: долго живущий объект постепенно
двигается во всё более старое поколение; сборка мусора в разных поколениях происходит
раздельно; чем поколение старше, тем сборка в нём реже, но и дороже.
Модель памяти статья.Статья с примерами
Java-модель памяти, используемая внутри JVM, делит память
на стеки потоков (thread stacks) и кучу (heap). Эта диаграмма
иллюстрирует Java-модель памяти с логической точки зрения:
Каждый поток имеет свой собственный стек. Стек содержит
информацию о том, какие методы вызвал поток. Я буду
называть это «стеком вызовов». Как только поток выполняет
свой код, стек вызовов изменяется.
Стек потока содержит все локальные переменные для каждого выполняемого метода. Поток может
получить доступ только к своему стеку. Локальные переменные, невидимы для всех других потоков,
кроме потока, который их создал. Даже если два потока выполняют один и тот же код, они всё равно
будут создавать локальные переменные этого кода в своих собственных стеках. Таким образом,
каждый поток имеет свою версию каждой локальной переменной.
Все локальные переменные примитивных типов (boolean, byte, short, char, int, long, float, double)
полностью хранятся в стеке потоков и не видны другим потокам. Один поток может передать копию
примитивной переменной другому потоку, но не может совместно использовать примитивную
локальную переменную.
Куча(Heap) содержит все объекты, созданные в вашем приложении, независимо от того, какой поток
создал объект. К этому относятся и версии объектов примитивных типов (например, Byte, Integer,
Long и т.д.)
Ниже диаграмма, которая иллюстрирует стек вызовов и локальные переменные (они хранятся в
стеках), а также объекты (они хранятся в куче):
Локальная переменная может быть примитивного типа, в этом
случае она полностью хранится в стеке потока.
Локальная переменная также может быть ссылкой на объект. В
этом случае ссылка (локальная переменная) хранится в стеке
потоков, но сам объект хранится в куче.
Объект может содержать методы, и эти методы могут содержать
локальные переменные. Эти локальные переменные также
хранятся в стеке потоков, даже если объект, которому
принадлежит метод, хранится в куче.
Переменные-члены объекта хранятся в куче вместе с самим объектом. Это верно как в случае, когда
переменная-член имеет примитивный тип, так и в том случае, если она является ссылкой на объект.
Статические переменные класса также хранятся в куче вместе с определением класса.
К объектам в куче могут обращаться все потоки, имеющие ссылку на объект. Когда поток имеет
доступ к объекту, он также может получить доступ к переменным-членам этого объекта. Если два
потока вызывают метод для одного и того же объекта одновременно, они оба будут иметь доступ к
переменным-членам объекта, но каждый поток будет иметь свою собственную копию локальных
переменных.
Интерпретация – простое последовательное воспроизведение кода программы, команда за
командой.
AOT-компиляция (ahead-of-time, статическая) – процесс превращения текста на языке
программирования в нативный код на машинном языке. Так работают языки вроде C++.
JIT-компиляция (just-in-time, динамическая) – «умная» интерпретация. Среда выполнения
анализирует исполняемый код, оптимизируя часто вызываемые участки. Таким способом программа
работает значительно быстрее. Именно с JIT-компиляцией связана необходимость «прогрева»
программ перед тестированием производительности.
Типы данных, передача в метод
Типы-Почитать
Считается что есть 8
примитивных типов и
8 Wrapper для примитивов +
String (9 ссылочных).
Для всех классов-оберток над
примитивами и String кроме
Float и Double работает
механизм кэширования.
Некоторые значения
создаются на этапе
инициализации класса, и пере
используются когда объект
создается не оператором new
(например valueOf).
Кэшируемые значения – оба возможных Boolean,
Character до '\u007f' (127) и все целые числа от 128 до 127 включительно.
Значения кэшируются и во многих других
встроенных классах: BigDecimal, Currency, пустые
коллекции. Детали можно узнавать из исходников
и документаций, так как эти кэши реализованы не
на уровне JVM а в коде классов.
Существует метод интерн который помещает или берет из пула - intern()
Передача в методы -Почитать
public void method(String name(Аргумент / значение)){
name (параметр / переменная в метода со значением аргумента)
}
Java всегда передает параметры по значению.Это означает — "скопировать значение и передать
копию."В метод передается копия ссылки. Через которую можно изменять объект.
default, super, static
default - В Java 8 вместе с лямбдами и стримами появилась острая необходимость дополнить
стандартные интерфейсы новыми методами. Никто естественно не собирался ломать обратную
совместимость, и было предложено добавить методы по умолчанию.
Теперь добавление ключевого слова default к методу интерфейса позволяет добавить ему тело. Все
новые методы старых интерфейсов снабжаются дефолтной реализацией.
super
1. Задать нижнюю границу generic-типа: Consumer<? super Number> см. Generics
2. Обратиться к члену класса-родителя, который перекрыт (shadowed) членами наследника или
локальными переменными: int foo = super.foo
3. Вызвать в конструкторе конструктор родителя: SubClass() { super("subclass param"); }
static - Ключевое слово static используется для объявления вложенных классов, статических
методов, полей, блоков инициализации и статических импортов.
Статические поля и методы – члены класса а не экземпляра, потому к ним можно обращаться через
имя класса. Код статического блока или метода имеет доступ только к статическим членам.
Статические поля не участвуют в сериализации.
Для статических методов используется раннее связывание, то есть вызов конкретного метода
разрешается на этапе компиляции, не работают перегрузка и переопределение в наследниках.
Статический блок инициализации выполняется потокобезопасно, один раз сразу после загрузки
класса класслоадером. Инициализаторы статических полей выполняются в неявном статическом
блоке. Блоков может быть несколько, выполнятся они в порядке объявления.
Статический импорт (static import) импортирует статические члены классов в .java-файл.
Модификаторы
Порядок: @Аннотации, доступ, static final transient volatile
В методах: @Аннотации, доступ, abstract static final synchronized native
Модификаторы доступа.
Модификаторы для многопоточности synchronized и volatile(ниже - многопоток)
final
● class - запрет наследования
● method - запрет переопределения
● field - запрет изменения после инициализации
● field с сылочным типом данных - только ссылка будет не изменяемой, поля самого обьекта
можно изменить.
abstract (рассмотрим подробнее в разделе #Классы)
native – реализация метода скрыта внутри JVM, нельзя указывать в пользовательском коде
transient – поле будет пропущено при сериализации
Аннотации
RetentionPolicy:
SOURCE – аннотация присутствует только в исходном коде, но не вовлечена в компиляцию. Можно
разделить их на две категории:
● Первая – аннотации для программиста. Это всевозможные маркеры. Они добавляют
аннотируемым элементам некоторую специальную семантику. Более формализованный
вариант документации. Примеры – @Immutable и @ThreadSafe из Hibernate.
● Вторая категория – инструкции для инструментов разработки @SuppressWarnings и
@Override могут влиять на предупреждения и ошибки компиляции.
CLASS – самое экзотическое, но при том стандартное значение. Аннотация попадает в байткод
.class-файла, но игнорируется загрузчиком классов. В результате такая аннотация недоступна для
рефлекшна. Используется для сторонних инструментов, обрабатывающих байткод, например для
обфускаторов.
RUNTIME – самое ходовое значение. Цель снабжается метаинформацией, доступной во время
выполнения программы. Сама по себе аннотация всё так же не добавляет нового поведения. Для
практической пользы runtime-аннотации в программе должен быть исполнен некоторый код
процессинга, который прочитает метаинформацию инструментами Reflection API. Такой механизм
широко используется во множестве популярных фреймворков: Spring, Hibernate, Jackson.
Мета-аннотация @Target определяет, в каком контексте может применяться объявляемая
аннотация. Допустимые контексты перечисляются значениями ElementType. По умолчанию, если
@Target не указан, разрешены все контексты кроме TYPE_PARAMETER. Возможные таргеты:
• TYPE – Объявление класса, интерфейса, аннотации или enum-а.
• FIELD – Объявление поля (включая константы enum-ов).
• METHOD – Объявление метода.
• PARAMETER – Формальный параметр в объявлении метода.
• CONSTRUCTOR – Объявление конструктора.
• LOCAL_VARIABLE – Объявление локальной переменной.
• ANNOTATION_TYPE – Объявление аннотации. Применяется для создания мета-аннотации.
• PACKAGE – Объявление пакета (в package-info.java).
–– С Java 8 ––
• TYPE_PARAMETER – Объявление generic типа-параметра.
• TYPE_USE – Любое использование типа. Например приведение: (@NonNull String) myObject.
–– С Java 9 ––
• MODULE – Объявление модуля.
Повторяющиеся аннотации
Мета-аннотации – это аннотации для
объявления других аннотаций.
Вообще мета-аннотациями можно
назвать любую аннотацию с таргетом
ANNOTATION_TYPE, но основных в
Java существует 5. Они определяют
для аннотации:
• @⁠Retention – переживет ли компиляцию.
• @⁠Inherited – применяется к наследникам.
• @⁠Repeatable – применяема несколько раз к одному и тому же элементу.
• @⁠Target – контексты, в которых можно применять.
• @⁠Deprecated – не должна использоваться.
Функциональные интерфейсы
Это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без
тела. Статические методы и default методы при этом не в счёт.
Виды String
Кроме очевидного класса String, в стандарте Java существует еще StringBuffer и StringBuilder. Класс
String иммутабелен, а эти два вспомогательных класса реализуют для него паттерн Builder и служат
способом редактирования строки без относительно дорогого пересоздания объекта.
Все методы StringBuffer синхронны. В Java 1.5 ему на замену пришел несинхронизированный
вариант StringBuilder. Эта ситуация аналогична HashMap и Hashtable. В остальном эти два класса
почти ничем не отличаются, имеют одинаковый набор методов и конструкторов.
Для буфера и билдера не работает синтаксический сахар строк:
● Их нельзя создать литералом, вместо этого используется обычный конструктор;
● Нельзя конкатенировать +, вместо этого используются обычные методы insert и append.
Сам оператор конкатенации константных выражений, компилируется в интернированную строку, но
для не-констант неявно использует StringBuilder.
Исключения
Иерархия исключений:
Throwable, RunTime, Error, Exception - classes.
Error - ошибки jvm, исполняемой среды, выдернуть шнур из розетки.
Exception - ошибки которые мы можем обработать, ошибка в коде.
Хорошей практикой считается использовать не проверяемые исключения. Т.к. проверяемые
раскрывают инкапсуляцию(были введены давно и устарели), по комментарию к методу можно легко
понять что нужно ловить.
Во многих языках применяются только не проверяемые исключения.
Разница между в checked и unchecked что мы обязаны обработать checked.
Streams
Это средства потоковой обработки данных в функциональном стиле.
Источником может быть заранее заданный набор данных, или динамический генератор, возможно
даже бесконечный. Сам источник никогда не модифицируется последующими операциями.
Промежуточные операции модифицируют стрим. На одном потоке можно вызвать сколько угодно
промежуточных операций.
flatMap() - преобразует каждый
элемент в Stream, объединяет
Stream всех элементов в один
и передает его следующему
оператору.
Терминальная операция «потребляет» поток. Она может быть только одна, в конце работы с
отдельно взятым стримом. Стримы работают лениво – вся цепочка промежуточных операций не
начнет выполняться до вызова терминальной.
collect(Collector collector) – метод собирает все элементы в список, множество или другую
коллекцию, группирует элементы по какому-нибудь критерию, объединяет всё в строку и т.д.:
Параллельные стримы
Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной
обработки.
Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel().
Для распараллеливания используется единый общий ForkJoinPool. Дробит сложные операции на
простейшие и так рекурсивно до тех пор пока конечная операция не станет элементарной.
Выполняются на разных ядрах процессора - parallel() блокирует метод основного потока пока не
вернет результат.
Если обрабатываем мало данных в параллель стримах то преимущества не будут задействованы.
Когда огромное количество элементов и будут разделяться на множество форк джоинов производительность упадет еще больше чем при обычном стриме.
Collections
Почитать,Почитать получше, Почему map не коллекция
Collection – хранилище отдельных значений, Map – хранилище ключ-значение. Отсюда разные
методы этих интерфейсов. Если проще, разные сигнатуры методов put и add.
Collection в свою очередь делится на три основных группы, и соответствующих им интерфейса:
● List – упорядоченные списки с возможностью содержания дубликатов и доступа по индексу
(random access);
● Queue – обычно FIFO-коллекции, предполагает добавление/удаление элементов с края.
Интерфейс-наследник Deque – двусвязная очередь;
● Set – не обязательно упорядоченный набор уникальных (с точки зрения equals) значений;
HashMap можно привести к виду Collection вызвав например keySet(), entrySet() или values().
Самые популярные коллекции
ArrayList - Является реализацией динамического массива объектов. В основе обычный массив.
По дефолту размер 10 при добавлении 11 элемента пересчитывается (дорогая операция) и
становится 16 (на 50% больше). Расширяется путем копирования старого массива в новый большего
объема.
Следует применять, если предполагается частое обращение к элементам по индексу.
Не надо применять, если требуется частое удаление/добавление элементов в середину коллекции.
Быстрый доступ к элементам по индексу за время O(1);
Доступ к элементам по значению за линейное время O(n);
Медленный, когда вставляются и удаляются элементы из «середины» списка;
LinkedList - Представляет связанный список в каждом элементе которого(кроме первого и
последнего) хранится: ссылка на предыдущий элемент, значение, ссылка на следующий элемент.
У нас есть доступ к первому и последнему элементу в любой момент времени O(1).
Из LinkedList можно организовать стек, очередь, или двойную очередь, со временем доступа O(1);
На вставку и удаление из середины списка, получение элемента по индексу или значению
потребуется линейное время O(n). Однако, на добавление и удаление из середины списка,
используя потребуется O(1);
Использует много памяти.
HashMap
Почитать, Почитать 2, Java - урок 42.5 (Структура и принцип работы HashMap)
По дефолту размер 16 - initial Capacity, 0.75 load Factor. Если не хватит 16, увеличится в 2 раза.
Условия увеличения (initial Capacity) * (load Factor) = 16 * 0.75 = 12.
Ассоциативный массив(ключ - любой объект, значение - любой объект).
Структура данных для хранения связанных вместе пар “ключ-значение”.
Хэширует только ключи для быстрого поиска. Хэширование выполняется немного быстрее, чем
вставка в дерево, поэтому данные отображение используется, когда не требуется отсортированный
порядок ключей.
Нельзя сохранить 2 значения по одинаковым ключам, второе значение заменит первое
HashMap содержит в себе динамически расширяемый массив, элементы в котором называются
Bucket(Бочка). В каждой ячейка этого массива находится связный список наподобие LinkedList из
элементов class Node.
Обычно один класс Node должен занимать одну ячейку и его поле next = null, но при обработки
коллизии(когда несколько объектов распределяются в одну ячейку) мы сравниваем ключи если они
разные тогда мы добавляем в ячейку еще один элемент, если одинаковые заменяем Node на новый.
Потокобезопасные коллекции:
HashTable - методы обращения синхронизированы (медленно)
ConcurrentHashMap - синхронизирует только сегменты, не полностью, делаю остальную часть
доступной для многопоточности.
CopyOnWriteArrayList - по принципу fail-safe, каждый поток работает со своей копией листа(не
возникает ConcurrentModificationException).
Generics
Основная идея показать - ограничить какаой тип данных , по сути мы говорим с какаими типами
данных может работать класс или метод.
● Типы дженерики обеспечивают параметрический полиморфизм, т.е выполнение идентичного
кода для различных типов. Если к разным типам можно безопасно применять одну и ту же
логику.Типичный пример — коллекции, итераторы
● type-erasure — это стирание информации о типе-параметре в runtime. Таким образом, в байткоде мы увидим List, Set вместо List<Integer>, Set<Integer>, ну и type-cast'ы при
необходимости
Обобщения или generics (обобщенные типы и методы) позволяют нам уйти от жесткого определения
используемых типов.
Когда мы используем generic мы ограничиваем тип данных с которым класс или метод может
работать.Не может работать с примитивами.
Можно обходиться и без generic но придется писать больше кода(реализация для каждого
конкретного типа, не нужно проверять типы при class casting для защиты от ClassCastException) и
менее безопасно.
<? extends Number> - Number и его наследники
<? super Number> - Number и его предки(Object в данном случае)
Вывод типов
Для начала разберемся, что такое вывод типов. Type inference – это способность компилятора
догадаться, какой тип нужно подставить, и сделать это за вас. Вывод происходит статически, только
на основании типов аргументов и ожидаемого типа результата
Виды ссылок
Обычная жесткая ссылка – любая переменная ссылочного типа. Очистится сборщиком мусора не
раньше, чем станет неиспользуемой (перестанет быть доступной из GC roots, подробнее в
следующих постах).
SoftReference – мягкая ссылка. Объект не станет причиной израсходования всей памяти –
гарантированно будет удален до возникновения OutOfMemoryError. Может быть раньше, зависит от
реализации сборщика мусора.
WeakReference – слабая ссылка. Слабее мягкой. Не препятствует утилизации объекта, сборщик
мусора игнорирует такие ссылки.
PhantomReference – фантомная ссылка. Используется для «предсмертной» обработки объекта:
объект доступен после финализации, пока не очищен сборщиком мусора.
Многопоточность
Мьютекс — это специальный объект для синхронизации потоков. Он есть у каждого объекта в java.
Задача мьютекса — обеспечить такой механизм, чтобы доступ к объекту в определенное время был
только у одного потока.
Возможны только два состояния — «свободен» и «занят», состояниями нельзя управлять напрямую
Монитор — это дополнительная «надстройка» над мьютексом, «невидимый» для программиста
кусок кода. В блоке кода, который помечен словом synchronized, происходит захват мьютекса
объекта.
Защитный механизм создает именно монитор! Компилятор преобразует слово synchronized в
несколько специальных кусков кода. Из блока synchronized =>
По сути, монитор в Java выражен с
помощью слова synchronized. Весь код,
который появился вместо слова
synchronized в последнем примере, — это
и есть монитор.
Семафор — это средство для
синхронизации доступа к какому-то
ресурсу, при создании механизма
синхронизации он использует счетчик
который указывает сколько потоков
одновременно могут получать доступ к
общему ресурсу. (мьютекс = двоичный
семафор - счетчик с 1 разрешением)
Semaphore(int permits) \ (int permits, boolean fair)
permits — сколько потоков одновременно могут иметь доступ к общему ресурсу;
fair = true, доступ предоставляется ожидающим потокам в том порядке, в котором они его
запрашивали.
Метод acquire() запрашивает разрешение на доступ к ресурсу у семафора. Если счетчик > 0,
разрешение предоставляется, а счетчик уменьшается на 1.
Метод release() «освобождает» выданное ранее разрешение и возвращает его в счетчик
(увеличивает счетчик разрешений семафора на 1).
synchronized - Можно применять как модификатор метода, и как самостоятельный оператор с
блоком кода. Выполняет код при захваченном мониторе объекта. В виде оператора объект
указывается явно. В виде модификатора нестатического метода используется this, статического –
.class текущего класса.
volatile поле
1 - Она всегда будет атомарно читаться и записываться. Даже если это 64-битные double или long.
2 - Java-машина не будет помещать ее в кэш. Так что ситуация, когда 10 потоков работают со
своими локальными копиями исключена.
Значит, между ними существует отношение happens-before. Это значит, что существует гарантия,
что произошедшее в памяти до записи будет видно после чтения. То есть будут успешно прочитаны
значения, записанные в другие переменные.
CountDownLatch, дословно «Запор с обратным отсчетом», – примитив синхронизации из
стандартной библиотеки Java. Он останавливает пришедшие потоки, пока внутренний счетчик не
достигнет нуля. Чтобы поставить поток на ожидание, нужно вызвать из него метод await().
Начальное значение счетчика задается параметром конструктора, затем уменьшается на 1 методом
countDown(). Узнать текущее значение можно с помощью getCount(). Изменение значения счетчика
никак не связано с потоками, его можно вызывать откуда и когда угодно.
CyclicBarrier – барьер для потоков, который ломается при достижении критической массы
ожидающих. Это тоже класс из Java Concurrency Framework. Поток также встает на ожидание
методом await(). Ожидающие потоки называются parties, их лимит также устанавливается в
конструкторе.
Технически, parties барьера и count латча – одно и то же, await барьера – это await+countDown
латча. В барьере тоже доступна информация о текущем состоянии барьера (методы isBroken,
getParties и getNumberWaiting).
Помимо этого, CyclicBarrier дает две дополнительных возможности. Во-первых, в конструктор кроме
parties можно передать коллбэк с действием, которое выполнится в момент прорыва барьера. Вовторых, этот примитив переиспользуется: метод reset() насильно прорывает текущий барьер и
устанавливает новый.
Есть специальный пакет с классами Atomic в java.
Пакет Concurrency
Механизм CAS(Compare and Swap)
Механизм микропроцессорного взаимодействия который позволяет на основании оптимистичной
блокировки осуществлять синхронизацию между потоками. Применяется в АТОМИК классах.
Алгоритм:
1. Место в памяти для работы (M)
2. Существующее ожидаемое значение (A) переменной
3. Новое значение (B), которое необходимо установить
Операция CAS атомарно обновляет значение в M до
B, но только если существующее значение в M
совпадает с A, в противном случае никаких действий
не предпринимается.
Когда несколько потоков пытаются обновить одно и то
же значение через CAS, один из них выигрывает и
обновляет значение.
Зацикленность постоянна пока ожидаемое значение
не совпадает.
Объект класса ReentrantLock решает те же задачи, что и блок synchronized. Поток висит на вызове
метода lock() в ожидании своей очереди занять этот объект. Владеть локом, как и находиться внутри
блока synchronized может только один поток одновременно. unlock(), подобно выходу из блока
синхронизации, освобождает объект-монитор для других потоков.
В отличие от блока синхронизации, ReentrantLock дает расширенный интерфейс для получения
информации о состоянии блокировки. Методы лока позволяют еще до блокировки узнать, занят ли
он сейчас, сколько потоков ждут его в очереди, сколько раз подряд текущий поток завладел им.
Шире и возможные режимы блокировки. Кроме обычного ожидающего lock(), вариант tryLock() с
параметром ожидает своей очереди только заданное время, а без параметра – вообще не ждет, а
только захватывает свободный лок.
Еще одно отличие – свойство fair. Лок с этим свойством обеспечивает «справедливость» очереди:
пришедший раньше поток захватывает объект раньше. Блок synchronized не дает никаких гарантий
порядка.
ExecutorService
Данное средство служит альтернативой классу Thread, предназначенному для управления потоками.
Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо
создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи,
а значит, мы можем создать в приложении столько задач, сколько хотим, используя один
исполнитель.
Executor пакета java.util.concurrent не требуется прибегать к низкоуровневой поточной
функциональности класса Thread, достаточно создать объект типа ExecutorService с нужными
свойствами и передать ему на исполнение задачу типа Callable. Впоследствии можно легко
просмотреть результат выполнения этой задачи с помощью объекта Future.
Интерфейс ExecutorService расширяет свойства Executor, дополняя его методами управления
исполнением и контроля. Так в интерфейс ExecutorService включен метод shutdown(), позволяющий
останавливать все потоки исполнения, находящиеся под управлением экземпляра ExecutorService.
Также в интерфейсе ExecutorService определяются методы, которые запускают потоки исполнения
FutureTask, возвращающие результаты и позволяющие определять статус остановки.
Callable vs Runnable
Кроме Runnable, исполнители могут принимать другой вид задач, который называется Callable.
Callable — это также функциональный интерфейс, но, в отличие от Runnable, он может возвращать
значение.
Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который
они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может
вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект
Future, у которого мы можем запросить результат задачи.
Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее
выполнения. Теперь future.isDone() вернет true, и мы увидим на консоли следующее:
InvokeAll - Исполнители могут принимать список задач на выполнение с помощью метода invokeAll(),
который принимает коллекцию callable-задач и возвращает список из Future.
InvokeAny- Другой способ отдать на выполнение несколько задач — метод invokeAny(). Он работает
немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна
задача, и возвращает ее результат.
Future vs CompletableFuture
CompletableFuture появился в Java 8. Это класс-реализация старого интерфейса Future, а значит всё
сказанное выше справедливо и для него. Вдобавок к этому, CompletableFuture реализует работу с
отложенными результатами посредством колбэков. Метод thenApply регистрирует код обработки
значения, который будет автоматически вызван позже, когда это значение появится.
Future - Java 5 (2004). Представляет пока еще не вычисленный результат. Когда породившая его
асинхронная операция заканчивается, он заполняется значением.
isDone() проверить что операция
завершена.
get() блокирует выполнение до
получения результата.
Future future =
ExecutorService.submit(Операция может
быть Runnable или Callable).
CompletableFutures Java 8 (2014).
Эволюция обычного Futures, позволяет
объединять в цепь задач(как стримы или
Mono - почти также обрабатываются
исключения). Вы можете использовать их
чтобы сказать “Сделай эту задачу, когда
она завершится сделай другую,
используя результат предыдущей".
Реализует работу с отложенными
результатами посредством колбэков.Вы
можете сделать что-то с результатом
операции без блокировки потока.
Fork/Join Framework
Выпуск 35. Как работает ForkJoinPool.
Дробит сложные операции на простейшие и так рекурсивно до тех пор пока конечная операция не
станет элементарной.
Сериализация и Маршалинг
Сериализация
Маршалинг - (выстраивание войск для парада) Создание структуры для передачи чего-то.
Мы маршируем данные из точки а в точку б. Также мы передаем структуру, что с этими данными
нужно обращаться определенным образом.
Spring Framework
Это популярных фреймворков для Java. Фреймворк — это внешний каркас, предоставляющий
заранее определенные точки расширения. В эти точки расширения вы и вставляете свой код, но
когда он будет вызван определяет именно фреймворк. Создает объекты классов и вызывать методы
за нас будет уже сам фреймворк.
Это скорее общее названия для целого ряда
небольших фреймворков, каждый из которых
выполняет какую-то свою работу.
Как видно, у спринга модульная структура. Это
позволяет подключать только те модули, что нам
нужны для нашего приложения и не подключать те,
которыми мы заведомо не будем пользоваться.
Именно этот подход и помог спрингу обойти своего
конкурента в то время (EJB).
Dependency Inversion(DI) - инверсию зависимостей, то есть попытки не делать жестких связей между
вашими модулями/классами, где один класс напрямую завязан на другой (SOLID - D):
● модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей
должны зависеть от абстракций;
● абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
IoC (Inversion of Control) — инверсия управления. Об этом я уже вскользь упоминал, когда писал, что
при использовании библиотеки вы сами прописываете в своем коде какой метод какого объекта
вызвать, а в случае с фреймворками — чаще всего уже фреймворк будет вызывать в нужный ему
момент тот код, который вы написали. То есть, тут уже не вы управляете процессом выполнения
кода/программы, а фреймворк это делает за вас. Вы передали ему управление (инверсия
управления). Некий принцип который говорит о том что сущность не сама создает свои зависимости,
они подставляются из вне, а Di это частный случай
Dependency Injection(DI) - это когда объекты котиков создаете не вы в main-е и потом передаете их в
свои методы, а за вас их создает спринг, а вы ему просто говорите что-то типа "хочу сюда получить
котика" и он вам его передает в ваш метод.
Это специальный паттерн, который уменьшает связь между компонентами. Таким образом, при
применении DI, ваш код становится чище, проще, его легче понять и тестировать. Согласно паттерну
DI, создание объектов для зависимостей переходит на фабрику или отдается третьей стороне. Это
означает, что мы можем сосредоточиться на использовании этих объектов вместо их создания.
ApplicationContext - это набор бинов (объектов), принцип реализации прост:
И у нас есть куча классов с кучей методов, где иногда нам нужен для метода котик, а для другого
метода — собачка, а в каких-то методах — все три.
Да, мы можем в main-е сначала создать эти три объекта, а потом их передавать в наши классы, а
уже внутри классов — в нужные нам методы... И так по всей программе. Сложно, не красиво.
Тогда мы решаем хранить мапу, где ключ - имя нужного нам объекта, а значением — сам объект.
Мы сможем доставать нужные нам объекты по их имени: get("попугайчик") и в ответ получили объект
попугайчика.Или ключ — это класс объекта, а значение — сам объект, тогда мы сможем указать уже
не имя объекта, а просто класс нужного нам объекта, тоже удобно.
Написать обертку над мапой, чтобы в каких-то случаях доставать объекты по их имени, а в других
случаях — по классу.
Мы переложили ответственность за создание объектов, передачу их в методы - спрингу.
Bean - это просто объект класса который может храниться в ApplicationContext и
связываться(подставляться) в нужное место при его внедрении. В Spring-е бином (bean) называют
любой класс, который управляется контейнером Spring. То есть такими вещами, как создание
экземпляра бина, его инициализация, внедрение зависимостей и параметров, деинициализация,
генерация всевозможных оберток над бином, занимается не наш код, а IoC-контейнер Spring.
Bean Scope - 6
● singleton - создается один раз инжектиться один и тот же бин.
● prototype - при каждом запросе создает новый.
-----------Web Scope---------● request - creates a bean instance for a single HTTP request
● session - creates a bean instance for an HTTP Session
● application -This is similar to the singleton scope, but there is a very important difference with
regards to the scope of the bean.
When beans are application scoped, the same instance of the bean is shared across multiple
servlet-based applications running in the same ServletContext, while singleton scoped beans are
scoped to a single application context only.
● websocket - When first accessed, WebSocket scoped beans are stored in the WebSocket session
attributes. The same instance of the bean is then returned whenever that bean is accessed during
the entire WebSocket session.
We can also say that it exhibits singleton behavior, but limited to a WebSocket session only.
Жизненный цикл Bean
С бинами происходит множество процессов под капотом. Во многие можно вмешаться, добавив
собственную логику в разные этапы жизненного цикла. Через следующие этапы проходит каждый
отдельно взятый бин:
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.
Не следует путать жизненный цикл отдельного бина с жизненным циклом контекста и этапами
подготовки фабрик бинов.
Типы Bean and Annotation
@SpringBootApplication - определяет автоматическое сканирование пакета, где находится класс
главный класс. Если ваш код целиком находится в указанном пакете или его подпакетах - ОК.
Но если бин вне этого пакета, вы должны использовать дополнительно аннотацию.
ComponentScan, где перечислите дополнительные пакеты для сканирования.
Эта аннотация включает другие:
1. @Configuration
2. @EnableAutoConfiguration
3. @ComponentScan
@ComponentScan - Спрингу нужно показать где искать бины дополнительно.
В параметре мы указываем пакеты, которые должны сканироваться, под-пакеты будут сканированы.
@Bean - Ставится над методом в классе с аннотацией @Configuration.
Он отделяет объявление компонента от определения класса и позволяет создавать и настраивать
компоненты на ваше усмотрение.По умолчанию имя компонента совпадает с именем метода.
@Component, Service, Repository - Используется для автоматического обнаружения бинов, спринг
сам настраивает их, между анноируемым классом и компонентом однозначное сопоставление
(По одному компоненту на класс).Service - Тоже самое что и Component но смысл что ставятся над
классами с бизнес логику.Repository - Тоже самое что и Component но смысл что ставятся над
классами с подключением к бд, также имеют обработку JDBC Exception.
@Autowired - Подставляет(Binding / Связывание) бин из контекста в нужное место: В сеттер,
конструктор (если он один можно просто создать приватное поле без @Autowired, все равно будет
инжект). Параметр required = false, если бин не найден не падает с NoSuchBeanDefinitionException.
Предположим, что бинов типа
ServiceDependency несколько (допустим
name1 и name2). Тогда, чтобы задать
конкретный, необходимо использовать
аннотацию @Qualifier(name1).
Best practice - не использовать @Autowired над полями:
Внедрении прямо в поля вы не предоставляете прямого способа создания экземпляра класса со
всеми необходимыми зависимостями.
Скрытие зависимостей - Когда класс более не отвечает за получение зависимостей, он должен явно
взаимодействовать с ними, используя публичные интерфейсы — методы или конструкторы. Таким
образом становится четко понятно, что требует класс, а также опциональные ли это зависомости
(через сеттеры) или обязательные (конструктор).
Зависимость от DI-контейнера - класс не должен зависеть от конкретного используемого контейнера.
Другими словами, это должен быть простой POJO-класс, экземпляр которого может быть создан
самостоятельно, если вы передадите ему все необходимые зависимости. Таким образом, вы можете
создать его в юнит-тесте без запуска контейнера и протестировать его отдельно.
@Controller - служит для авто обнаружения бина этого класса. Аннотацией @RequestMapping(value =
"/simple1") сообщаем, что данный контроллер будет обрабатывать запрос, URI которого "/simple1"
@RestController = @Controller + @ResponseBody
превращает помеченный класс в бин. Этот бин для конвертации входящих/исходящих данных
использует Jackson. Как правило данные представлены в json или xml.
ResponseEntity необходим, только если мы хотим кастомизировать ответ, добавив к нему статус
ответа. Во всех остальных случаях будем использовать @ResponseBody.
Для @ResponseBody единственные состояния статуса это SUCCESS(200), если всё ок и SERVER
ERROR(500), если произошла ошибка.
@RequestMapping - используется для маппинга (связывания) с URL для всего класса или для
конкретного метода обработчика.
@Scheduled - Мы можем запускать метод(задачу) по расписанию используя эту аннотацию.
У нее есть несколько свойств.
fixedDelay - задержка между успешным выполнением и новым запуском задачи.
fixedRate - запускает задачу через заданное время, не волнуется о прошлом выполнении.
cron = "0 15 10 15 * ?", zone = "Europe/Paris" - запускать в 10:15 каждый 15-й день месяца по Парижу.
MVC (Model-View-Controller)
Контроллер - управляет запросами
пользователя. Его основная функция —
вызывать и координировать действие
необходимых ресурсов и объектов.
Модель - Бизнес-логика.
Вид - обеспечивает различные способы
представления данных, которые получены
из модели.
Веб приложение обычно состоит из набора
контроллеров, моделей и видов.
Базы Данных
Теория
Типы связей таблиц - почитать
Золотой ключик. Он обозначает слово «один».Знак бесконечности. Он обозначает слово «многие»
one-to-many(левый рисунок)
Нужно реализовать некую БД, которая ведет учет данных о пользователях. У пользователя есть:
имя, фамилия, возраст, номера телефонов. При этом у каждого пользователя может быть от одного
и больше номеров телефонов (многие номера телефонов).Один учитель и предметы преподавания.
many-to-many(правый рисунок) - Для реализации связи многие ко многим нам нужен посредник
между двумя рассматриваемыми таблицами. Он должен хранить два внешних ключа, первый из
которых ссылается на первую таблицу, а второй — на вторую. Учители и ученики.
one-to-one - можно сказать, что — это разделение одной и той же таблицы на две.
primary key отличается от ограничения unique лишь тем, что не может принимать значения null.
Нормализация БД
1) Таблица не должна
содержать повторяющихся
столбцов или таких
столбцов, которые содержат
наборы значений.
2) Каждое запись должна иметь уникальный ключ(Primary key / id). Каждый столбец в таблице,
который не является ключом, должен зависеть от ключа.
3) Каждый столбец, не являющийся ключом, должен зависеть только от столбца, который
является ключом, то есть должна отсутствовать транзитивная функциональная зависимость
(transitive functional dependency)
Если столбец зависит не только от первичного ключа, то данный столбец находится не в той
таблице, в которой он должен находиться, либо же является производным от других
столбцов.
SQL
Тренажер и учебник
Kafka
Про Kafka (основы)
JPA
Spring Data JPA - теория
java persistence api - модуль упрощает процесс создания приложений которые работают с базой
данных, маппит сущности (объекты) в соответствующие им таблицы.
Download