Java: fill the gaps
12.9K subscribers
7 photos
215 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
IDEA: Code with me

Парное программирование - agile практика, когда два человека сидят за одним компьютером и работают над задачей. Один пишет код, другой - наблюдает.

Если партнёры хорошо сработаются, то код получится проще, а ошибок будет меньше.

Intellij IDEA недавно выпустила классный плагин Code with me, и теперь парным программированием можно заняться и на удалёнке.

Найти его легко: File → Settings → Plugins → Marketplace → Code with me

Новая плашка появится рядом с иконками Run/Debug. Можно отправить ссылку коллеге и вместе работать над задачей👯

#idea
По статистике 64% приложений сидят на java 8, а самая популярная фича java 8 - это Stream API.

Самая запутанная часть Stream API - коллекторы, о них я написала лонгрид в 3 частях.

Часть 1: обзор и простые коллекторы
Часть 2: сложные коллекторы
Часть 3: особенности дизайна

Рассмотрим почти все, что есть в JDK. Без внимания останутся collectingAndThen и teeing(Java 12). Не нашла ни одного кейса, где они полезны🤷
Коллекторы Stream API, часть 1: простые методы

В первой части повторим основы - из чего состоит стрим и что такое коллектор. Каждый код со Stream API состоит из 3х частей:
1️⃣ Получение стрима
2️⃣ Преобразования
3️⃣ Терминальная операция

1️⃣ list.stream()
2️⃣ .filter(e -> e != 3)
3️⃣ .count();

Терминальная операция collect собирает элементы стрима в другую структуру данных. Все подробности передаются через аргумент:
collect(Collector collector)

Коллекторы - статические методы класса Collectors, которые возвращают аргумент для метода collect. Я буду опускать основной класс и вместо Collectors.counting() будут писать counting(). Чтобы было короче.

Чаще всего элементы стрима собирают в обычную коллекцию:

▪️toCollection, toList, toSet
▪️toUnmodifiableSet, toUnmodifiableList

Set res=students.stream()
.filter(…).collect(toSet())

Ещё одна группа - коллекторы, которые возвращают одно значение:

▫️counting
▫️averagingToInt / Long / Double
▫️joining
▫️maxBy, minBy
▫️reducing
▫️summingInt / Long / Double
▫️summarizingInt / Long / Double

Интересны здесь только два метода:
🔸 joining
Соединяет элементы в одну строку:

chars.stream().collect(joining("-"));
// ['a','b','c'] → a-b-c

🔸summarizingInt
Возвращает объект IntSummaryStatistics, который содержит минимум, максимум, среднее, количество элементов и их сумму.

Остальные методы сами по себе бесполезны, так как есть простые аналоги:

list.stream().collect(counting())
// аналог
list.stream().count()

Коллекторы mapping, flatMapping и filtering применяют функцию к элементам перед отправкой в другой коллектор.

