Skip List
Сегодня расскажу о структуре данных Skip List. Хочу сделать упор не на деталях реализации, а на том, зачем он нужен. И почему в java Skip List есть только в многопоточном варианте.
Кто вообще использует связные списки?
Давным-давно мы сравнивали
В большинстве энтерпрайзных задач
✅ В инфраструктурных задачах вставка в середину более актуальна.
✅ Многие структуры используют связный список косвенно: элементы ссылаются друг на друга для упрощения обхода или дополнительной сортировки.
Взять ту же
Самая проблемная операция
Как устроен Skip List
Чтобы искать элементы быстрее, добавляем для исходного списка несколько уровней со ссылками.
Пример — на картинке под постом. Чтобы найти пятёрку, движемся от верхнего уровня к нижнему.
В реальности на верхних уровнях промежутки больше, и мы быстрее приходим к нужному месту. В идеальном случае за O(log N).
❓ Это разве не дерево теперь?
Действительно, алгоритм поиска очень похож. Но чем Skip List отличается от дерева:
🔸 Балансировка
В деревьях очень строгие правила, при добавлении-удалении дерево часто перестраивается. Зато мы получаем высокую и стабильную скорость поиска.
В Skip List более расслабленный подход. Новый элемент добавляется в основной список, а вопрос с наличием ссылки на верхних уровнях решается через рандом.
В целом структура получается нормально сбалансированной. Одни элементы будут находиться быстрее, другие медленнее, в среднем время поиска стремится к O(log N).
🔸 Расположение элементов
В Skip List все элементы хранятся на нижнем уровне. Добавление-удаление происходит очень легко — надо переписать всего пару ссылок.
В дереве элементы находятся в узлах со строгой структурой. Вставка и удаление часто приводят к ребалансировке.
Резюме
✍️ Связные списки редко используются в энтерпрайзе, но часто в инфраструктуре (кэши, БД). Основной сценарий здесь — поиск. И сам по себе, и для работы с текущими значениями.
✍️ Дерево не всегда подойдёт, тк часто балансируется. В нагруженных системах лишняя суета ни к чему😑
✍️ Чтобы ускорить поиск в связном списке, добавляем дополнительные уровни. Получаем Skip List!
Сортированные структуры в JDK
В однопоточной среде для хранения сортированных элементов чаще используют
Поддерживать дерево в многопоточной среде сложно как раз из-за балансировки. Чтобы дерево безопасно сбалансировалось, лучше заблокировать его целиком, что снижает общую пропускную способность.
В многопоточной среде для той же задачи используется
Ответ на вопрос перед постом
Сегодня расскажу о структуре данных Skip List. Хочу сделать упор не на деталях реализации, а на том, зачем он нужен. И почему в java Skip List есть только в многопоточном варианте.
Кто вообще использует связные списки?
Давным-давно мы сравнивали
ArrayList
(список на основе массива) и LinkedList
(связный список). В большинстве энтерпрайзных задач
LinkedList
проиграл. Вставка в середину редко встречается в бизнес-логике, в основном мы работаем со списком как с хранилищем данных. Но✅ В инфраструктурных задачах вставка в середину более актуальна.
SortedSet
в Redis — сортированный список уникальных элементов. Элементы часто добавляются и удаляются из произвольных мест, логично взять за основу именно LinkedList
. В очереди с приоритетами и secondary indexes тоже пригодится связный список.✅ Многие структуры используют связный список косвенно: элементы ссылаются друг на друга для упрощения обхода или дополнительной сортировки.
Взять ту же
LinkedHashMap
из JDK — хэшмэп, в котором элементы связаны между собой в порядке добавления.Самая проблемная операция
LinkedList
— поиск элемента. Даже в сортированном списке приходится ходить по ссылкам последовательно. Это долго.Как устроен Skip List
Чтобы искать элементы быстрее, добавляем для исходного списка несколько уровней со ссылками.
Пример — на картинке под постом. Чтобы найти пятёрку, движемся от верхнего уровня к нижнему.
В реальности на верхних уровнях промежутки больше, и мы быстрее приходим к нужному месту. В идеальном случае за O(log N).
❓ Это разве не дерево теперь?
Действительно, алгоритм поиска очень похож. Но чем Skip List отличается от дерева:
🔸 Балансировка
В деревьях очень строгие правила, при добавлении-удалении дерево часто перестраивается. Зато мы получаем высокую и стабильную скорость поиска.
В Skip List более расслабленный подход. Новый элемент добавляется в основной список, а вопрос с наличием ссылки на верхних уровнях решается через рандом.
В целом структура получается нормально сбалансированной. Одни элементы будут находиться быстрее, другие медленнее, в среднем время поиска стремится к O(log N).
🔸 Расположение элементов
В Skip List все элементы хранятся на нижнем уровне. Добавление-удаление происходит очень легко — надо переписать всего пару ссылок.
В дереве элементы находятся в узлах со строгой структурой. Вставка и удаление часто приводят к ребалансировке.
Резюме
✍️ Связные списки редко используются в энтерпрайзе, но часто в инфраструктуре (кэши, БД). Основной сценарий здесь — поиск. И сам по себе, и для работы с текущими значениями.
✍️ Дерево не всегда подойдёт, тк часто балансируется. В нагруженных системах лишняя суета ни к чему😑
✍️ Чтобы ускорить поиск в связном списке, добавляем дополнительные уровни. Получаем Skip List!
Сортированные структуры в JDK
В однопоточной среде для хранения сортированных элементов чаще используют
TreeMap
/Set
. Это красно-чёрное дерево. Балансируется при каждом изменении, поиск работает быстро и стабильно.Поддерживать дерево в многопоточной среде сложно как раз из-за балансировки. Чтобы дерево безопасно сбалансировалось, лучше заблокировать его целиком, что снижает общую пропускную способность.
В многопоточной среде для той же задачи используется
ConcurrentSkipListMap
/Set
. Изменения очень локальные: меняется несколько ссылок, а большая часть списка остаётся неизменной. Поэтому одновременно со структурой могут работать больше потоков.Ответ на вопрос перед постом
ConcurrentSkipListSet
— сортированный список без повторов. Skip List — название базовой структуры данных, Set — признак уникальности элементов.Разделение строк и велосипеды
Простой способ разделить строку на части — встроенный метод
Исходный код split выглядит как-то так:
Разделитель — один символ
Сразу видим fast path для разделителей из одного символа. Алгоритм в этом случае не использует регулярку и выполняется быстрее. Но проверка, что строка-разделитель является символом, занимает 10(!) строк.
🚲 Напишем свой split — изменим тип входных данных на char, чтобы проверку делал компилятор:
Разделитель — несколько символов
Также в глаза бросается работа с регуляркой, если разделитель состоит из нескольких символов. Компиляция регулярного выражения выполняется долго, для набора строк кажется разумным выполнить её один раз:
Насколько это поможет — проверим в бенчмарке.
С другой стороны, кажется, что регулярка — это слишком серьёзное решение. Если разделитель — двоеточие с пробелом, то мощь регулярного выражения здесь не нужна.
🚲 Поэтому напишем второй велосипед, который очень похож на первый. Будем искать в исходной строке подстроку-разделитель. По полученным индексам делить строку на части.
Оба велосипеда припаркованы тут: 🚲🚲
Оценим готовые решения
Библиотека Apache Commons предлагает метод split, но работает он чуть по-другому. Метод ищет только первый разделитель и делит строку максимум на две части:
Результаты
на картинке внизу. Результаты на разных железках могут отличаться.
🔸 Оба велосипеда в пух и прах разбили стандартный split.
К слову, это не первый велосипед, который выигрывает у JDK. Такое уже случалось при сравнение строк. Надо бы завести тикет по этим кейсам:)
🔸 Если строка делится только на две части — подойдёт split из Apache Commons
❗️Для нормальной нагрузки и однократного вызова подойдёт стандартный
Не могу не отметить, что хотя подобные задачи появляются редко, они приносят море удовольствия. Разобрать код в деталях, найти пути улучшения, и в итоге метод выполняется в 2 раза быстрее, красота😊
Простой способ разделить строку на части — встроенный метод
split
:"123-456-789".split("-") → [123, 456, 789]Сегодня разберём, насколько оптимально работает метод, и как написать код быстрее, чем JDK.
Исходный код split выглядит как-то так:
public String[] split(String regex) {Разберём обе ветки этого кода
if (разделитель — один символ)
пройтись по всем символам. Встречаем разделитель - извлекаем подстроку
} else {
Pattern.compile(regex).split(this)
}
Разделитель — один символ
Сразу видим fast path для разделителей из одного символа. Алгоритм в этом случае не использует регулярку и выполняется быстрее. Но проверка, что строка-разделитель является символом, занимает 10(!) строк.
🚲 Напишем свой split — изменим тип входных данных на char, чтобы проверку делал компилятор:
split(String regex) → split(char delim)Остальной код остаётся тем же. Мы только убрали лишние условия. Потом проверим, стало ли лучше:)
Разделитель — несколько символов
Также в глаза бросается работа с регуляркой, если разделитель состоит из нескольких символов. Компиляция регулярного выражения выполняется долго, для набора строк кажется разумным выполнить её один раз:
List<String> list = ……
❌ list.stream().map(s -> s.split(": "))…
✅ Pattern p = Pattern.compile(": ");
list.stream().map(s -> splitPattern.split(s))
Насколько это поможет — проверим в бенчмарке.
С другой стороны, кажется, что регулярка — это слишком серьёзное решение. Если разделитель — двоеточие с пробелом, то мощь регулярного выражения здесь не нужна.
🚲 Поэтому напишем второй велосипед, который очень похож на первый. Будем искать в исходной строке подстроку-разделитель. По полученным индексам делить строку на части.
Оба велосипеда припаркованы тут: 🚲🚲
Оценим готовые решения
Библиотека Apache Commons предлагает метод split, но работает он чуть по-другому. Метод ищет только первый разделитель и делит строку максимум на две части:
StringUtils.split("1-2-3", "-");Ставим минус за неспортивное поведение, но включаем метод в наши эксперименты.
// получим 2 строки: "1" и "2-3"
Результаты
на картинке внизу. Результаты на разных железках могут отличаться.
🔸 Оба велосипеда в пух и прах разбили стандартный split.
String#split
слишком универсальный, и как любое универсальное решение, проигрывает кастомизированному. Если деление строк — ваш hot spot, не стесняйтесь написать свой метод. К слову, это не первый велосипед, который выигрывает у JDK. Такое уже случалось при сравнение строк. Надо бы завести тикет по этим кейсам:)
🔸 Если строка делится только на две части — подойдёт split из Apache Commons
❗️Для нормальной нагрузки и однократного вызова подойдёт стандартный
String#split
. Оптимизации нужны, когда деление строк происходит очень часто. Не могу не отметить, что хотя подобные задачи появляются редко, они приносят море удовольствия. Разобрать код в деталях, найти пути улучшения, и в итоге метод выполняется в 2 раза быстрее, красота😊
IDEA: live templates
Хейтеры говорят, что java многословная. Похоже, они пишут код в блокноте, потому что IDEA помогает писать код со скоростью мысли🚀
Есть две полезные фичи: live templates и code completion.
1️⃣ Live templates
Это по сути аббревиатуры для кода. Вводите 4 символа, нажимаете Enter, и они разворачиваются в 40!
🔸 Простые
▫️ St → String
▫️ sout
System.out.println();
▫️ main
Разворачиваются в методы с параметрами для автозаполнения. Перемещаться между полями можно через Tab:
▫️ fori
Есть для Java, Kotlin, JS, Groovy, для разработки под Android и React.
2️⃣ Code completion
Это дополнение имен на основе контекста.
🔸 Начните набирать начало класса/метода:
▫️ Int → Integer
▫️ Cust → Customer
🔸 Для классов наберите заглавные буквы:
▫️ NPE → NullPointerException
▫️ CHM → ConcurrentHashMap
🔸 Добавьте синтаксическую конструкцию:
▫️ count == 4.if
Хейтеры говорят, что java многословная. Похоже, они пишут код в блокноте, потому что IDEA помогает писать код со скоростью мысли🚀
Есть две полезные фичи: live templates и code completion.
1️⃣ Live templates
Это по сути аббревиатуры для кода. Вводите 4 символа, нажимаете Enter, и они разворачиваются в 40!
🔸 Простые
▫️ St → String
▫️ sout
System.out.println();
▫️ main
public static void main(String[] args) {}▫️ prsf
private static final🔸 Сложные
Разворачиваются в методы с параметрами для автозаполнения. Перемещаться между полями можно через Tab:
▫️ fori
for (int i=0; i< ; i++) {}▫️ ifn
if (args == null) {}▫️ mx
Math.max(, );▫️ lazy
if (obj == null)Полный список live templates: File → Settings→ Editor → Live Templates.
{ obj = new Integer(); }
Есть для Java, Kotlin, JS, Groovy, для разработки под Android и React.
2️⃣ Code completion
Это дополнение имен на основе контекста.
🔸 Начните набирать начало класса/метода:
▫️ Int → Integer
▫️ Cust → Customer
🔸 Для классов наберите заглавные буквы:
▫️ NPE → NullPointerException
▫️ CHM → ConcurrentHashMap
🔸 Добавьте синтаксическую конструкцию:
▫️ count == 4.if
if (count == 4) {}▫️ list.for
for(Integer i : list) {}▫️ obj.opt
Optional.of(obj)▫️ answer.switch
switch (answer) {}Полный список: File → Settings → Editor → General → Postfix Completion. Есть варианты для Java, Kotlin и JS.
12 factor app: кодовая база
12 factor app — чеклист из 12 пунктов для адаптации сервиса к облаку.
Список появился в 2017 году, и был модным года до 20. Упоминание 12 факторов на собеседовании производило вау-эффект и приводило интервьюеров в восторг:)
Большинство идей до сих пор актуальны. Но
🤔 Оригинальный текст очень формальный, иногда это просто набор терминов и процессов. Не написано, в чём смысл, и какая проблема решается. Когда цель непонятна, работа превращается в карго культ или бесполезные движения
🤔 Не всегда понятны конкретные шаги. Иногда это "делайте хорошо, плохо не делайте"
🤔 Некоторые рекомендации в оригинальном тексте слишком жёсткие или устарели
Так что потихоньку пройдусь по всем пунктам, объясню суть и дам более актуальные рекомендации, чем в оригинале.
Первый пункт посвящён работе с кодовой базой и звучит так: one codebase tracked in revision control, many deploys
Приложение полностью отслеживается системой контроля версий.
Экземпляр запущенного приложения называется deploy. Неважно, где он запущен — в проде, локально у разработчика или в тестовой среде. Хотя в жизни чаще используются другие термины (билд, артефакт или просто сервис), дальше буду использовать оригинальный термин.
🔸 У каждого сервиса своя кодовая база в одном репозитории.
🔸 У разных приложений разные кодовые базы. Общий код выделяется в библиотеку/модуль и импортируется через менеджер зависимостей.
🔸 У каждого приложения одна кодовая база, но на разных средах могут использоваться разные версии. Например, локально у разработчика запускается ветка с фичей, тестировщик работает с мастером, на проде разворачивается релизная ветка.
В чем смысл ограничений: если продакшн использует одну кодовую базу, а разработка и тестирование — немного другую, однажды появится ошибка, которая воспроизведётся только на продакшене.
На практике вполне нормально, что для разных сред код работает по-разному. Обычно изменения касаются внешних компонентов.
Яркий пример — платёжная система. Работать с настоящими деньгами — непозволительная роскошь, поэтому для разработки и тестирования используются либо заглушки, либо специальные песочницы.
Как проверить приложение:
✅ В коде нет условий вроде
✅ Общие модули импортируются через менеджер зависимостей — Maven или Gradle
✅ Финальный этап тестирования происходит на версии, которая потом отправится в продакшн
12 factor app — чеклист из 12 пунктов для адаптации сервиса к облаку.
Список появился в 2017 году, и был модным года до 20. Упоминание 12 факторов на собеседовании производило вау-эффект и приводило интервьюеров в восторг:)
Большинство идей до сих пор актуальны. Но
🤔 Оригинальный текст очень формальный, иногда это просто набор терминов и процессов. Не написано, в чём смысл, и какая проблема решается. Когда цель непонятна, работа превращается в карго культ или бесполезные движения
🤔 Не всегда понятны конкретные шаги. Иногда это "делайте хорошо, плохо не делайте"
🤔 Некоторые рекомендации в оригинальном тексте слишком жёсткие или устарели
Так что потихоньку пройдусь по всем пунктам, объясню суть и дам более актуальные рекомендации, чем в оригинале.
Первый пункт посвящён работе с кодовой базой и звучит так: one codebase tracked in revision control, many deploys
Приложение полностью отслеживается системой контроля версий.
Экземпляр запущенного приложения называется deploy. Неважно, где он запущен — в проде, локально у разработчика или в тестовой среде. Хотя в жизни чаще используются другие термины (билд, артефакт или просто сервис), дальше буду использовать оригинальный термин.
🔸 У каждого сервиса своя кодовая база в одном репозитории.
🔸 У разных приложений разные кодовые базы. Общий код выделяется в библиотеку/модуль и импортируется через менеджер зависимостей.
🔸 У каждого приложения одна кодовая база, но на разных средах могут использоваться разные версии. Например, локально у разработчика запускается ветка с фичей, тестировщик работает с мастером, на проде разворачивается релизная ветка.
В чем смысл ограничений: если продакшн использует одну кодовую базу, а разработка и тестирование — немного другую, однажды появится ошибка, которая воспроизведётся только на продакшене.
На практике вполне нормально, что для разных сред код работает по-разному. Обычно изменения касаются внешних компонентов.
Яркий пример — платёжная система. Работать с настоящими деньгами — непозволительная роскошь, поэтому для разработки и тестирования используются либо заглушки, либо специальные песочницы.
Как проверить приложение:
✅ В коде нет условий вроде
if (env == dev)
. Переключение между заглушками и настоящими системами происходит через конфиг✅ Общие модули импортируются через менеджер зависимостей — Maven или Gradle
✅ Финальный этап тестирования происходит на версии, которая потом отправится в продакшн
12 factor app: зависимости
Продолжаем разбирать признаки здорового сервиса. Второй пункт рекомендует
Для java приложений нужные модули и библиотеки описываются в pom.xml или build.gradle файле. Тогда приложение будет собираться на любой среде, где есть среда исполнения и установленный менеджер зависимостей. Не будет проблем с локальной разработкой и настройкой CI.
Dependency isolation часто пропускают при обсуждении 12 факторов, потому что непонятно, что это такое:) Оригинальный текст и правда звучит туманно: ☁️no implicit dependencies “leak in” from the surrounding system☁️
Вообще это означает, что версия каждой зависимости должна быть чётко определена.
Альтернатива — опция latest для Docker образов и gradle зависимостей.
Почему это не ок? Допустим, приложение использует библиотеку Х версии 1 во время разработки и всех стадий тестирования. Затем у библиотеки Х выходит версия 2, которая использует другое API. Вторая версия подтягивается во время релиза, и продакшн погружается во мрак🌑
Таких сюрпризов не будет, если обозначить версию явно
🔹 в pom/build файле проекта
🔹 в BOM файле
🔹 в родительском pom/build
В большинстве компаний используются специальные инструменты для работы с зависимостями — Artifactory или Nexus. Они нужны, чтобы
🔸 хранить приватные артефакты — корпоративные модули или библиотеки
🔸 кэшировать библиотеки, чтобы экономить трафик
🔸 использовать проверенные службой безопасности артефакты
При использовании Artifactory/Nexus конфликты версий случаются редко. Но есть и обратная сторона: апгрейд библиотеки превращается в увлекательное бюрократическое приключение:)
Как проверить приложение:
✅ Для сборки проекта достаточно команды maven/gradle
✅ Для всех сред используется один pom/build файл
✅ Если в компании используется Artifactory или Nexus, не добавляйте явно другие репозитории
12 factor app — классный документ, он объединяет важные моменты, которые часто остаются в стороне. Работа с зависимостями, кодовой базой и остальные 10 пунктов — это БАЗА. Обычно она описана в недрах Confluence или передаётся из уст в уста:)
Объединить все важные знания в один документ — прекрасная идея, а для нас — отличный способ закрыть возможные пробелы🧡
Продолжаем разбирать признаки здорового сервиса. Второй пункт рекомендует
Explicitly declare and isolate dependenciesРазберём его подробнее.
Для java приложений нужные модули и библиотеки описываются в pom.xml или build.gradle файле. Тогда приложение будет собираться на любой среде, где есть среда исполнения и установленный менеджер зависимостей. Не будет проблем с локальной разработкой и настройкой CI.
Dependency isolation часто пропускают при обсуждении 12 факторов, потому что непонятно, что это такое:) Оригинальный текст и правда звучит туманно: ☁️no implicit dependencies “leak in” from the surrounding system☁️
Вообще это означает, что версия каждой зависимости должна быть чётко определена.
Альтернатива — опция latest для Docker образов и gradle зависимостей.
Почему это не ок? Допустим, приложение использует библиотеку Х версии 1 во время разработки и всех стадий тестирования. Затем у библиотеки Х выходит версия 2, которая использует другое API. Вторая версия подтягивается во время релиза, и продакшн погружается во мрак🌑
Таких сюрпризов не будет, если обозначить версию явно
🔹 в pom/build файле проекта
🔹 в BOM файле
🔹 в родительском pom/build
В большинстве компаний используются специальные инструменты для работы с зависимостями — Artifactory или Nexus. Они нужны, чтобы
🔸 хранить приватные артефакты — корпоративные модули или библиотеки
🔸 кэшировать библиотеки, чтобы экономить трафик
🔸 использовать проверенные службой безопасности артефакты
При использовании Artifactory/Nexus конфликты версий случаются редко. Но есть и обратная сторона: апгрейд библиотеки превращается в увлекательное бюрократическое приключение:)
Как проверить приложение:
✅ Для сборки проекта достаточно команды maven/gradle
✅ Для всех сред используется один pom/build файл
✅ Если в компании используется Artifactory или Nexus, не добавляйте явно другие репозитории
12 factor app — классный документ, он объединяет важные моменты, которые часто остаются в стороне. Работа с зависимостями, кодовой базой и остальные 10 пунктов — это БАЗА. Обычно она описана в недрах Confluence или передаётся из уст в уста:)
Объединить все важные знания в один документ — прекрасная идея, а для нас — отличный способ закрыть возможные пробелы🧡
Пользовались уже ChatGPT для работы? Как впечатления?
Anonymous Poll
17%
Использую часто, нравится
27%
Попробовал пару раз, было полезно
17%
Использовал, но не впечатлился
39%
Нет ещё
ChatGPT внутри IDEA
Общаться с нейросетью через текстовое окошко — популярная форма работы с ChatGPT, но не единственная.
Я попробовала 3 варианта интеграции нейросетей с IDEA:
▫️ AI Assistant от Jetbrains
▫️ Самый популярный плагин на основе ChatGPT — EasyCode
▫️ Помощника от Amazon — CodeWhisperer
В этом посте поделюсь впечатлениями!
🤖 AI Assistant от JetBrains
Полноценный assistant пока не вышел в релиз и доступен только в билде 2023.2 EAP 6 (скачивается отдельно). Альтернатива — подключить плагин AI Assistant, работает только для IDEA Ultimate.
Основные фичи:
✅ Пообщаться с нейросетью
в отдельной вкладке, не выходя из IDE. Сейчас это прокси к ChatGPT, в будущем появится больше моделей
✅ Узнать, что делает выделенный код, возможные проблемы и варианты рефакторинга
Здесь пока нет вау-эффекта. Объяснение получается слишком длинным, рефакторинг и поиск проблем работают только для простых случаев. Но уверена, что эти фичи будут развиваться
🔥 Написать документацию
Пока моя любимая фича. Набираете перед методом
✅ Написать сообщение для коммита
При коммите появляется кнопка ✨ (без шуток, так и выглядит). ИИ описывает изменения в стиле: "в классе А добавился метод B, в классе C изменилась реализация метода D". Очень многословно, пока not recommend
🤖 Плагин ChatGPT - EasyCode
Самый популярный плагин по работе с ChatGPT. Лучший вариант, если у вас IDEA Community, и хочется попробовать ИИ прямо сейчас. Основные фичи такие же, как в AI Assistant:
✅ Окошко для общения
✅ Получить объяснение, что делает выделенный код
✅ Узнать варианты рефакторинга
Но есть кое-что, чего в у JetBrains пока нет: опция Write Unit Tests🔥
Тесты ужасно примитивные, но идея чудесная! Возлагаю на этот функционал большие надежды
🤖 CodeWhisperer от Amazon
Не самый известный вариант, но самый интригующий. Амазон пишет, что натренировал модель на огромном количестве кода, и контрольная группа увеличила productivity на десятки процентов.
Установить CodeWhisperer чуть сложнее, чем предыдущие варианты: поставить плагин AWS Toolkit, зарегистрироваться и привязать учётку к IDEA.
Название CodeWhisperer очень точно отображает поведение. В предыдущих плагинах надо явно спрашивать совет у ИИ, а здесь помощник шепчет рекомендации, даже если не просишь. Причём они появляются не в отдельном месте, а сразу в коде призрачным шрифтом.
Меня это бесит, но формат real-time рекомендаций выглядит круто. В менее навязчивой форме будет вообще отлично💛
Другие фичи:
✅ Поиск OWASP уязвимостей в коде + идеи по исправлению
✅ Посмотреть похожий код в open-source проекте
По этим функциям ничего сказать не могу. В моём проекте не оказалось уязвимостей и не нашлось кода, похожего на open-source проекты. Но звучит интересно.
Что не понравилось:
😒 Нет окошка "просто спросить". Можно попросить написать код в самом классе, но без диалога и дальнейших уточнений
😒 Нет кнопок с базовыми действиями вроде "сгенерировать документацию". Все запросы надо писать целиком и самостоятельно
😒 Неудобный интерфейс и мутная документация
Хотелось удалить помощник через 5 минут после использования. Непонятно, какие ключевые слова использовать, что за странные кнопки со стрелками, неудобно смотреть предложенные варианты. В документации никаких примеров.
CodeWhisperer выглядит как сырой продукт, но очень аутентичный.
Общее впечатление
Интеграция ИИ в IDE делает первые робкие шаги. Мне понравилась генерация документации, за остальным пока буду наблюдать со стороны:)
Для ваших задач выводы могут отличаться. Попробуйте сами, все инструменты в посте — бесплатные, а установка не занимает много времени🔥
Общаться с нейросетью через текстовое окошко — популярная форма работы с ChatGPT, но не единственная.
Я попробовала 3 варианта интеграции нейросетей с IDEA:
▫️ AI Assistant от Jetbrains
▫️ Самый популярный плагин на основе ChatGPT — EasyCode
▫️ Помощника от Amazon — CodeWhisperer
В этом посте поделюсь впечатлениями!
🤖 AI Assistant от JetBrains
Полноценный assistant пока не вышел в релиз и доступен только в билде 2023.2 EAP 6 (скачивается отдельно). Альтернатива — подключить плагин AI Assistant, работает только для IDEA Ultimate.
Основные фичи:
✅ Пообщаться с нейросетью
в отдельной вкладке, не выходя из IDE. Сейчас это прокси к ChatGPT, в будущем появится больше моделей
✅ Узнать, что делает выделенный код, возможные проблемы и варианты рефакторинга
Здесь пока нет вау-эффекта. Объяснение получается слишком длинным, рефакторинг и поиск проблем работают только для простых случаев. Но уверена, что эти фичи будут развиваться
🔥 Написать документацию
Пока моя любимая фича. Набираете перед методом
/**
, появляется кнопка Suggest documentation. Классно заполняется краткое описание и смысл входных-выходных параметров. Требует немного правок, но здорово экономит время!✅ Написать сообщение для коммита
При коммите появляется кнопка ✨ (без шуток, так и выглядит). ИИ описывает изменения в стиле: "в классе А добавился метод B, в классе C изменилась реализация метода D". Очень многословно, пока not recommend
🤖 Плагин ChatGPT - EasyCode
Самый популярный плагин по работе с ChatGPT. Лучший вариант, если у вас IDEA Community, и хочется попробовать ИИ прямо сейчас. Основные фичи такие же, как в AI Assistant:
✅ Окошко для общения
✅ Получить объяснение, что делает выделенный код
✅ Узнать варианты рефакторинга
Но есть кое-что, чего в у JetBrains пока нет: опция Write Unit Tests🔥
Тесты ужасно примитивные, но идея чудесная! Возлагаю на этот функционал большие надежды
🤖 CodeWhisperer от Amazon
Не самый известный вариант, но самый интригующий. Амазон пишет, что натренировал модель на огромном количестве кода, и контрольная группа увеличила productivity на десятки процентов.
Установить CodeWhisperer чуть сложнее, чем предыдущие варианты: поставить плагин AWS Toolkit, зарегистрироваться и привязать учётку к IDEA.
Название CodeWhisperer очень точно отображает поведение. В предыдущих плагинах надо явно спрашивать совет у ИИ, а здесь помощник шепчет рекомендации, даже если не просишь. Причём они появляются не в отдельном месте, а сразу в коде призрачным шрифтом.
Меня это бесит, но формат real-time рекомендаций выглядит круто. В менее навязчивой форме будет вообще отлично💛
Другие фичи:
✅ Поиск OWASP уязвимостей в коде + идеи по исправлению
✅ Посмотреть похожий код в open-source проекте
По этим функциям ничего сказать не могу. В моём проекте не оказалось уязвимостей и не нашлось кода, похожего на open-source проекты. Но звучит интересно.
Что не понравилось:
😒 Нет окошка "просто спросить". Можно попросить написать код в самом классе, но без диалога и дальнейших уточнений
😒 Нет кнопок с базовыми действиями вроде "сгенерировать документацию". Все запросы надо писать целиком и самостоятельно
😒 Неудобный интерфейс и мутная документация
Хотелось удалить помощник через 5 минут после использования. Непонятно, какие ключевые слова использовать, что за странные кнопки со стрелками, неудобно смотреть предложенные варианты. В документации никаких примеров.
CodeWhisperer выглядит как сырой продукт, но очень аутентичный.
Общее впечатление
Интеграция ИИ в IDE делает первые робкие шаги. Мне понравилась генерация документации, за остальным пока буду наблюдать со стороны:)
Для ваших задач выводы могут отличаться. Попробуйте сами, все инструменты в посте — бесплатные, а установка не занимает много времени🔥
12 factor app: конфиг
Где хранить конфиги? На каком этапе передать их приложению?
Эти вопросы обсуждаются в третьем пункте 12 factor app, который звучит как
Store config in the environment
Разберём, что это значит:)
Конфигурация — это всё, чем отличаются запущенные приложения на разных средах. Например,
✅ Адреса, логины, пароли, токены внешних сервисов
✅ Специфичные значения для конкретного сервиса: имя хоста, порт
✅ Управляющие параметры: имя профайла, флажки и прочие свойства
Не являются конфигом:
❌ Классы спринга с аннотацией @Configuration. Это описание компонентов и связей, часть исходного кода
❌ Описания и состав профайлов. В спринге это аннотация @Profile. Причина та же: профайлы описаны в кодовой базе, которая не меняется. Но вот параметр, который задаёт, какой именно профайл нужен — это конфиг
Не конфиг:
Где хранить конфиги?
Оригинальный документ категоричен: конфиг должен передаваться через environment переменные, а конфиг-файлы не должны существовать. Потому что:
🔸 Рано или поздно сервис с продакшн конфигами попадёт в лапки разработчиков, и они сделают delete table users
🔸 Файлы с конфигами расползаются по всей системе, это небезопасно и сложно в управлении
🔸 Environment переменные не зависят от ОС, фреймворка и языка разработки
Мотивация понятна, но на практике всё работает не так:) Конфиги группируются в файлы, а в гите часто хранится дефолтный файлик для разработки.
Конфиги могут лежать в другом сервисе, например, в Kubernetes, Zookeeper или даже в HashiCorp Vault. Последний поддерживает версионирование и следит, кто и когда запрашивал данные.
Для ежедневной разработки есть следующие best practices:
✅ В параметрах конфига нет префикса среды выполнения, а в коде — логики по их разделению:
Кажется, что это само собой разумеется, но нет:) В 2020 году утекли личные данные 243 миллионов бразильцев, потому что пароль БД был записан константой в исходном коде сервиса минздрава. А согласно этому исследованию более 100к GitHub репозиториев содержат токены и криптографические ключи прямо в исходном коде. Будем надеяться, что это всё pet проекты, которые нигде не используются🤞
Где хранить конфиги? На каком этапе передать их приложению?
Эти вопросы обсуждаются в третьем пункте 12 factor app, который звучит как
Store config in the environment
Разберём, что это значит:)
Конфигурация — это всё, чем отличаются запущенные приложения на разных средах. Например,
✅ Адреса, логины, пароли, токены внешних сервисов
✅ Специфичные значения для конкретного сервиса: имя хоста, порт
✅ Управляющие параметры: имя профайла, флажки и прочие свойства
Не являются конфигом:
❌ Классы спринга с аннотацией @Configuration. Это описание компонентов и связей, часть исходного кода
❌ Описания и состав профайлов. В спринге это аннотация @Profile. Причина та же: профайлы описаны в кодовой базе, которая не меняется. Но вот параметр, который задаёт, какой именно профайл нужен — это конфиг
Не конфиг:
@ConfigurationА вот это конфиг:
@Profile(”dev”)
public class DBConfiguration {…}
-Dspring.profiles.active=devЗадача разработчика простая: отделить конфигурацию от основного кода. Тогда приложение легко запустить и настроить на работу в разных средах.
Где хранить конфиги?
Оригинальный документ категоричен: конфиг должен передаваться через environment переменные, а конфиг-файлы не должны существовать. Потому что:
🔸 Рано или поздно сервис с продакшн конфигами попадёт в лапки разработчиков, и они сделают delete table users
🔸 Файлы с конфигами расползаются по всей системе, это небезопасно и сложно в управлении
🔸 Environment переменные не зависят от ОС, фреймворка и языка разработки
Мотивация понятна, но на практике всё работает не так:) Конфиги группируются в файлы, а в гите часто хранится дефолтный файлик для разработки.
Конфиги могут лежать в другом сервисе, например, в Kubernetes, Zookeeper или даже в HashiCorp Vault. Последний поддерживает версионирование и следит, кто и когда запрашивал данные.
Для ежедневной разработки есть следующие best practices:
✅ В параметрах конфига нет префикса среды выполнения, а в коде — логики по их разделению:
❌ @Value("dev.datasource")✅ В коде нет констант вроде "8080", "admin", имён хостов, логинов и паролей
✔️ @Value("datasource")
Кажется, что это само собой разумеется, но нет:) В 2020 году утекли личные данные 243 миллионов бразильцев, потому что пароль БД был записан константой в исходном коде сервиса минздрава. А согласно этому исследованию более 100к GitHub репозиториев содержат токены и криптографические ключи прямо в исходном коде. Будем надеяться, что это всё pet проекты, которые нигде не используются🤞
Java 21: sequenced collections
или "фича, которая опоздала на 25 лет".
19 сентября выходит java 21, и среди прочего там будет JEP 431: Sequenced Collections.
В чём суть изменений?
В JDK добавится новый интерфейс
♨️ Пример 1: получить последний элемент в списке
Сейчас это так:
Сейчас это так:
В java 21 всё гораздо проще:
♨️ Пример 3: обойти LinkedHashSet
LinkedHashSet — список с уникальными элементами. Хотя это список, класс реализует только интерфейс Set. Поэтому работы с индексами нет вообще.
Получить первый элемент ещё можно:
В java 21 те же операции выполняются легко и просто:
В java 21 появится интерфейс SequencedCollection с методами
▫️
Новые методы появятся в
Было бы здорово увидеть эти методы 25 лет назад, но лучше поздно, чем никогда:)
или "фича, которая опоздала на 25 лет".
19 сентября выходит java 21, и среди прочего там будет JEP 431: Sequenced Collections.
В чём суть изменений?
В JDK добавится новый интерфейс
SequencedCollection
. В него войдут методы, которые должны были появиться в джаве ещё в 98 году. Простые операции, для которых каждый раз пишется маленький велосипедик🚲♨️ Пример 1: получить последний элемент в списке
Сейчас это так:
last = list.get(list.size() - 1);
В java 21 наконец-то появится специальный метод: last = list.getLast();♨️ Пример 2: пройти список в обратном порядке
Сейчас это так:
for(int i=list.size(); i>0; i--){Выглядит жутко. Альтернатива — использовать Collections.reverse:
int value = list.get(i));
}
List<Integer> reversed = new ArrayList<>(list);Выглядит симпатичнее, но здесь море лишних действий: создаём новую(!) коллекцию, переставляем её элементы и только потом делаем обход.
Collections.reverse(reversed);
reversed.forEach(…);
В java 21 всё гораздо проще:
list.reversed().forEach(…)Метод
reversed
не меняет исходную коллекцию и возвращает view с обратным порядком обхода.♨️ Пример 3: обойти LinkedHashSet
LinkedHashSet — список с уникальными элементами. Хотя это список, класс реализует только интерфейс Set. Поэтому работы с индексами нет вообще.
Получить первый элемент ещё можно:
first = linkedHashSet.iterator().next();А вот последний — никак, надо полностью обходить структуру. Код писать не буду, слишком громоздкий.
В java 21 те же операции выполняются легко и просто:
first = linkedHashSet.getFirst();Резюме
last = linkedHashSet.getLast();
В java 21 появится интерфейс SequencedCollection с методами
▫️
SequencedCollection<E> reversed()
▫️ void addFirst(E)
▫️ void addLast(E)
▫️ E getFirst()
▫️ E getLast()
▫️ E removeFirst()
▫️ E removeLast()
Плюс интерфейсы SequencedSet
и SequencedMap
с тем же функционалом.Новые методы появятся в
ArrayList
, LinkedList
, HashSet
, LinkedHashMap
, LinkedHashSet
, частично в TreeSet
и некоторых других классах.Было бы здорово увидеть эти методы 25 лет назад, но лучше поздно, чем никогда:)
Как я провалила курс по спрингу
В декабре прошлого года я проводила адвент-календарь по java. 21 день почти тысяча человек разбирали нюансы java core. В конце я спросила: какую тему ещё разобрать, где есть непонятки? Ответ был почти единогласным — Spring.
И я поставила цель на 2023 — сделать что-нибудь полезное по спрингу. Не для начинающих, с практическими фишками и лучшими практиками.
За 8 месяцев так ничего и не сделала😞
Я не склонна к самобичеванию, и попробовала разобраться, почему так получилось. Ответ лежал на поверхности.
Все силы идут на курс по многопоточке. Чуть появляется свободное время — бегу его улучшать. Хотя давно можно остановиться. Отзывы прекрасные, доходимость высокая. Хороший баланс между пользой и нагрузкой. Аналогов до сих пор нет ни в СНГ, ни в англоязычном мире.
И кажется, это отличный момент пойти дальше.
Следующий поток будет в октябре, и это будет последний раз в текущем виде. Дальше я пройдусь по бэклогу, сделаю рефакторинг, и курс останется только в варианте без обратной связи.
А со следующего года снова займусь образовательным творчеством. Руки чешутся взяться за спринг и другие темы!
Всё это похоже на чувства от принятого оффера. Когда на старом месте всё получается, и коллеги лапочки, но есть чувство, что можешь больше. Новый проект обещает что-то интересное, и ты с надеждой устремляешься в путь.
Так что кто хотел на курс с обратной связью — откладывать больше нельзя. Через пару недель объявлю набор, и в начале октября начнём🚀
В декабре прошлого года я проводила адвент-календарь по java. 21 день почти тысяча человек разбирали нюансы java core. В конце я спросила: какую тему ещё разобрать, где есть непонятки? Ответ был почти единогласным — Spring.
И я поставила цель на 2023 — сделать что-нибудь полезное по спрингу. Не для начинающих, с практическими фишками и лучшими практиками.
За 8 месяцев так ничего и не сделала😞
Я не склонна к самобичеванию, и попробовала разобраться, почему так получилось. Ответ лежал на поверхности.
Все силы идут на курс по многопоточке. Чуть появляется свободное время — бегу его улучшать. Хотя давно можно остановиться. Отзывы прекрасные, доходимость высокая. Хороший баланс между пользой и нагрузкой. Аналогов до сих пор нет ни в СНГ, ни в англоязычном мире.
И кажется, это отличный момент пойти дальше.
Следующий поток будет в октябре, и это будет последний раз в текущем виде. Дальше я пройдусь по бэклогу, сделаю рефакторинг, и курс останется только в варианте без обратной связи.
А со следующего года снова займусь образовательным творчеством. Руки чешутся взяться за спринг и другие темы!
Всё это похоже на чувства от принятого оффера. Когда на старом месте всё получается, и коллеги лапочки, но есть чувство, что можешь больше. Новый проект обещает что-то интересное, и ты с надеждой устремляешься в путь.
Так что кто хотел на курс с обратной связью — откладывать больше нельзя. Через пару недель объявлю набор, и в начале октября начнём🚀
Pattern matching: зачем?
Последние годы в джаву активно добавляется группа фич на тему pattern matching.
Тагир Валеев из Intellij IDEA в этом докладе рассказывает, какие это сложные фичи, как много челленджей стояло перед командой разработки. Но не говорит, зачем он вообще нужен.
Архитектор java Brian Goetz пишет, что pattern matching — не просто синтаксический сахар, а движение в сторону data oriented programming. Но это слабо применимо к большинству корпоративных систем, где доминирует ООП и изредка встречается функциональное программирование.
Большая часть моих знакомых считает паттерн матчинг "стрелкой вместо двоеточия в свиче":)
Так что сегодня расскажу, для каких задач пригодится pattern matching. В следующем посте обсудим реализацию в java.
Формат входных данных в энтерпрайзе обычно чётко определён. Если входящее сообщение не подходит по формату — это ошибка со стороны клиента.
Но бывает, что система работает со множеством источников данных, и выделить общий формат очень сложно.
Например, вы парсите чужие сайты/документы/дампы и достаёте оттуда что-то полезное. Знаю один проект, где бизнес-данные вытаскивают из логов(!) другой системы🤯
В итоге входные данные выглядят как
Паттерн — схема того, что мы ищем. Паттерн для поиска координат может выглядеть так:
▫️
▫️
Дальше идём по набору данных и проверяем их на соответствие паттерну.
Традиционно для такой задачи используется связка
Вот что нужно написать, чтобы проверить, является ли координатами массив
Ещё пример:
С if-ами эта конструкция будет гораздо объёмнее
Резюме
✅ Pattern matching нужен, когда мы пытаемся найти что-то знакомое в слабо- или неструктурированных данных.
✅ Большинство
✅ Разумеется, применить pattern matching можно и в других сценариях, но здесь видится наибольший профит в корпоративном царстве ООП.
В следующем посте распишу возможности pattern matching конкретно в джаве.
Спойлер:пока не впечатляет😑
Последние годы в джаву активно добавляется группа фич на тему pattern matching.
Тагир Валеев из Intellij IDEA в этом докладе рассказывает, какие это сложные фичи, как много челленджей стояло перед командой разработки. Но не говорит, зачем он вообще нужен.
Архитектор java Brian Goetz пишет, что pattern matching — не просто синтаксический сахар, а движение в сторону data oriented programming. Но это слабо применимо к большинству корпоративных систем, где доминирует ООП и изредка встречается функциональное программирование.
Большая часть моих знакомых считает паттерн матчинг "стрелкой вместо двоеточия в свиче":)
Так что сегодня расскажу, для каких задач пригодится pattern matching. В следующем посте обсудим реализацию в java.
Формат входных данных в энтерпрайзе обычно чётко определён. Если входящее сообщение не подходит по формату — это ошибка со стороны клиента.
Но бывает, что система работает со множеством источников данных, и выделить общий формат очень сложно.
Например, вы парсите чужие сайты/документы/дампы и достаёте оттуда что-то полезное. Знаю один проект, где бизнес-данные вытаскивают из логов(!) другой системы🤯
В итоге входные данные выглядят как
[Object, Object, Object]
. Паттерн — схема того, что мы ищем. Паттерн для поиска координат может выглядеть так:
▫️
[Double, Double]
— ищём массив из двух чисел с плавающей запятой▫️
[(-90;90), (-180,180)]
— массив с двумя числами в указанных диапазонахДальше идём по набору данных и проверяем их на соответствие паттерну.
Традиционно для такой задачи используется связка
if + instanceOf
. Вариант рабочий, но читаемость ужасная. Вот что нужно написать, чтобы проверить, является ли координатами массив
[Object, Object] data
:if (data.length == 2 && data[0] instanceOf Double && data[1] instanceOf Double) {В pattern matching многие проверки убираются под капот, и код выглядит симпатичнее:
double n = (Double) data[0];
double e = (Double) data[1];
if (n ≥ -90 && n≤ 90 && e ≥ -180 && e ≤ 180) {
// что-то делаем
}
}
switch(data) {Близкий родственник паттерн матчинга — регулярные выражения. Строка — это набор символов, внутри этого набора ищутся паттерны. Можно сделать ту же работу через if, но регулярка удобнее. Плюс в строке элементы однородные (символы), а паттерн матчинг работает с разными типами данных.
case [(-90; 90), (-180, 180)]:
// что-то делаем
}
Ещё пример:
double discount = switch(transaction) {Здесь ищем список из 5 элементов, где первый — строка "vip", второй и пятый — число. Если нашли — можно сразу работать с полем
case ["vip", Long, _, _, Double sum] → sum*0,9
case _ → 0
}
sum
. Если не нашли — используем паттерн по умолчанию _
С if-ами эта конструкция будет гораздо объёмнее
Резюме
✅ Pattern matching нужен, когда мы пытаемся найти что-то знакомое в слабо- или неструктурированных данных.
✅ Большинство
instanceOf
и if
отправляются под капот, и мы получаем более компактный код. ✅ Разумеется, применить pattern matching можно и в других сценариях, но здесь видится наибольший профит в корпоративном царстве ООП.
В следующем посте распишу возможности pattern matching конкретно в джаве.
Спойлер:
Что будет напечатано в консоли? (используется Java 20)
Anonymous Poll
24%
Even Even
4%
Even Even Other
51%
Even Other Even
8%
Even Other Even Other
13%
Ошибка компиляции в одном из методов
Pattern matching: синтаксис
В прошлый раз мы обсудили pattern matching в вакууме. Сегодня обсудим, как он выглядит в java с учётом 21 версии, и чего не хватает в текущей реализации.
Под зонтик pattern matching относят много фич: sealed классы, records, обновление switch и instanceOf. Sealed и records в контексте pattern matching — специфичные кейсы data oriented programming, не будем о них. Cфокусируемся на более популярных сценариях:)
Итак, как pattern matching воплощается в синтаксисе:
1️⃣ Компактный instanceOf
Раньше для неизвестного типа выполнялись две операции: instanceOf и явное приведение типа:
В логическом И можно использовать переменную в том же if:
✅
❌
У древнейшей конструкции доступен новый синтаксис:
▫️ Стрелочка вместо двоеточия
▫️ Break в конце case по умолчанию, и его можно не писать
▫️ Можно объединить несколько case в один
Старый синтаксис никуда не делся, им можно пользоваться:
🔥 Ответ на вопрос перед постом: выведется
3️⃣ Присвоение элемента через switch
Раньше в case принимались только константы. Теперь можно проверить тип переменной:
❌ Нет работы с массивами
Один из кейсов pattern matching — работа с набором данных, в которых мы ищем определённые признаки. Такого в java пока нет:
❌ Нет паттернов по полям класса
Под капот спрятан только instanceOf, остальные условия выглядят как в обычном if. Вся работа с полями происходит через методы:
Резюме
В текущем виде pattern matching выглядит слабо, особенно по сравнению с другими языками. Покрыты только базовые кейсы, в таком виде область применения очень ограничена.
Возможно это лишь промежуточный этап. Посмотрим, как фича будет развиваться и использоваться✨
В прошлый раз мы обсудили pattern matching в вакууме. Сегодня обсудим, как он выглядит в java с учётом 21 версии, и чего не хватает в текущей реализации.
Под зонтик pattern matching относят много фич: sealed классы, records, обновление switch и instanceOf. Sealed и records в контексте pattern matching — специфичные кейсы data oriented programming, не будем о них. Cфокусируемся на более популярных сценариях:)
Итак, как pattern matching воплощается в синтаксисе:
1️⃣ Компактный instanceOf
Раньше для неизвестного типа выполнялись две операции: instanceOf и явное приведение типа:
if (obj instanceof String) {Теперь эти операции объединены. Рядом с instanceOf объявляем имя переменной и сразу ей пользуемся:
String str = (String) obj;
// используем str
}
if (obj instanceof String str) {Тонкий момент— переменная определяется только при успешном выполнении instanceOf, поэтому есть нюанс с областью видимости.
// используем str
}
В логическом И можно использовать переменную в том же if:
✅
if (obj instanceof String s && s.length() > 5)
Логическое ИЛИ такого не позволяет, будет ошибка компиляции:❌
if (obj instanceof String s || s.length() > 5)
2️⃣ Компактный switchУ древнейшей конструкции доступен новый синтаксис:
▫️ Стрелочка вместо двоеточия
▫️ Break в конце case по умолчанию, и его можно не писать
▫️ Можно объединить несколько case в один
Старый синтаксис никуда не делся, им можно пользоваться:
switch (value) {В "новом стиле" это будет так:
case 1:
case 3: println("Odd"); break;
case 2:
case 4: println("Even"); break;
default: println("Other");
}
switch (value) {Важный момент — break добавляется по умолчанию только в вариант "со стрелочками". В "двоеточиях" break всё ещё добавляется вручную.
case 1,3 -> println("Odd");
case 2,4 -> println("Even");
default -> println("Other")
}
🔥 Ответ на вопрос перед постом: выведется
Even Other Even
. Чтобы работало как надо, нужно переписать на стрелочки или добавить break.3️⃣ Присвоение элемента через switch
int value = switch(…) {…};4️⃣ Проверка в switch по типам и сложные case
Раньше в case принимались только константы. Теперь можно проверить тип переменной:
switch (obj) {Если произошёл мэтч, для переменной сразу доступна доп. информация, и можно добавить условия в case:
case Integer i -> …
case Long l -> …
case Double d -> …
}
switch (response) {А теперь самое интересное! Обсудим, чего в текущей реализации нет:
case String s when s.length() == 10 -> …
}
❌ Нет работы с массивами
Один из кейсов pattern matching — работа с набором данных, в которых мы ищем определённые признаки. Такого в java пока нет:
double discount = switch(transaction) {В других языках такой функционал есть. Например, List patterns в С# или Matching sequences в питоне.
case ["vip", Long, _, _, Double sum] → sum*0,9
case _ → 0
}
❌ Нет паттернов по полям класса
Под капот спрятан только instanceOf, остальные условия выглядят как в обычном if. Вся работа с полями происходит через методы:
switch (figure) {В том же питоне запись гораздо компактнее:
case Square s when s.getLength() != 10 → …
}
match point:JEP Record Patterns мог быть как раз об этом, ведь records позиционируются как лаконичные контейнеры данных. Но увы.
case (0, 0): …
case (0, y): …
case _: …
Резюме
В текущем виде pattern matching выглядит слабо, особенно по сравнению с другими языками. Покрыты только базовые кейсы, в таком виде область применения очень ограничена.
Возможно это лишь промежуточный этап. Посмотрим, как фича будет развиваться и использоваться✨
Анонс курса по многопоточке🥳
Старт: 2 октября
Длительность: 9 недель
Кто давно ждал и уже готов → https://fillthegaps.ru/mt
Теперь подробнее. У курса две основные задачи:
✅ Научиться писать хороший многопоточный код
Разберём типовые энтерпрайзные задачи, огромное количество кейсов, лучших практик и возможных ошибок. Сравним производительность разных решений для разных ситуаций
✅ Подготовимся к собеседованиям, где требуется concurrency. Обсудим стандартные и нестандартные вопросы, порешаем тестовые задания
Что говорят ученики:
👨🦱 "Курс понравился тем, что он "от разработчиков разработчикам": примеры реальных библиотек для разбора, приближенные к реальным задачи для кодинга"
👨🦱 "Курс очень интенсивный, охватывает не только многопоточку, но и смежные темы, учит разным лайфхакам полезным для практического использования, обращает внимание на темы, которые легко или упустить, изучая тему самостоятельно, или вообще можно никогда не узнать без курса"
👨🦱 "Есть очень много свежей информации, которую сконцентрировано в едином источнике не получить"
👨🦱 "Это не с нуля совсем курс, и больше про правду разработки, разбавленную вопросами с собесов, а не про чистые знания."
Океан отзывов можно почитать тут
Для какого уровня курс?
Middle и выше
Сколько стоит?
🔸 Без обратной связи: 32000 руб.
🔸 С обратной связью: мест нет
Разницу между тарифами можно почитать тут. Этот поток — последний с обратной связью.
✔️ Есть рассрочка на 3 и 6 месяцев
✔️ Принимаются карты любых банков
✔️ Курс можно оплатить за счёт компании
✔️ Налоговый вычет 13%
Аналогов у курса нет. С каждым потоком программа становится лучше, задания интереснее, а учёба приятнее. Если хотите разобраться с многопоточкой, и вам близок мой стиль изложения — записывайтесь, будет очень полезно!
https://fillthegaps.ru/mt
Старт: 2 октября
Длительность: 9 недель
Кто давно ждал и уже готов → https://fillthegaps.ru/mt
Теперь подробнее. У курса две основные задачи:
✅ Научиться писать хороший многопоточный код
Разберём типовые энтерпрайзные задачи, огромное количество кейсов, лучших практик и возможных ошибок. Сравним производительность разных решений для разных ситуаций
✅ Подготовимся к собеседованиям, где требуется concurrency. Обсудим стандартные и нестандартные вопросы, порешаем тестовые задания
Что говорят ученики:
👨🦱 "Курс понравился тем, что он "от разработчиков разработчикам": примеры реальных библиотек для разбора, приближенные к реальным задачи для кодинга"
👨🦱 "Курс очень интенсивный, охватывает не только многопоточку, но и смежные темы, учит разным лайфхакам полезным для практического использования, обращает внимание на темы, которые легко или упустить, изучая тему самостоятельно, или вообще можно никогда не узнать без курса"
👨🦱 "Есть очень много свежей информации, которую сконцентрировано в едином источнике не получить"
👨🦱 "Это не с нуля совсем курс, и больше про правду разработки, разбавленную вопросами с собесов, а не про чистые знания."
Океан отзывов можно почитать тут
Для какого уровня курс?
Middle и выше
Сколько стоит?
🔸 Без обратной связи: 32000 руб.
🔸 С обратной связью: мест нет
Разницу между тарифами можно почитать тут. Этот поток — последний с обратной связью.
✔️ Есть рассрочка на 3 и 6 месяцев
✔️ Принимаются карты любых банков
✔️ Курс можно оплатить за счёт компании
✔️ Налоговый вычет 13%
Аналогов у курса нет. С каждым потоком программа становится лучше, задания интереснее, а учёба приятнее. Если хотите разобраться с многопоточкой, и вам близок мой стиль изложения — записывайтесь, будет очень полезно!
https://fillthegaps.ru/mt
Любимые подписчики, поздравляю вас с днём разработчика!
Мы все разные, с разным опытом, интересами и бэкграундом:
👦🏻 Кто-то пришёл в профессию по зову сердца, кто-то ради денег
👩 Кто-то горит работой, а кто-то уже выгорел
🧔🏻♀️ Кто-то работает 10 лет и видел всё. Кто-то только делает первые шаги
Но каждый прошёл огромный путь. Изучил тонны информации, научился тысяче вещей. Гибкий ум, критическое мышление, способность быстро обучаться и разбираться в сложном — наши сильные стороны.
Cегодня отличный день, чтобы признать свои заслуги и сказать себе: "я умничка". Признать, что делаешь сложную работу, и хорошо с ней справляешься.
Ребята, вы прекрасны! Вы очень умные и талантливые, каждый из вас.
Не будьте критичны к себе, почаще отдыхайте. Не стесняйтесь просить большую зарплату. Берегите своё желание учиться и покорять новые высоты❤️
С праздником🥳
Мы все разные, с разным опытом, интересами и бэкграундом:
👦🏻 Кто-то пришёл в профессию по зову сердца, кто-то ради денег
👩 Кто-то горит работой, а кто-то уже выгорел
🧔🏻♀️ Кто-то работает 10 лет и видел всё. Кто-то только делает первые шаги
Но каждый прошёл огромный путь. Изучил тонны информации, научился тысяче вещей. Гибкий ум, критическое мышление, способность быстро обучаться и разбираться в сложном — наши сильные стороны.
Cегодня отличный день, чтобы признать свои заслуги и сказать себе: "я умничка". Признать, что делаешь сложную работу, и хорошо с ней справляешься.
Ребята, вы прекрасны! Вы очень умные и талантливые, каждый из вас.
Не будьте критичны к себе, почаще отдыхайте. Не стесняйтесь просить большую зарплату. Берегите своё желание учиться и покорять новые высоты❤️
С праздником🥳
Какая версия Java сейчас основная на вашем проекте?
Anonymous Poll
19%
8
1%
9 или 10
28%
11
2%
12-16
44%
17
6%
18+
Сегодня релиз Java 21🥳
Главные фичи новой версии:
🔸 Виртуальные потоки
В чём суть: для многих задач производительность ограничивается количеством потоков в ОС. Чтобы обойти это ограничение и использовать потоки на максимум, придумали множество способов — асинхронные библиотеки, реактивное программирование и тд. Вариант рабочий, но код усложняется.
Виртуальные потоки позволяют писать простой код и не упираться в ограничения ОС.
Ближайший аналог виртуальных потоков — горутины в Go. Собственно, горутины — основная причина, почему многие сервисы написаны на Go.
Более дальний аналог — корутины в Kotlin. Они имитируют виртуальные потоки на уровне библиотеки и несут большие накладные расходы. В java виртуальные потоки реализованы на уровне JVM, поэтому их производительность гораздо выше.
В теории виртуальные потоки принесут пользу большинству приложений. Посмотрим, что будет на практике.
🔸 Pattern matching
В этих постах (один, два) я подробно расписала, зачем нужен pattern matching в целом, и как он реализован в java. Вкратце: нужен не всем, реализация в джаве так себе
🔸 Sequenced collections
Новые методы в коллекциях:
Интересные preview фичи:
🔹 String templates — интерполяция строк, возможность писать
🔹 Scoped Values — аналог ThreadLocal c ограниченной областью действия
🔹 Structured Concurrency — способ организации подзадач в многопоточной среде
Остальные фичи очень специфичные и вряд ли пригодятся большинству разработчиков:
▫️ Generational ZGC — в сборщик добавили поколения, как следует из названия. Молодые объекты будут собираться чаще, и сборщик будет работать эффективнее
▫️ Record Patterns — records можно использовать внутри case
▫️ Foreign Function & Memory API (Third Preview) — методы для работы с нативным кодом и управлению памятью за пределами JVM. Это нужно для приложений, которые хотят сами управлять размещением объектов в памяти и не зависеть от сборщика мусора.
▫️ Unnamed Patterns and Variables (Preview) — можно не указывать имя переменной, если оно не нужно. Вместо него ставить
▫️ Deprecate the Windows 32-bit x86 Port for Removal — перестать работать с Windows 32-bit x86, в будущем удалить
▫️ Prepare to Disallow the Dynamic Loading of Agents
Агент — компонент, который изменяет классы при загрузке или меняет уже загруженные классы в JVM. Используется для мониторинга, профайлинга и других служебных целей
▫️ Key Encapsulation Mechanism API — методы для защиты ключей симметричного шифрования
▫️ Unnamed Classes and Instance Main Methods (Preview)
Видимо чтобы короче писать Hello World:
Главные фичи новой версии:
🔸 Виртуальные потоки
В чём суть: для многих задач производительность ограничивается количеством потоков в ОС. Чтобы обойти это ограничение и использовать потоки на максимум, придумали множество способов — асинхронные библиотеки, реактивное программирование и тд. Вариант рабочий, но код усложняется.
Виртуальные потоки позволяют писать простой код и не упираться в ограничения ОС.
Ближайший аналог виртуальных потоков — горутины в Go. Собственно, горутины — основная причина, почему многие сервисы написаны на Go.
Более дальний аналог — корутины в Kotlin. Они имитируют виртуальные потоки на уровне библиотеки и несут большие накладные расходы. В java виртуальные потоки реализованы на уровне JVM, поэтому их производительность гораздо выше.
В теории виртуальные потоки принесут пользу большинству приложений. Посмотрим, что будет на практике.
🔸 Pattern matching
В этих постах (один, два) я подробно расписала, зачем нужен pattern matching в целом, и как он реализован в java. Вкратце: нужен не всем, реализация в джаве так себе
🔸 Sequenced collections
Новые методы в коллекциях:
getFirst()
, getLast()
, reversed()
и другие. Подробный пост тутИнтересные preview фичи:
🔹 String templates — интерполяция строк, возможность писать
String str = "Hello, ${name}!";Подробнее в этом посте
🔹 Scoped Values — аналог ThreadLocal c ограниченной областью действия
🔹 Structured Concurrency — способ организации подзадач в многопоточной среде
Остальные фичи очень специфичные и вряд ли пригодятся большинству разработчиков:
▫️ Generational ZGC — в сборщик добавили поколения, как следует из названия. Молодые объекты будут собираться чаще, и сборщик будет работать эффективнее
▫️ Record Patterns — records можно использовать внутри case
▫️ Foreign Function & Memory API (Third Preview) — методы для работы с нативным кодом и управлению памятью за пределами JVM. Это нужно для приложений, которые хотят сами управлять размещением объектов в памяти и не зависеть от сборщика мусора.
▫️ Unnamed Patterns and Variables (Preview) — можно не указывать имя переменной, если оно не нужно. Вместо него ставить
_
Например, в case:case Point(int x, _) → …Или при ловле исключения:
catch (IllegalStateException _)Было бы удобно сделать такое для лямбд, но для этого есть отдельный JEP, который сделают чёрт знает когда
▫️ Deprecate the Windows 32-bit x86 Port for Removal — перестать работать с Windows 32-bit x86, в будущем удалить
▫️ Prepare to Disallow the Dynamic Loading of Agents
Агент — компонент, который изменяет классы при загрузке или меняет уже загруженные классы в JVM. Используется для мониторинга, профайлинга и других служебных целей
▫️ Key Encapsulation Mechanism API — методы для защиты ключей симметричного шифрования
▫️ Unnamed Classes and Instance Main Methods (Preview)
Видимо чтобы короче писать Hello World:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Вот такой релиз. Конечно, самая ожидаемая фича — это виртуальные потоки. Если планируете в ближайшее время внедрять их, отпишитесь по впечатлениям, мне очень интересно😊Scoped Value (preview)
Неделю назад вышла java 21. Cегодня разберу интересную фичу в стадии превью — Scoped Value.
Ближайший аналог Scoped Value — ThreadLocal. Это когда мы объявляем переменную
В бизнес-логике это редко нужно, но фреймворки активно пользуются этим классом. Spring Security использует ThreadLocal для хранения информации о текущем пользователе. Давайте на этом кейсе посмотрим недостатки ThreadLocal, и что предлагает ScopedValue.
Как работает секьюрити:
1️⃣ Когда приходит новый запрос, Spring вытаскивает информацию о пользователе и записывает в ThreadLocal переменную:
3️⃣ В конце работы с запросом удаляем информацию из ThreadLocal переменной
Что в итоге:
✅ Не надо передавать Principal в параметрах
❌ Надо явно очищать значение ThreadLocal переменной в конце работы
❌ В любом месте можно вызвать set/remove и всё сломать
❌ Подход несовместим с виртуальными потоками
Scoped Value намерен решить проблемы выше. Как это выглядит:
Кроме
Что получаем:
✅ Видимость переменной задаётся для конкретного вызова метода
✅ У ScopedValue нет метода set, переменную нельзя обнулить/поменять внутри блока
✅ Код совместим с виртуальными потоками
Что вызывает вопросы:
🤔 Сценарии использования
Неизменяемый аналог ThreadLocal, совместимый с Project Loom точно нужен, но не вижу смысла задавать область видимости настолько гранулярно
🤔 Нельзя использовать несколько ScopedValue без использования вложенности. Хотя это легко реализовать по аналогии с try-with-resources
⚒ Где использовать: пока вижу только как замену ThreadLocal при переходе на виртуальные потоки.
Фича сейчас в стадии превью, посмотрим, как она будет развиваться. Если будет, конечно:)
Неделю назад вышла java 21. Cегодня разберу интересную фичу в стадии превью — Scoped Value.
Ближайший аналог Scoped Value — ThreadLocal. Это когда мы объявляем переменную
ThreadLocal<Integer> value;и для каждого потока будет своё независимое значение value.
В бизнес-логике это редко нужно, но фреймворки активно пользуются этим классом. Spring Security использует ThreadLocal для хранения информации о текущем пользователе. Давайте на этом кейсе посмотрим недостатки ThreadLocal, и что предлагает ScopedValue.
Как работает секьюрити:
1️⃣ Когда приходит новый запрос, Spring вытаскивает информацию о пользователе и записывает в ThreadLocal переменную:
public static ThreadLocal<Principal> PRINCIPAL = …2️⃣ Бизнес-логика. В любом месте кода можно узнать, кто выполняет запрос:
void serve(Request request, Response response) {
…
var principal = ADMIN;
PRINCIPAL.set(principal);
…}
var principal = PRINCIPAL.get();Обычно каждый запрос обрабатывается в своём потоке, поэтому данные между запросами не пересекаются.
3️⃣ В конце работы с запросом удаляем информацию из ThreadLocal переменной
Что в итоге:
✅ Не надо передавать Principal в параметрах
❌ Надо явно очищать значение ThreadLocal переменной в конце работы
❌ В любом месте можно вызвать set/remove и всё сломать
❌ Подход несовместим с виртуальными потоками
Scoped Value намерен решить проблемы выше. Как это выглядит:
public static ScopedValue<Principal> PRINCIPAL = …Переменная
void serve(Request request, Response response) {
…
var principal = ADMIN;
ScopedValue.where(PRINCIPAL, principal)
.run(() -> process(request, response));
…}
PRINCIPAL
со значением principal будет доступна только внутри конкретного вызова метода process
. Достать значение внутри process
:var principal = PRINCIPAL.get()
;Кроме
run
есть метод call
, который возвращает значение из переданной функции:var result = ScopedValue.where(Server.PRINCIPAL, guest)Сначала кажется, что для java синтаксис Scoped Value очень необычный — как будто переменная главнее основного действия. Но такое в java уже есть, вспомните try-with-resources.
.call(() -> getResult());
Что получаем:
✅ Видимость переменной задаётся для конкретного вызова метода
✅ У ScopedValue нет метода set, переменную нельзя обнулить/поменять внутри блока
✅ Код совместим с виртуальными потоками
Что вызывает вопросы:
🤔 Сценарии использования
Неизменяемый аналог ThreadLocal, совместимый с Project Loom точно нужен, но не вижу смысла задавать область видимости настолько гранулярно
🤔 Нельзя использовать несколько ScopedValue без использования вложенности. Хотя это легко реализовать по аналогии с try-with-resources
⚒ Где использовать: пока вижу только как замену ThreadLocal при переходе на виртуальные потоки.
Фича сейчас в стадии превью, посмотрим, как она будет развиваться. Если будет, конечно:)
IDEA: как не потерять важные места в коде
В огромном проекте всегда есть места, которые хочется отметить или быстро находить.
Раньше это делали так:
🔴 Ставили неактивный брейкпойнт в нужном месте. В принципе нормально, но иногда сложно вспомнить, что где находится
⭐️ Добавляли файл в favorites. Файл добавляется целиком, что не очень удобно
Затем в IDEA убрали favorites и добавили закладки. Супер удобно, ни одна важная строчка теперь не потеряется.
▫️ Курсор на нужной строке → F11 → появляется закладка
▫️ Правый щёлк по закладке → Rename bookmark… → ввести что-то осмысленное
▫️ Посмотреть закладки: View → Tool Windows → Bookmarks (или Shift + F11)
В огромном проекте всегда есть места, которые хочется отметить или быстро находить.
Раньше это делали так:
🔴 Ставили неактивный брейкпойнт в нужном месте. В принципе нормально, но иногда сложно вспомнить, что где находится
⭐️ Добавляли файл в favorites. Файл добавляется целиком, что не очень удобно
Затем в IDEA убрали favorites и добавили закладки. Супер удобно, ни одна важная строчка теперь не потеряется.
▫️ Курсор на нужной строке → F11 → появляется закладка
▫️ Правый щёлк по закладке → Rename bookmark… → ввести что-то осмысленное
▫️ Посмотреть закладки: View → Tool Windows → Bookmarks (или Shift + F11)
Postgres и Kafka, часть 1
Возвращаюсь к ведению канала, и на этой неделе вас ждут 2 огненных поста🔥🔥 о взаимодействии Postgres и Kafka.
Эта пара безумно популярна на большинстве проектов👯♀️ База хранит данные, кафка отвечает за коммуникации между сервисами. Сегодня разберу одну проблему в их отношениях и расскажу вариант решения. А в следующем посте обсудим ещё 2 способа.
Рассмотрим проблему на простом примере.
Пользователь создал заказ, сервис принял запрос. Сервис добавил заказ в базу, и хочет рассказать другим сервисам о новом заказе. Что-то вроде такого:
В чём проблема?
Мы обращаемся к двум отдельным компонентам — базе данных и брокеру сообщений. Каждый из них в любой момент может отвалиться, например, пропадёт связь по сети. В зависимости от порядка строк в saveOrder возможны 2 негативных исхода:
😢 запись в базу сделали, сообщение не отправили
😢 отправили сообщение, но запись в БД не прошла
Получим несоответствие. Поэтому иногда хочется, чтобы события выполнились атомарно: либо оба успешно завершаются, либо ни одно из них.
Большинство разработчиков нетерпеливо скажут: "Что тут думать, нужен transaction outbox!!1". Но если спросить 10 человек, что они под этим понимают, получится 10 разных ответов.
В лучших традициях канала обсудим всё простыми словами:) Очень грубо все решения можно назвать так:
1️⃣ Убираем кафку
2️⃣ Убираем БД
3️⃣ Добавляем координатор
Сегодня рассмотрим первый вариант, в следующем посте — остальные два.
Вариант 1: убираем кафку
У Postgres есть механизм notify/listen, который отправляет уведомления заинтересованным лицам. И вместо отправки сообщений через кафку мы возьмём механизм подписки внутри БД.
База становится единственным компонентом и выполняет оба действия (сохранить в таблицу, уведомить заинтересованных) в одной транзакции.
Чтобы не решать проблемы с координацией двух компонентов, мы переложили всю работу на один.
✅ Образцовая транзакция: атомарность и доставка exactly once
✅ Минимальная задержка между сохранением в базу и уведомлением
❌ Ограниченная функциональность уведомлений
❌ Размытие ответственности — часть уведомлений делает Kafka, часть — Postgres
❌ Увеличение нагрузки на БД
Последний пункт — главный ограничитель, поэтому подход "база делает всё" не очень популярен.
Реализация
Можно взять spring-integration-jdbc и для отправки сообщений, и для получения уведомлений. Документация максимально скудная, дополнительные детали есть в этой статье (под VPN)
В следующем посте обсудим ещё 2 варианта🔥
Возвращаюсь к ведению канала, и на этой неделе вас ждут 2 огненных поста🔥🔥 о взаимодействии Postgres и Kafka.
Эта пара безумно популярна на большинстве проектов👯♀️ База хранит данные, кафка отвечает за коммуникации между сервисами. Сегодня разберу одну проблему в их отношениях и расскажу вариант решения. А в следующем посте обсудим ещё 2 способа.
Рассмотрим проблему на простом примере.
Пользователь создал заказ, сервис принял запрос. Сервис добавил заказ в базу, и хочет рассказать другим сервисам о новом заказе. Что-то вроде такого:
public Order saveOrder(…) {Другие сервисы, подписанные на orders, получат сообщение и что-то сделают. Посчитают скидки, обновят статистику, запишут заказ в свою БД и тд.
Order saved = orderRepo.save(…);
kafkaTemplate.send("orders",new OrderCreated(…));
}
В чём проблема?
Мы обращаемся к двум отдельным компонентам — базе данных и брокеру сообщений. Каждый из них в любой момент может отвалиться, например, пропадёт связь по сети. В зависимости от порядка строк в saveOrder возможны 2 негативных исхода:
😢 запись в базу сделали, сообщение не отправили
😢 отправили сообщение, но запись в БД не прошла
Получим несоответствие. Поэтому иногда хочется, чтобы события выполнились атомарно: либо оба успешно завершаются, либо ни одно из них.
Большинство разработчиков нетерпеливо скажут: "Что тут думать, нужен transaction outbox!!1". Но если спросить 10 человек, что они под этим понимают, получится 10 разных ответов.
В лучших традициях канала обсудим всё простыми словами:) Очень грубо все решения можно назвать так:
1️⃣ Убираем кафку
2️⃣ Убираем БД
3️⃣ Добавляем координатор
Сегодня рассмотрим первый вариант, в следующем посте — остальные два.
Вариант 1: убираем кафку
У Postgres есть механизм notify/listen, который отправляет уведомления заинтересованным лицам. И вместо отправки сообщений через кафку мы возьмём механизм подписки внутри БД.
База становится единственным компонентом и выполняет оба действия (сохранить в таблицу, уведомить заинтересованных) в одной транзакции.
Чтобы не решать проблемы с координацией двух компонентов, мы переложили всю работу на один.
✅ Образцовая транзакция: атомарность и доставка exactly once
✅ Минимальная задержка между сохранением в базу и уведомлением
❌ Ограниченная функциональность уведомлений
❌ Размытие ответственности — часть уведомлений делает Kafka, часть — Postgres
❌ Увеличение нагрузки на БД
Последний пункт — главный ограничитель, поэтому подход "база делает всё" не очень популярен.
Реализация
Можно взять spring-integration-jdbc и для отправки сообщений, и для получения уведомлений. Документация максимально скудная, дополнительные детали есть в этой статье (под VPN)
В следующем посте обсудим ещё 2 варианта🔥