Set<Long> ids = …
collect(mapping(Student::id, toSet())

Использовать их напрямую тоже смысла нет. Проще применить к элементам map, flatmap или filter, а потом собрать результаты:

map(Student::id).collect(toSet())

Зачем нужны эти методы?

Они нужны в коллекторах groupingBy и partitioningBy, про них подробно поговорим завтра.

#core
👍51
Вы поручили джуниору задачу, в которой нужно написать пару строк на Stream API. Джуниор неопытный, но старательный, и принёс 5 вариантов решения. Все, кроме одного, приводят к одному результату. Какой код ошибочный?
Результат какого кода отличается от других?
Anonymous Poll
23%
1
27%
2
28%
3
7%
4
16%
5
Коллекторы Stream API, часть 2: сложные коллекторы

В этой части разберёмся в коллекторах toMap, groupingBy и partitioningBy. Уверена, года через 3 их будут спрашивать на занудных собеседованиях.

Будем тренироваться на классе Student из вопроса перед постом. У него есть id, имя, город и список оценок.

🔸toMap, toConcurrentMap
Цель понятна: уложить элементы стрима в map. Указываем функцию для ключа и для значения:
collect(toMap(Student::id, Student::name))

Если ключи повторяются, то вылетит IllegalStateException. Чтобы этого избежать, укажите третьим параметром, как объединять значения:

collect(toMap(Student::city, 
Student::name,
(a,b) -> a+" "+b))

["Москва":"Антон Аня Эдуард"]
Похоже на группировку по городу, но это не она. В результате получается одно значение, а не группа.

🔸groupingBy
Настоящую группировку делает коллектор groupingBy:

Map<String, List<Student>>=…
collect(groupingBy (Student::city))

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

groupingBy(Student::city, toSet())

Здесь нам наконец пригодятся коллекторы из предыдущего поста, которые подставим во второй аргумент.
▫️Города и количество студентов:
groupingBy(Student::city, counting())

▫️Города и имена студентов:
groupingBy(Student::city, 
mapping(Student::name, toSet()))

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

▫️ID студента и средняя оценка за экзамены:
...
Для этой задачи группировка не подходит. У одного студента только одна средняя оценка, количество итоговых элементов = количеству исходных. Группировать нечего, поэтому используем toMap:

toMap(Student::id,
s -> s.marks().stream().расчёт())

Вместо расчёт() - километр кода. Подсчёт среднего недоступен в интерфейсе Stream, а удобных методов перевода List<Integer> в IntStream нет.

🔸partitioningBy
Метод делит элементы на две группы по заданному условию. Результат - map с двумя ключами - true и false.
▫️Делим студентов на москвичей и жителей других городов:
Map<Boolean,Set<Student>>…
partitioningBy(s-> s.city().equals("Moscow"), toSet())

Вместо toSet можно подставить другой коллектор. Посчитаем количество студентов:

partitioningBy(s-> s.city().equals("Moscow"), counting())

Не очень понятно, зачем нужен partitioningBy. groupingBy даёт такой же результат, но на 2 символа короче:

groupingBy(s-> s.city().equals("Moscow"), counting())

Ответ на вопрос перед постом

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

#core
👍3
Коллекторы Stream API, часть 3: дизайн

Вчера мы разбирали groupingBy и partitioningBy. У многих возникла мысль, что лучше держаться от группировок подальше. Коллекторы выглядят неважно относительно других методов Stream API:

▪️Многословные: collect(Collectors.toSet())
▪️Вложенные коллекторы
▪️Дублированные методы map, flatMap, max, min - 18 штук

Итог: плохая читаемость и желание написать всё через цикл for.

Почему так получилось? Разберёмся в этом посте.

Выделим три вопроса:
1️⃣ Почему вместо toSet() такой сложный collect(Collectors.toSet())?
2️⃣ Почему группировка - терминальная операция?
3️⃣ Зачем в классе 18 неполноценных методов и вложенные коллекторы?

Разберём по порядку.
1️⃣ Метод collect и класс Collectors.
Цель Stream API - удобная работа с данными. За интерфейсом Stream может быть любой источник данных: список, строка, коллекция или файл. Сторонние библиотеки могут реализовать свой источник данных и работать с ним стандартными средствами Stream API.

В обратную сторону это тоже работает. Пользователь может преобразовать стрим в свою структуру данных, для этого в интерфейсе Stream метод collect(Collector).

А ещё источник данных и конечная структура данных - разные сущности, поэтому в интерфейсе Stream нет методов toSet(), toList(). Single Responsibility.

2️⃣ Почему группировка - терминальная операция?

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

Дизайн Stream API для этого не подходит. Источник данных делится на части, элементы обрабатываются независимо, иногда в разных потоках.

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

Группировка работает со всеми элементами, и в терминах Stream API это терминальная операция, дальше работать со стримом нельзя.

3️⃣ Вложенные коллекторы и 18 методов для groupingBy

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

Что может понадобиться программисту?
🔸Указать итоговый тип данных.
🔸Преобразовать итоговые данные. Это сложно сделать напрямую, потому что универсального интерфейса для filter, map и average нет.

Для решения этих задач groupingBy получил аргумент-коллектор, а Collectors пополнился 18 методами-обёртками над map, flatMap, filter и average .

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

💅 - Удобные методы для целевых кейсов, но с ограничениями
💪 - Максимум возможностей и кастомизации

#core
👍21
Сегодня начнётся онлайн конференция JLove❤️

Бесплатная!

Расписание уже готово, не забудьте подставить свой часовой пояс.

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

Зарегистрироваться: jlove.konfy.care
Паттерн проектирования, который в соответствии с принципом единственной обязанности передает другому объекту ответственность построения требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму
Anonymous Poll
40%
Dependency injection
9%
Dependency invertion
36%
Inversion of Control
16%
Factory Method
👍2
DI vs DI vs IoC

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

Сегодня разберём разницу между Dependency injection, Dependency invertion и Inversion of Control. Понимание пригодится на собеседованиях, при чтении статей по дизайну и архитектуре. Плюс поймёте, как хорошо вы программируете и какие проблемы решаете, даже не задумываясь.

Будем разбираться на простом примере.

Точка А: Сервис Service записывает логи в файл с помощью класса FileLogger:

class FileLogger {…}
class Service {
FileLogger logger=new FileLogger();
}

Сделаем код чуть лучше:

1️⃣ Dependency injection
это когда компоненты создаются не внутри класса, а передаются в конструкторах или сеттерах. Перенесём инициализацию логгера в конструктор:

class Service {
FileLogger logger;
Service (FileLogger logger) {
this.logger= logger;
}
}
Класс не занимается инициализацией логгера

2️⃣ Dependency invertion
Буква D в аббревиатуре SOLID, формулировка состоит из двух частей:

▫️Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
▫️Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.

Суть: пусть сервис работает не с конкретным логгером, а с интерфейсом

interface Logger {…}
class FileLogger implements Logger {…}
class Service {
Logger logger=new FileLogger();
}

В интерфейсе доступно меньше методов, поэтому его проще использовать
Реализацию легко заменить
Оба класса проще тестировать

Почему используется слово "абстракция"? Группу методов можно выделить в интерфейс, в абстрактный класс и даже в обычный класс. Но интерфейс самый подходящий вариант.

3️⃣ IoC - Inversion of Control
В маленьких программах жизнь начинается в методе main(). Программист создаёт объекты, вызывает методы, все шаги явно прописаны.

Inversion of Control - это когда ход выполнения программы задаёт фреймворк. Spring смотрит на классы и аннотации, а затем создаёт объекты, связывает их вместе и не даёт программе завершиться.

@Component class FileLogger {…}
@Component class Service {
@Autowired
FileLogger logger;
}

Меньше скучного кода
Низкая связность - код легко менять, тестировать и переиспользовать

Spring создаёт обёртки классов и работает через Dependency Injection. Можно и по-другому: через паттерн фабричный метод, стратегия или сервис локатор.

⚔️Историческая справка.
Сервис локатор иногда встречается в легаси проектах. Это когда компоненты создаются в классе ServiceLocator, а другие классы получают к ним доступ через статические методы.

class ServiceLocator {
private static Logger logger=…
public static Logger getLogger() {
return logger;
}
}

public class Service {
private Logger logger = ServiceLocator.getLogger();
}

Резюме:
🔸Dependency injection - класс не создаёт компоненты напрямую, они передаются через конструктор или сеттер
🔸Dependency invertion - класс работает с другими компонентами через интерфейс
🔸Inversion of Control - ход программы задаёт фреймворк. Соединять компоненты может Dependency injection, фабричный метод, стратегия или сервис локатор.

❗️Ответ на вопрос перед постом:
Это словоблудие относится к Dependency injection

#теория
👍4
IDEA: 4 метода для рефакторинга

IDEA - очень продвинутая IDE . Методов рефакторинга так много, что у них даже отдельная вкладка в меню. На каждом проекте точно пригодится:

1️⃣ Переименовать класс, метод, переменную или файл

Правой кнопкой по имени → Refactor → Rename
Имя изменится везде, где упоминается сущность.

2️⃣ Выделить константу

"Магические числа" в коде - плохая практика, лучше читаются именованные константы.

Правой кнопкой по числу → Refactor → Introduce Constant

 for(int i=0;i<100;i++)
for(int i=0;i<HLIMIT;i++)

3️⃣ Перенести код в отдельный метод

Выделяем нужные строки правый щелчок мышки Refactor → Extract Method...

И наоборот
4️⃣ Убрать лишние методы и переменные, "уплотнить" код

Правый щёлк → Refactor → Inline Method/Inline Variable
Switch: успеть до 30-ти

Сегодняшняя тема - история успеха оператора switch. Он появился в java 1.0, и с тех пор оставался в неизменном виде. В 2018 разработчики JDK смахнули пыль с кодовой базы switch, и теперь над ним идёт активная работа.

Почему о нём вспомнили, и как меняется switch? Рассмотрим по порядку.

1️⃣ Часть 1. Эпоха ООП

Java работает с объектами уже 25 лет. В этих условиях switch редко встречается в коде и часто считается плохой практикой.

Почему? Всё дело в сценарии использования:
switch (user.getState()) {
case NEW: …
case CONFIRMED: …
case BANNED: … }

Switch - это не просто набор нескольких if. У объекта user 3 статуса. Список чётко определен, статусы не пересекаются между собой.

Почему switch так себе вариант?
Сложный код. Работа со всеми состояниями в одной куче.
Дублирование кода. Если поле проверяется несколько раз, то менять такой код неудобно и легко ошибиться.

Для объекта с понятным набором состояний switch лучше заменить на полиморфные методы. Это несложный рефакторинг - пример1, пример2.

Цель ООП - смоделировать реальный мир через объекты. Главное здесь - объекты взаимодействуют и меняют состояние друг друга. В таких условиях switch проигрывает полиморфным методам и редко используется.

2️⃣ Часть 2. Эра функциональности

Сегодня для бизнеса недостаточно простой автоматизации. Основной задачей становится работа с данными.

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

Поэтому внедряются подходы из фунциональных языков. Один из них - pattern matching, а switch идеально подходит для его реализации. Паттерн - некоторое условие для переменной:
▫️Равна заданной константе
▫️Имеет определённый тип
▫️Подходит под регулярное выражение

Если произошёл мэтч, то для переменной сразу доступна доп.информация. Например, она приводится к нужному типу:

switch (animal) {
case Cat c → c.putToBox();
case Dog d → d.train(); }

Итак, в чём разница между switch в 2000 и switch в 2021?

Switch 2000 работает с объектами, у которых меняется состояние. Вокруг этого строится бизнес-логика.

Switch 2021 работает с неизменными данными и помогает найти среди них подходящие. В следующем году выйдет java 17, и switch будет появляться в коде чаще.

Вот так один непопулярный оператор в JDK нашёл своё место в мире спустя 23 года⭐️
👍2
Спасибо за этот год!

Завтра новый год, это отличный повод сказать нечто важное.

Ребята, вы супер! Спасибо, что читаете мои нудные посты без картинок, помогаете найти ошибки и задаёте интересные вопросы. Благодаря вам блог ещё жив❤️

В 2020 году на канале вышло 85 постов, которые в сумме набрали 475к просмотров! Я в шоке и постараюсь в 2021 не сбавлять обороты.

Желаю всем в следующем году +1 грейд, интересные проекты и яркую жизнь вне работы🔥
Гороскоп на 2021

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

Вот что говорят звёзды про 2021 год:

♈️ Овен
Металлический Бык симпатизирует Овнам, поэтому все инициативы будут удачны, особенно зимой, весной, летом и осенью. Будьте активны на ретро, предлагайте новые фичи и подходы, возьмите под наставничество стажёров. В сентябре ожидайте наплыв писем от HR.

♉️ Телец
В год Быка Тельцы нацелены на быстрый карьерный взлёт. Подтяните пробелы и обсудите с тимлидом возможности роста. В этом году звёзды раскрутили ваш потенциал до максимума. В августе будьте осторожнее с git push --force.

♊️ Близнецы
Год будет спокойным и приятным. В прошлом году вы много работали, в 2021 выделяйте больше времени на отдых. Качество жизни и работы только улучшится. Самое время взяться за большие и фундаментальные книги, которые вы долго откладывали.

♋️ Рак
Откажитесь от лишней эмоциональности, она может помешать вашему развитию. Подтяните DevOps, в этом году он вам пригодится. Сложные задачи ждут вас в середине лета, но они дадут нужный стимул для дальнейшего роста.

♌️ Лев
В этом году удача не на вашей стороне, придётся много работать. Хотите успеха — начните сейчас, чтобы уже весной видеть первые результаты. Смотрите на вещи шире. Почитайте книжки по архитектуре, посмотрите видео с конференций HighLoad и ArchDays. В апреле высокий риск простудиться, одевайтесь теплее.

♍️ Дева
Год будет богат на свежие идеи и начинания. Начните то, что давно откладывали. Интересные идеи, вопросы и решения придут в самый неожиданный момент. Сохраняйте их сразу - запишите в блокнот, голосовое сообщение, как угодно, а то улетят. Обязательно делайте бэкапы и резервные копии.

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

♏️ Скорпион
В этом году вы будете на переднем фронте. Вас ждут горячие фиксы и спасение команды перед дедлайном. Будет сложно, но Сатурн вам поможет. Помните об отдыхе и набирайтесь сил в спокойное время.

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

♑️ Козерог
Для вас 2021 год — это борьба со своими слабостями. Уделяйте больше внимания тестированию и самопроверке. Разберитесь с NoSQL: книга для начинающих, для продолжающих. Хорошей идеей будет сходить на каток и покататься на ватрушках.

♒️ Водолей
Лучшее время для решительных шагов — начало весны. Много возможностей принесёт нетворкинг - поддерживайте тёплые отношения с коллегами, участвуйте в конференциях, митапах и корпоративных мероприятиях. Идите в ногу со временем - освойте Kotlin и Cloud computing.

♓️ Рыбы
Наступает период, когда пора применить все накопленные знания. А возможности для этого обязательно будут. Меркурий помешает сделать важные задачи в срок, поэтому закладывайте на выполнение в 2 раза больше времени. Лето подкинет массу интересных вариантов для отдыха.

Дружите со звёздами, они плохого не посоветуют💫
👍2
У аннотации Test определены все возможные Target. В какой строке будет ошибка компиляции (если будет)?
В какой строке будет ошибка компиляции (если будет)?
Anonymous Poll
16%
1
17%
2
4%
3
11%
4
35%
5
18%
Всё отлично скомпилируется
Аннотации, часть 1: обзор

Аннотации - дополнительная информация к исходному коду. @Override, @Deprecated, @SuppressWarnings - вот это всё.

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

Создать аннотацию легко:
public @interface MyAnnotation {}

Почему ключевое слово @интерфейс, а не @аннотейшн?

Во времена java 4 аннотаций не было и для дополнительной информации классу добавляли интерфейс-маркер. В интерфейсах Cloneable, Serializable, Remote нет методов, они используются только как дополнительный признак класса.

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

Вернёмся в наши дни. Посмотрим исходный код @Deprecated:

@Retention(RUNTIME)
@Target(value={FIELD,…})
public @interface Deprecated {

String since() default "";
boolean forRemoval() default false;

}

На этом примере видно, из чего состоит аннотация:

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

@Deprecated(since="14") 
// forRemoval по умолчанию false

🔸Список внутри @Target показывает элементы, для которых работает аннотация.
В java 7 аннотации доступны для классов, методов, параметров, полей и переменных.

В java 8 аннотации действуют везде, где указан тип. Можно писать даже такое:

▫️new @Test Account()
▫️throws @Test IOException
▫️implements @Test Comparable<@Test
T>

Такие аннотации называются type annotations и используются в IDE и компиляторах для анализа и строгого контроля типов.

Аннотации нельзя ставить для имён переменных. Правильный ответ на вопрос перед постом - ошибка в 5 строке:
@Test String doubled
String @Test out

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

@Target и @Retention- это мета аннотации, то есть аннотации для аннотаций.

Какие ещё бывают мета-аннотации:

🔸@Documented - аннотация появится в JavaDoc
🔸@Inherited - наследуется подклассами
🔸@Repeatable (Java 8) - можно использовать несколько раз для одного элемента. Иногда такое приятнее читать, чем один массив:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")

Создать аннотацию легко, правильно применить - уже сложнее. С этим вопросом разберёмся во второй части.
1
Аннотации, часть 2: как использовать

Продолжим вчерашнюю тему. Рассмотрим Retention, и когда пригодится самодельная аннотация.

@Retention определяет, на каком этапе доступна аннотация:
🔹 SOURCE - аннотация видна только во время компиляции
🔹 CLASS - доступна также в байт-коде
🔹 RUNTIME - видна всегда, даже во время работы программы

Доступность выбирается исходя из цели. Поэтому перейдём к кейсам.

Что можно сделать через аннотации?

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

Пример: @SpringBootApplication - это комбинация @Configuration, @EnableAutoConfiguration и @ComponentScan.

2️⃣ Генерация кода и файлов.
Происходит на этапе компиляции:
🔸 Отмечаем код аннотацией
🔸 Создаём класс-наследник от AbstractProcessor. Определяем, на какие аннотации реагировать
🔸 Вытаскиваем дополнительную информацию через .class

Deprecated d = Account.class. getAnnotation(Deprecated.class)

🔸 Делаем что-то полезное
🔸 Включаем процессор в компиляцию. В maven-compiler-plugin это секция annotationProcessors.

Для обработки подойдут аннотации с любой RetentionPolicy.

Что получаем:
Долгая компиляция
Специфичное тестирование. Пример
😐 Сложный и запутанный код
Не тратится время на старте приложений

Этот подход используется в библиотеке Lombok, микрофреймворках Quarkus и Micronaut, в Android фреймворке Dagger.
Библиотека Dekorate на основе аннотаций создаёт манифесты для Kubernetes и OpenShift.

3️⃣ Статический анализ.
Алгоритм такой же - создать наследник от AbstractProcessor, добавить в процесс компиляции.

Примеры: библиотека Google Error Prone ищет в коде ошибки, Hibernate Validator проверяет, что аннотации Hibernate корректно расставлены.

4️⃣ Работа с байт-кодом.
Редкий случай. В байт-коде остаются аннотации с RetentionPolicy.CLASS или RUNTIME.

5️⃣ Создать объекты и прокси-классы.
Доступно для аннотаций с RetentionPolicy.RUNTIME.

Основа работы многих фреймворков: Spring, Hibernate, Java EE. Под работу с аннотациями в рантайме заточены многие библиотеки: Reflections, Spring. Работать с ними удобно и приятно.

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

Теперь рассмотрим анти-кейсы, для чего аннотации НЕ нужны:

1️⃣ Отметить код для себя или команды.
Поставить аннотацию @RefactorASAP легко, но без дальнейших действий это бесполезно. Аннотации нужно обрабатывать, и делать это автоматически.

Чтобы запомнить место в коде, используйте TODO комментарии в Intellij IDEA.

2️⃣ Для бизнес-логики.
Аннотации легко добавить в обход ООП и основной логики. Так можно быстро решить проблему, но долгосрочно это неудачный вариант:

Нельзя контролировать процесс целиком
Сложно писать тесты
Сложно дебажить
Внезапные сайд-эффекты

Частая ситуация на Spring проектах: на старте запускаются десятки @PostConstruct. Если в процессе возникает ошибка, то найти и исправить её непросто.

Но вообще, чем меньше вы полагаетесь на аннотации, тем лучше.
👍31
Stream API: новые методы в Java 16

16 марта вышла java 16. Новые фичи входят во вторую превью стадию перед главным релизом 2021 - java 17 LTS.

Существующие классы тоже развиваются. В java 16 в Stream API появилось 4 новых метода, которые мы и рассмотрим в этом посте.

1️⃣ toList()

С 25 по 27 ноября была серия постов о коллекторах (часть 1, часть 2, часть 3). Там я писала, что вместо

collect(Collectors.toList())
было бы удобно писать просто
toList()

30 ноября разработчик Oracle добавил метод toList() в класс Stream. Вряд ли он читает этот канал, но совпадение интересное🙂

Новый метод не совсем равнозначный:
▫️Collectors.toList() возвращает экземпляр ArrayList.
▫️Новый метод toList() возвращает неизменяемый список.

2️⃣ mapMulti

Это оптизированный flatMap. Объясню суть на примере. Заказ - класс Order, товар - класс Item. Заказ состоит из нескольких товаров. Из списка заказов хотим получить список всех товаров.

orders.stream()
.flatMap(order->order.getItems().stream())
.toList();

flatMap переводит "список списков" в один список. Товары для каждого заказа превращаются в Stream, а метод flatMap объединяет эти стримы в один.

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

mapMulti устраняет этот недостаток:

orders.stream()
.<Item>mapMulti((order, consumer) ->
order.getItems().forEach(item -> consumer.accept(item))
).toList();

Что происходит:
mapMulti принимает на вход (order, consumer):
▪️order - элемент стрима, в нашем случае - заказ
▪️consumer - следующий этап в стриме. Наша задача - передать этому этапу все будущие элементы. Берём у заказа товары и для каждого вызываем consumer.accept(item).

Мультимэп не знает, для каких объектов будет вызван accept, и не может вывести тип выходных элементов. Поэтому для нормальной работы тип надо указать явно:

<Item>mapMulti(…)

Когда использовать mapMulti?

Небольшое количество элементов в списках.
Например, много заказов с 1-2 товарами

Элементы легко получить без Stream API
Основная фишка mapMulti - нет промежуточных стримов. Если внутри метода создаётся стрим, то вся выгода сходит на нет.
order.getItems().stream()…

Есть три вариации метода:
🔸IntStream mapMultiToInt
🔸LongStream mapMultiToLong
🔸DoubleStream mapMultiToDouble

Для них выходной тип не указывается.
1
Intellij IDEA: как выучить шорткаты

В IDEA сотни горячих клавиш. С ними удобно работать, не надо тащить курсор через 2 монитора и бродить по контекстному меню. Есть только одна проблема - шорткаты сложно запомнить сразу.

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

1️⃣ Выучить топ-15

Статистика использования IDE находится в Help → Productivity Guide. Сортируем по колонке Used, получаем часто используемые команды. В описании указаны шорткаты. Запоминаем горячие клавиши для 10-15 действий, и продуктивность заметно растёт.

2️⃣ Плагин Key Promoter X

Установка: File → Settings → Plugins → Key Promoter X.

При действиях с мышкой в углу всплывает подсказка-шорткат.

Здесь работает правило 80/20: шорткаты для навигации, поиска и рефакторинга покрывают большинство ежедневных задач. Их легко выучить с помощью этих двух инструментов.
Intellij IDEA: Database View

Продолжая тему с БД, расскажу как можно работать с базой через Intellij IDEA.

Окошко открывается так: View → Tool Windows → Database

Подключить базу просто, самые необходимые функции есть:
▫️ Информация о таблицах, столбцах, индексах и т.д
▫️ Выполнить запрос
▫️ Выгрузить данные или метадату

Какие базы доступны:
Реляционные: Oracle, PostgreSQL, MySQL
Многие NoSQL: Mongo, Cassandra, Hive, ClickHouse, Vertica
Экзотичные: Exasol, Greenplum и Snowflake
Redis, Couchbase, HBase, CouchDB, Neo4j

Полный список баз на сегодня: