Best practice: не использовать boolean параметры в методах
На вопрос выше сходу ответят только знатоки многопоточки. Остальные наверняка попытались придумать, что означает
Метод
Public методы определяют интерфейс. В хорошем API пользователю не нужно зарываться в доки, чтобы понять, как пользоваться классом.
Даже если у параметров хорошие имена, итоговые true и false выглядят несимпатично и неудобно.
С private методами ситуация другая. Внутренняя реализация должна быть читаемой и понятной, но требования всё же менее жёсткие, чем у public методов. В private методах флажки допустимы, но увлекаться не стоит:)
Как исправить метод с boolean параметром?
1️⃣ Сделать два метода
и зашить "особенность" флажка в название. Необязательно дублировать реализацию — public методы могут под капотом вызывать private метод с флажком.
Так сделано, например, в классе
2️⃣ Создать enum с двумя значениями и передавать его вместо флажка
Такой вариант предлагается в книге Effective Java (Item 51).
Выглядит лучше, чем флажки, но на практике такой способ встречается редко. Если enum нужен только для обозначения true/false, быстрее и проще сделать 2 метода с разными именами.
🔥 Ответ на вопрос перед постом — "невозможно определить".
Мы не знаем точно, начала ли задача выполнение до вызова cancel. Даже если задача стартовала, успех cancel зависит от кода внутри задачи. Поэтому итог непредсказуем🤷♀️
На вопрос выше сходу ответят только знатоки многопоточки. Остальные наверняка попытались придумать, что означает
cancel(true)
, и в чём разница с cancel(false)
.Метод
Future#cancel
— наглядная иллюстрация, почему boolean параметры в public методах не ок. Потому что непонятно, что означают true и false.Public методы определяют интерфейс. В хорошем API пользователю не нужно зарываться в доки, чтобы понять, как пользоваться классом.
Даже если у параметров хорошие имена, итоговые true и false выглядят несимпатично и неудобно.
С private методами ситуация другая. Внутренняя реализация должна быть читаемой и понятной, но требования всё же менее жёсткие, чем у public методов. В private методах флажки допустимы, но увлекаться не стоит:)
Как исправить метод с boolean параметром?
1️⃣ Сделать два метода
и зашить "особенность" флажка в название. Необязательно дублировать реализацию — public методы могут под капотом вызывать private метод с флажком.
Так сделано, например, в классе
HashMap
для методов put(k,v)
и putIfAbsent(k,v)
. Оба в итоге вызывают private метод putVal
с флажком ifAbsent2️⃣ Создать enum с двумя значениями и передавать его вместо флажка
enum ReplicateStatus {NO_REPLICATION, REPLICATE};
...
saveRequest(req, ReplicateStatus.REPLICATE);
Такой вариант предлагается в книге Effective Java (Item 51).
Выглядит лучше, чем флажки, но на практике такой способ встречается редко. Если enum нужен только для обозначения true/false, быстрее и проще сделать 2 метода с разными именами.
🔥 Ответ на вопрос перед постом — "невозможно определить".
Мы не знаем точно, начала ли задача выполнение до вызова cancel. Даже если задача стартовала, успех cancel зависит от кода внутри задачи. Поэтому итог непредсказуем🤷♀️
Java 22: изменения в Stream API (с плохим API)
19 марта выйдет Java 22. Там появится интересная превью фича Stream Gatherers. Коротко расскажу, что это, зачем нужно, и что с ней не так.
Начнём. Код со Stream API делится на 3 части:
▫️ источник данных
▫️ промежуточные операции: map, filter, flatMap
▫️ терминальная операция: collect, count, findAny
Промежуточных операций немного, но они легко покрывают большую часть задач.
Иногда их недостаточно. Например, хочется сделать distinct по какому-то признаку и дальше работать с исходными объектами:
Но это невозможно, distinct работает только по equals. В итоге надо либо извращаться, либо переписывать на for.
В Java 22 в Stream API появится универсальный метод gather. Туда можно передать логику преобразований с любыми отношениями - 1:1, 1:N, N:1, N:N. Синтаксис сложный, но круто, что такая возможность появилась.
Второе нововведение. Для collect есть готовые статические методы в классе Collectors, а для gather появится Gatherers с новыми методами. Самое полезное — два метода по работе с окнами:
🪟 windowFixed — поделить стрим на подмножества заданного размера
🪟 windowSliding — подмножества с пересечением одного элемента
Что не так c методом gather?
1️⃣ Многословность
Та же ситуация, что и с collect. Чтобы разбить список на окна, придётся написать
Даже со статическим импортом выглядит не очень. Хочется писать без лишних слов:
Да, исходный код Stream API сложный, и его тяжело расширять. Да, через статические методы реализация получится проще. Но не вижу ничего невозможного, чтобы сделать новые методы лаконичными, без приставки "
2️⃣ Странные новые методы
Начнём с оконных. windowSliding делает пересечение только по одному элементу. Зачем нужно это ограничение — непонятно. Так же непонятно, зачем делать два отдельных метода windowFixed и windowSliding.
За образец можно взять Kotlin:
Первый параметр задаёт размер окна, второй — шаг, с которым идём по списку. Удобно и понятно.
Ещё в Gatherers появятся три странных метода: fold, mapConcurrent и scan. С первого взгляда непонятно, зачем они нужны, очень уж специфичны.
В целом криминала в gather/Gatherers нет, жить можно. Но важный навык разработчика — замечать слабые места в своих и чужих решениях. Этот навык нужно развивать, для этого и нужен этот пост:)
Что ещё почитать:
🔥 Серия постов про коллекторы. Там же я рассказала, почему в стримах используется отдельный метод collect, а не просто toList()
🔥 Новые методы Stream API в Java 16
🔥 Критикую метод HashMap в Java 20. Хотя сам метод маленький, он показывает серьёзную ошибку проектирования API
19 марта выйдет Java 22. Там появится интересная превью фича Stream Gatherers. Коротко расскажу, что это, зачем нужно, и что с ней не так.
Начнём. Код со Stream API делится на 3 части:
▫️ источник данных
▫️ промежуточные операции: map, filter, flatMap
▫️ терминальная операция: collect, count, findAny
Промежуточных операций немного, но они легко покрывают большую часть задач.
Иногда их недостаточно. Например, хочется сделать distinct по какому-то признаку и дальше работать с исходными объектами:
clients.stream()
.distinct(Client::getBonusType)
.map(Client::getId).forEach(…)
Но это невозможно, distinct работает только по equals. В итоге надо либо извращаться, либо переписывать на for.
В Java 22 в Stream API появится универсальный метод gather. Туда можно передать логику преобразований с любыми отношениями - 1:1, 1:N, N:1, N:N. Синтаксис сложный, но круто, что такая возможность появилась.
Второе нововведение. Для collect есть готовые статические методы в классе Collectors, а для gather появится Gatherers с новыми методами. Самое полезное — два метода по работе с окнами:
🪟 windowFixed — поделить стрим на подмножества заданного размера
[1,2,3,4,5,6] → windowFixed(3) → [1,2,3], [4,5,6]
🪟 windowSliding — подмножества с пересечением одного элемента
[1,2,3,4,5,6,7] → windowSliding(3) → [1,2,3], [3,4,5], [5,6,7]
Что не так c методом gather?
1️⃣ Многословность
Та же ситуация, что и с collect. Чтобы разбить список на окна, придётся написать
list.stream()
.gather(Gatherers.windowFixed(2))
.collect(Collectors.toList())
Даже со статическим импортом выглядит не очень. Хочется писать без лишних слов:
list.stream()
.windowFixed(2)
.toList()
Да, исходный код Stream API сложный, и его тяжело расширять. Да, через статические методы реализация получится проще. Но не вижу ничего невозможного, чтобы сделать новые методы лаконичными, без приставки "
gather(Gatherers
"2️⃣ Странные новые методы
Начнём с оконных. windowSliding делает пересечение только по одному элементу. Зачем нужно это ограничение — непонятно. Так же непонятно, зачем делать два отдельных метода windowFixed и windowSliding.
За образец можно взять Kotlin:
list.windowed(5,2)
Первый параметр задаёт размер окна, второй — шаг, с которым идём по списку. Удобно и понятно.
Ещё в Gatherers появятся три странных метода: fold, mapConcurrent и scan. С первого взгляда непонятно, зачем они нужны, очень уж специфичны.
В целом криминала в gather/Gatherers нет, жить можно. Но важный навык разработчика — замечать слабые места в своих и чужих решениях. Этот навык нужно развивать, для этого и нужен этот пост:)
Что ещё почитать:
🔥 Серия постов про коллекторы. Там же я рассказала, почему в стримах используется отдельный метод collect, а не просто toList()
🔥 Новые методы Stream API в Java 16
🔥 Критикую метод HashMap в Java 20. Хотя сам метод маленький, он показывает серьёзную ошибку проектирования API
Чего ждать от Java ближайшие 10 лет?
Новые фичи в Java не создаются в вакууме, а объединяются в группы с конкретной целью. Каждый релиз — небольшие шаги в сторону этой цели. Расскажу про основные текущие проекты.
⭐️ Project Loom
Цель: добавить легковесные(виртуальные) потоки
Самая заметная фича со времён Stream API. Большинство проектов получат огромный буст от внедрения виртуальных потоков. Что важно — с минимальными изменениями в коде.
В Java 21 вышло базовое апи по работе с виртуальными потоками. Предстоит ещё много работы внутри JVM и в рамках языка, чтобы удобно управлять тысячами задач.
⭐️ ZGC / Shenandoah
Цель: сборщик мусора с минимальными паузами
Сборщики чуть отличаются по реализации, но задача одна — обеспечить минимум простоя во время сборки мусора. Разумеется, не бесплатно. Паузы уменьшаются, но увеличивается расход памяти и снижается пропускная способность.
Для большинства проектов это не актуально. Сборщик по умолчанию G1 отлично работает и становится лучше с каждым релизом.
⭐️ Project Panama
Цель: упростить работу с native кодом и памятью за пределами JVM
Проект делится на 2 направления:
🔹 Новый вариант JNI
Нужен для работы с библиотеками, которые не написаны на Java, и вряд ли когда-нибудь будут: работа с графикой, манипуляции с ОС, сетью и тд. Текущий JNI очень старый, работает медленно и не безопасен. Поэтому пишут новый:)
🔹 Работа с памятью за пределами JVM
Нужна проектам, которые хотят управлять памятью без посредничества Java. Самим делать сборку мусора, сжимать и раскладывать данные по определённым структурам.
⭐️ Project Amber
Цель: упростить язык, добавить новые конструкции
Самые "народные" фичи, которые часто попадают в обзоры и статьи:
Что-то получается хорошо, что-то не очень. Где-то много пафосных разговоров про data-oriented programming. Есть странные фичи, вроде упрощения написания Hello world.
⭐️ Project Leyden
Цель: ускорить время старта Java программ
Оптимизировать загрузку классов, линковку, перенести часть процессов на этап компиляции. На энтерпрайз повлияет мало, по сравнению с работой фреймворков ускорения на уровне JVM будут мало заметны.
⭐️ Project Valhalla
Цель: оптимизировать работу с данными
Здесь так же два направления:
🔹 Создать value types — объект с полями и методами, работа с которым идёт как с примитивом:
✅ Передаётся по значению
✅ Компактно лежит в памяти
✅ Не может быть null
🔹 Создать общую схему работы с примитивами, объектами и value types, избавить разработчика от мыслей про boxing/unboxing
❓ А когда будет готово?
Плохая новость — реализации всех проектов растягиваются на десятки лет.
10 лет — не преувеличение. Лямбда-выражения в java обсуждались с 2004 года, а увидели свет только в 2014.
В случае Java медлительность — это фича и часть стратегии: смотреть, как решаются проблемы в других языках и не изобретать велосипед. Осторожно выбирать, что войдёт в язык, а что — нет, тщательно продумывать архитектуру.
На java пишут большие системы, которые работают десятки лет. Поэтому основательный подход абсолютно оправдан😌
Новые фичи в Java не создаются в вакууме, а объединяются в группы с конкретной целью. Каждый релиз — небольшие шаги в сторону этой цели. Расскажу про основные текущие проекты.
⭐️ Project Loom
Цель: добавить легковесные(виртуальные) потоки
Самая заметная фича со времён Stream API. Большинство проектов получат огромный буст от внедрения виртуальных потоков. Что важно — с минимальными изменениями в коде.
В Java 21 вышло базовое апи по работе с виртуальными потоками. Предстоит ещё много работы внутри JVM и в рамках языка, чтобы удобно управлять тысячами задач.
⭐️ ZGC / Shenandoah
Цель: сборщик мусора с минимальными паузами
Сборщики чуть отличаются по реализации, но задача одна — обеспечить минимум простоя во время сборки мусора. Разумеется, не бесплатно. Паузы уменьшаются, но увеличивается расход памяти и снижается пропускная способность.
Для большинства проектов это не актуально. Сборщик по умолчанию G1 отлично работает и становится лучше с каждым релизом.
⭐️ Project Panama
Цель: упростить работу с native кодом и памятью за пределами JVM
Проект делится на 2 направления:
🔹 Новый вариант JNI
Нужен для работы с библиотеками, которые не написаны на Java, и вряд ли когда-нибудь будут: работа с графикой, манипуляции с ОС, сетью и тд. Текущий JNI очень старый, работает медленно и не безопасен. Поэтому пишут новый:)
🔹 Работа с памятью за пределами JVM
Нужна проектам, которые хотят управлять памятью без посредничества Java. Самим делать сборку мусора, сжимать и раскладывать данные по определённым структурам.
⭐️ Project Amber
Цель: упростить язык, добавить новые конструкции
Самые "народные" фичи, которые часто попадают в обзоры и статьи:
var
, текстовые блоки
, records
, pattern matching
, sealed
классы, string templates
и так далее.Что-то получается хорошо, что-то не очень. Где-то много пафосных разговоров про data-oriented programming. Есть странные фичи, вроде упрощения написания Hello world.
⭐️ Project Leyden
Цель: ускорить время старта Java программ
Оптимизировать загрузку классов, линковку, перенести часть процессов на этап компиляции. На энтерпрайз повлияет мало, по сравнению с работой фреймворков ускорения на уровне JVM будут мало заметны.
⭐️ Project Valhalla
Цель: оптимизировать работу с данными
Здесь так же два направления:
🔹 Создать value types — объект с полями и методами, работа с которым идёт как с примитивом:
✅ Передаётся по значению
✅ Компактно лежит в памяти
✅ Не может быть null
🔹 Создать общую схему работы с примитивами, объектами и value types, избавить разработчика от мыслей про boxing/unboxing
❓ А когда будет готово?
Плохая новость — реализации всех проектов растягиваются на десятки лет.
10 лет — не преувеличение. Лямбда-выражения в java обсуждались с 2004 года, а увидели свет только в 2014.
В случае Java медлительность — это фича и часть стратегии: смотреть, как решаются проблемы в других языках и не изобретать велосипед. Осторожно выбирать, что войдёт в язык, а что — нет, тщательно продумывать архитектуру.
На java пишут большие системы, которые работают десятки лет. Поэтому основательный подход абсолютно оправдан😌
Есть ли у вас высшее айтишное образование?
Anonymous Poll
39%
Есть. Доволен, что получил
15%
Есть, но мог бы обойтись и без
30%
Нет, но думаю, что вышка полезна
17%
Нет, и не вижу смысла
Нужно ли высшее образование для разработчика?
Встречаются 2 радикальных мнения:
🧔♂️ Вышка не нужна, там старая программа. Всё есть в интернете и можно выучить самому
👩🦰 Только с высшим образованием станешь “нормальным” разработчиком, без вышки твой потолок — это круды
Абстрактно рассуждать можно долго, я расскажу свой личный опыт.
Я отучилась в ИТМО на инженера-программиста 11 лет назад. "Информационные технологии" присутствуют в названии универа, факультета и специальности — уровень айтишности максимальный.
Какие плюсы вижу с высоты прожитых лет:
✅ Можно попробовать разные стороны IT
Помимо разработки было море других предметов: кодирование данных, математическое моделирование, ассемблер, компьютерная графика и даже 1С:). Здорово расширяет горизонт и помогает нащупать, что интересно конкретно тебе.
Выбор самоучек ограничен энтерпрайзным меню: фронт, бэк или мобилки. Дальше человек ориентируется на требования рынка.
Подход практичный, но не даёт шанса влюбиться в биоинформатику или разработку компиляторов.
✅ Базовые знания
Как работают операционные системы, компьютерные сети, linux/bash и тд.
На курсах такому не учат, в вакансиях эти требования не прописаны. В итоге часто получаем сотрудника, который боится консоли и не выходит за пределы IDE.
Джунам это простительно — один раз покажешь, как смотреть логи на удалённой машине, и они запомнят. Но чем сеньористее становишься, тем важнее понимание общей картины, и тут пробелы становятся очень заметны.
Эти темы легко освоить самостоятельно, глубокого погружения не требуется. Но на практике редко у кого доходят руки до таких тем. В универе у тебя нет выбора:)
✅ Стажировки, факультативы, конкурсы, хакатоны
от университета и известных компаний. Учёба по обмену в другой стране. Интересные люди приходят читать лекции и делиться опытом.
Во "внешнем мире" тоже полно стажировок, лекций и хакатонов, но для студентов количество программ в разы выше.
А теперь минусы:
❌ Долго и непродуктивно
За 4-5 лет через студента проходит огромный объём информации, и большинство тем он видит в первый и последний раз. Ни ассемблер, ни теория поля мне ни разу не пригодились.
Весь объём знаний, который я использую сейчас, осваивается за полгода-год самостоятельного обучения.
❌ Знаний из университета недостаточно
Нет специальности "Java разработчик". В университете учат на универсального инженера-программиста. Поэтому уходить вглубь пришлось самостоятельно.
В универе рассказывали, что после выпуска нас "будут с руками отрывать топовые компании". Так вот, это неправда. Хотя сейчас во многих универах есть практика с джавой, спрингом и докером, этого недостаточно. Доучиваться всё равно придётся.
Но не всё так страшно. Мне хватило пары месяцев, чтобы подготовиться к собеседованиям и устроиться на работу. После косинусных преобразований и электротехники освоить технологии энтерпрайза очень просто:)
Итого
Хотя мне не пригодилось 90% того, что было в университете, сама учёба мне нравилась, и это было отличное время🥰
🔸 Если вам 15-18, интересно айти и есть время на учебу, рекомендую идти в универ и брать от него максимум. Ходить на факультативы, вписываться в стажировки и доп.программы, наращивать связи
🔸 Если чувствуете серьёзный интерес к области, далёкой от энтерпрайза, поищите соответствующую магистратуру. Разработка языков, биоинформатика, квантовые вычисления, etc — всё это лучше осваивать комплексно и в кругу единомышленников
🔸 Если вы уже работаете и видите, куда и как расти, то получать дополнительно высшее образование не нужно. Полезнее прицельно изучать технологии и качать софт-скиллы
Встречаются 2 радикальных мнения:
🧔♂️ Вышка не нужна, там старая программа. Всё есть в интернете и можно выучить самому
👩🦰 Только с высшим образованием станешь “нормальным” разработчиком, без вышки твой потолок — это круды
Абстрактно рассуждать можно долго, я расскажу свой личный опыт.
Я отучилась в ИТМО на инженера-программиста 11 лет назад. "Информационные технологии" присутствуют в названии универа, факультета и специальности — уровень айтишности максимальный.
Какие плюсы вижу с высоты прожитых лет:
✅ Можно попробовать разные стороны IT
Помимо разработки было море других предметов: кодирование данных, математическое моделирование, ассемблер, компьютерная графика и даже 1С:). Здорово расширяет горизонт и помогает нащупать, что интересно конкретно тебе.
Выбор самоучек ограничен энтерпрайзным меню: фронт, бэк или мобилки. Дальше человек ориентируется на требования рынка.
Подход практичный, но не даёт шанса влюбиться в биоинформатику или разработку компиляторов.
✅ Базовые знания
Как работают операционные системы, компьютерные сети, linux/bash и тд.
На курсах такому не учат, в вакансиях эти требования не прописаны. В итоге часто получаем сотрудника, который боится консоли и не выходит за пределы IDE.
Джунам это простительно — один раз покажешь, как смотреть логи на удалённой машине, и они запомнят. Но чем сеньористее становишься, тем важнее понимание общей картины, и тут пробелы становятся очень заметны.
Эти темы легко освоить самостоятельно, глубокого погружения не требуется. Но на практике редко у кого доходят руки до таких тем. В универе у тебя нет выбора:)
✅ Стажировки, факультативы, конкурсы, хакатоны
от университета и известных компаний. Учёба по обмену в другой стране. Интересные люди приходят читать лекции и делиться опытом.
Во "внешнем мире" тоже полно стажировок, лекций и хакатонов, но для студентов количество программ в разы выше.
А теперь минусы:
❌ Долго и непродуктивно
За 4-5 лет через студента проходит огромный объём информации, и большинство тем он видит в первый и последний раз. Ни ассемблер, ни теория поля мне ни разу не пригодились.
Весь объём знаний, который я использую сейчас, осваивается за полгода-год самостоятельного обучения.
❌ Знаний из университета недостаточно
Нет специальности "Java разработчик". В университете учат на универсального инженера-программиста. Поэтому уходить вглубь пришлось самостоятельно.
В универе рассказывали, что после выпуска нас "будут с руками отрывать топовые компании". Так вот, это неправда. Хотя сейчас во многих универах есть практика с джавой, спрингом и докером, этого недостаточно. Доучиваться всё равно придётся.
Но не всё так страшно. Мне хватило пары месяцев, чтобы подготовиться к собеседованиям и устроиться на работу. После косинусных преобразований и электротехники освоить технологии энтерпрайза очень просто:)
Итого
Хотя мне не пригодилось 90% того, что было в университете, сама учёба мне нравилась, и это было отличное время🥰
🔸 Если вам 15-18, интересно айти и есть время на учебу, рекомендую идти в универ и брать от него максимум. Ходить на факультативы, вписываться в стажировки и доп.программы, наращивать связи
🔸 Если чувствуете серьёзный интерес к области, далёкой от энтерпрайза, поищите соответствующую магистратуру. Разработка языков, биоинформатика, квантовые вычисления, etc — всё это лучше осваивать комплексно и в кругу единомышленников
🔸 Если вы уже работаете и видите, куда и как расти, то получать дополнительно высшее образование не нужно. Полезнее прицельно изучать технологии и качать софт-скиллы
Fluent API: что такое и зачем нужен
Fluent API набирает популярность последние лет 5 и часто вызывает вопросы:
🤔 В чём его польза?
🤔 Чем он отличается от билдера?
🤔 Когда нужен, а когда нет?
В этом и следующем посте постараюсь ответить на эти вопросы.
Основная задача Fluent API — улучшить читаемость кода. Для этого:
🔸 Несколько методов объединяются в цепочку и вызываются друг за другом. Получается логический блок, который воспринимается как единое целое
🔸 Названия методов не ограничиваются глаголом
Пример:
Fluent API не всегда уместен и не всегда реализован удачно. Список формальных условий определить сложно, на практике такой формат часто выбирается интуитивно. Поэтому рассмотрим побольше примеров!
✅ Хороший пример из AssertJ:
За раз пишем несколько проверок для строки str. Без Fluent API кода будет больше
✅ Хороший пример из Мокито:
Читается как одно предложение. Мне даже сложно представить, как это написать в “традиционном” стиле:)
✅ Прекрасный пример из Spring JDBC:
Почему пример прекрасен? Потому что Fluent API скрывает работу с объектом PreparedStatement. Код получается не только короче, но и проще🔥
😐 Так себе пример из Spring Data:
Приставка with у методов лишняя, название последнего метода неудачное
❌ Плохой пример из SLF4J:
Классический
❌ Плохой пример из популярного джава канала:
Тоже никакой пользы от Fluent API, ни по читаемости, ни по удобству использования.
Итого
Fluent API подойдёт, когда работа с объектом проходит в несколько шагов, но единым логическим блоком. Основная цель — улучшить читаемость. Высший пилотаж — повысить с помощью Fluent API уровень инкапсуляции.
Чтобы сделать удобно и красиво, нужен опыт, насмотренность и немножко вдохновения:)
Отдельный случай — Fluent API при создании объектов. Здесь часто возникает путаница с билдером, и непонятно, что когда использовать. Этот вопрос я разберу отдельно в следующем посте🔥
Fluent API набирает популярность последние лет 5 и часто вызывает вопросы:
🤔 В чём его польза?
🤔 Чем он отличается от билдера?
🤔 Когда нужен, а когда нет?
В этом и следующем посте постараюсь ответить на эти вопросы.
Основная задача Fluent API — улучшить читаемость кода. Для этого:
🔸 Несколько методов объединяются в цепочку и вызываются друг за другом. Получается логический блок, который воспринимается как единое целое
🔸 Названия методов не ограничиваются глаголом
Пример:
CompletableFuture.runAsync(…).thenRun(…).exceptionally(…);
Fluent API не всегда уместен и не всегда реализован удачно. Список формальных условий определить сложно, на практике такой формат часто выбирается интуитивно. Поэтому рассмотрим побольше примеров!
✅ Хороший пример из AssertJ:
assertThat(str).startsWith(…).contains(…);
За раз пишем несколько проверок для строки str. Без Fluent API кода будет больше
✅ Хороший пример из Мокито:
when(mock.method()).thenReturn(…).thenReturn(…).thenThrow(…);
Читается как одно предложение. Мне даже сложно представить, как это написать в “традиционном” стиле:)
✅ Прекрасный пример из Spring JDBC:
List<User > users = jdbcClient.sql(…)
.param("rating", 5, Types.INTEGER)
.query(mapper)
.list();
Почему пример прекрасен? Потому что Fluent API скрывает работу с объектом PreparedStatement. Код получается не только короче, но и проще🔥
😐 Так себе пример из Spring Data:
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnorePaths(…)
.withStringMatcher(StringMatcher.ENDING);
Приставка with у методов лишняя, название последнего метода неудачное
❌ Плохой пример из SLF4J:
logger.atInfo().log(…);
Классический
logger.info(…)
короче и удобнее❌ Плохой пример из популярного джава канала:
Person person = new Person().setName(…).setAge(…);
Тоже никакой пользы от Fluent API, ни по читаемости, ни по удобству использования.
Итого
Fluent API подойдёт, когда работа с объектом проходит в несколько шагов, но единым логическим блоком. Основная цель — улучшить читаемость. Высший пилотаж — повысить с помощью Fluent API уровень инкапсуляции.
Чтобы сделать удобно и красиво, нужен опыт, насмотренность и немножко вдохновения:)
Отдельный случай — Fluent API при создании объектов. Здесь часто возникает путаница с билдером, и непонятно, что когда использовать. Этот вопрос я разберу отдельно в следующем посте🔥
Что лучше — Fluent API или Builder?
Продолжим разговор о Fluent API и рассмотрим частный случай — создание объекта. Сравним "классический" флуент апи с билдером и посмотрим, когда что использовать.
Вопрос очень актуален, так как не раз приходилось созваниваться и объяснять коллегам это всё словами:) Теперь буду кидать ссылку на пост.
Уточним термины
▫️
▫️
В чём разница на практике?
Допустим, надо создать экземпляр класса Account с информацией о счёте. Рассмотрим 4 варианта:
1️⃣ Все параметры передаём в конструкторе:
✅ Идеально для обязательных параметров. Компилятор не даст шанса пользователю что-то забыть
❌ Плохая читаемость — передаётся набор чисел, непонятно, что есть что
❌ Если есть необязательные параметры, нужно несколько конструкторов
2️⃣ Параметры выставляем через сеттеры:
✅ Читаемость лучше. Понятно, с какими полями работаем
❌ Нет контроля за обязательными параметрами. Пользователь должен знать, какие сеттеры вызвать обязательно, а какие нет
3️⃣ Используем Fluent API:
✅ Симпатичнее, чем сеттеры
❌ Та же проблема с обязательными параметрами, никаких проверок
❌ Не подходит для неизменяемых классов
❌ Более сложный код. Но если у вас разрешён ломбок, аннотация
4️⃣ Создание объекта через билдер:
✅ В build() можно добавить нужные проверки
✅ Можно использовать для неизменяемых классов
❌ Надо написать отдельный класс Builder или использовать аннотацию
Создание объекта через Fluent API не очень отличается от билдера по лаконичности, но проигрывает ему в функциональности. Fluent API не может проверить поля на "обязательность".
Итого
🔸 Если в классе мало полей, идём по классическому пути — обязательные параметры помещаем в конструктор, необязательные выставляем сеттерами
🔸 Если полей много или между полями сложные связи — используем билдер. Именованые методы улучшат читаемость, а метод build() проверит всё, что нужно
Fluent API обычно не даёт преимуществ в ситуациях выше, и редко используется для создания объектов. Его сильная сторона — логические блоки из нескольких операций🔥
Продолжим разговор о Fluent API и рассмотрим частный случай — создание объекта. Сравним "классический" флуент апи с билдером и посмотрим, когда что использовать.
Вопрос очень актуален, так как не раз приходилось созваниваться и объяснять коллегам это всё словами:) Теперь буду кидать ссылку на пост.
Уточним термины
▫️
Fluent API
— это стиль написания кода. Грубо говоря, когда методы соединяются через точку (method chaining). С помощью Fluent API можно создать объект и проинициализировать поля.▫️
Builder
— паттерн для создания объектов, суть которого в постепенном накоплении информации. Билдер тоже использует method chaining и относится к fluent api. В чём разница на практике?
Допустим, надо создать экземпляр класса Account с информацией о счёте. Рассмотрим 4 варианта:
1️⃣ Все параметры передаём в конструкторе:
Account acc = new Account(111, 222, 333);
✅ Идеально для обязательных параметров. Компилятор не даст шанса пользователю что-то забыть
❌ Плохая читаемость — передаётся набор чисел, непонятно, что есть что
❌ Если есть необязательные параметры, нужно несколько конструкторов
2️⃣ Параметры выставляем через сеттеры:
Account acc = new Account();
acc.setINN(111);
acc.setKPP(222);
acc.setRCBIC(333);
✅ Читаемость лучше. Понятно, с какими полями работаем
❌ Нет контроля за обязательными параметрами. Пользователь должен знать, какие сеттеры вызвать обязательно, а какие нет
3️⃣ Используем Fluent API:
Account acc = Account.new()
.INN(111)
.KPP(222)
.RCBIC(333);
✅ Симпатичнее, чем сеттеры
❌ Та же проблема с обязательными параметрами, никаких проверок
❌ Не подходит для неизменяемых классов
❌ Более сложный код. Но если у вас разрешён ломбок, аннотация
@Accessors(chain = true)
упрощает задачу4️⃣ Создание объекта через билдер:
Account acc = Account.builder().
.INN(111)
.KPP(222)
.RCBIC(333)
.build();
✅ В build() можно добавить нужные проверки
✅ Можно использовать для неизменяемых классов
❌ Надо написать отдельный класс Builder или использовать аннотацию
@Builder
из ломбокаСоздание объекта через Fluent API не очень отличается от билдера по лаконичности, но проигрывает ему в функциональности. Fluent API не может проверить поля на "обязательность".
Итого
🔸 Если в классе мало полей, идём по классическому пути — обязательные параметры помещаем в конструктор, необязательные выставляем сеттерами
🔸 Если полей много или между полями сложные связи — используем билдер. Именованые методы улучшат читаемость, а метод build() проверит всё, что нужно
Fluent API обычно не даёт преимуществ в ситуациях выше, и редко используется для создания объектов. Его сильная сторона — логические блоки из нескольких операций🔥
Паттерн Builder для продвинутых
Начав обсуждать билдер, остановиться сложно. Я и не буду:) Сегодня подсвечу пару интересных моментов и один приём, который точно пригодится на практике.
Сразу к делу:
1️⃣ Билдер может содержать сложную логику, а не просто копить будущие поля
Например:
🔸 Собирать одну информацию, отдавать объекту другую
🔸 Возвращать любые классы и объекты, даже прокси и уже созданные
🔸 Проводить операции внутри метода
Всё это при умелом использовании создаёт симпатичное и лаконичное API.
Пример 1: класс
Гораздо интереснее, чем кажется:
▫️ Копит данные в изменяемом массиве, на выходе отдаёт неизменяемую строку
▫️ Методы
▫️ Есть дополнительные методы вроде
В результате получаем удобный и функциональный класс. Ломбок такой билдер не соберёт, чатЖПТ такое не придумает:)
Пример 2: класс HttpClient
В самом простом варианте код выглядит так:
🤔 Зачем тут билдер? Почему просто не сделать new HttpClient()?
Потому что внутри build происходит такая магия:
Тут прячется установка работы с сетью в 200+ строк кода и механизм по завершению работы с сетью, когда объект HttpClient станет не нужен. Хотя объект работает с ресурсами, его не надо помещать в блок
Мелочь, а приятно🥰
И вторая классная особенность:
2️⃣ Билдер может сделать несколько объектов на базе переданных данных
Часто помогает при написании тестов.
Пример: тестируем работу с классом Account, в котором много полей. Можно в каждом тесте создавать тестовые объекты с нуля и копипастить километр кода. Или поступить иначе:
▫️ Сделать общий для всех тестов билдер, но build() не вызывать
▫️ В каждом тесте доставить нужные поля и построить новый объект
Получается так:
Это короче, чем в каждом тесте создавать объект с нуля. Плюс сразу видно "главное" для теста поле.
Приём подходит не всегда, но иногда здорово улучшает читаемость.
Итого
✅ Метод
✅ Builder может создать несколько объектов на основе переданных данных
Возьмите эти свойства на заметку, в умелых руках паттерн Builder делает код проще и удобнее🔥
Начав обсуждать билдер, остановиться сложно. Я и не буду:) Сегодня подсвечу пару интересных моментов и один приём, который точно пригодится на практике.
Сразу к делу:
1️⃣ Билдер может содержать сложную логику, а не просто копить будущие поля
Например:
🔸 Собирать одну информацию, отдавать объекту другую
🔸 Возвращать любые классы и объекты, даже прокси и уже созданные
🔸 Проводить операции внутри метода
build
Всё это при умелом использовании создаёт симпатичное и лаконичное API.
Пример 1: класс
StringBuilder
Гораздо интереснее, чем кажется:
▫️ Копит данные в изменяемом массиве, на выходе отдаёт неизменяемую строку
▫️ Методы
append
можно вызывать сколько угодно раз▫️ Есть дополнительные методы вроде
reverse
или delete
В результате получаем удобный и функциональный класс. Ломбок такой билдер не соберёт, чатЖПТ такое не придумает:)
Пример 2: класс HttpClient
В самом простом варианте код выглядит так:
HttpClient client = HttpClient.newBuilder().build();
🤔 Зачем тут билдер? Почему просто не сделать new HttpClient()?
Потому что внутри build происходит такая магия:
SingleFacadeFactory facadeFactory = new SingleFacadeFactory();
HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);
impl.start();
…
return facadeFactory.facade;
Тут прячется установка работы с сетью в 200+ строк кода и механизм по завершению работы с сетью, когда объект HttpClient станет не нужен. Хотя объект работает с ресурсами, его не надо помещать в блок
try-with-resources
.Мелочь, а приятно🥰
И вторая классная особенность:
2️⃣ Билдер может сделать несколько объектов на базе переданных данных
Часто помогает при написании тестов.
Пример: тестируем работу с классом Account, в котором много полей. Можно в каждом тесте создавать тестовые объекты с нуля и копипастить километр кода. Или поступить иначе:
▫️ Сделать общий для всех тестов билдер, но build() не вызывать
▫️ В каждом тесте доставить нужные поля и построить новый объект
Получается так:
// общее поле c базовой информацией
Account.Builder accBuilder = Account.builder().INN(…).KPP(…)
// в тесте, где важен БИК:
Account acc = accBuilder.RCBIC(123).build();
// в тесте, где нужно название банка
Acccount acc = accBuilder.bankName("green").build();
Это короче, чем в каждом тесте создавать объект с нуля. Плюс сразу видно "главное" для теста поле.
Приём подходит не всегда, но иногда здорово улучшает читаемость.
Итого
✅ Метод
build
может содержать сложную логику: от проверки параметров до шифрований и преобразований✅ Builder может создать несколько объектов на основе переданных данных
Возьмите эти свойства на заметку, в умелых руках паттерн Builder делает код проще и удобнее🔥
👩🏫Чему поучиться
Посты это прекрасный, но очень сжатый формат. Для тех, кто хочет большего, у меня есть ещё два курса:
1️⃣ Основы многопоточности
Бесплатный!
Его задача — пройтись по базовым темам многопоточки и заполнить пробелы, если они есть:
▫️ Класс Thread
▫️ Экзекьюторы
▫️ Основные проблемы в многопоточной среде
▫️ Модификатор volatile
▫️ Ключевое слово synchronized
Темы не раскрыты даже на 50%, многие вещи сильно упрощены. Но чтобы понять базу и пройти несложные собеседования — самое то.
2️⃣ Многопоточное программирование на Java
Самый полный и практичный курс по java.util.concurrent и смежным темам🔥
Разбираемся, как писать эффективный и быстрый код и готовимся к собеседованиям. Изучаем best practices и популярные ошибки.
Самая мощная часть — это практика:
✅ Решаем типичные энтерпрайзные задачи
✅ Анализируем многопоточный код Spring, Kafka, Hadoop и других популярных фреймворков — смотрим интересные приёмы и ищем ошибки
✅ Сравниваем разные решения и оцениваем производительность
Такого вы не найдёте нигде. Курс не пересказывает документацию и Concurrency in practice. Он о том, как применять многопоточку в реальной жизни.
Курс нацелен на самостоятельное прохождение, для всех заданий есть подсказки, инструкции для самопроверки и разбор важных нюансов. Я вела курс с обратной связью 3 года и прекрасно понимаю, где возникают сложности.
Если что-то совсем непонятно, можно у меня спросить:)
Что ещё:
▫️ Прохождение занимает 2-3 месяца
▫️ Доступ сразу после оплаты
▫️ Есть рассрочка и оплата за счёт компании
▫️ Можно оформить налоговый вычет и вернуть в следующем году 13% стоимости
Почитать программу, отзывы и записаться → https://fillthegaps.ru/mt
Посты это прекрасный, но очень сжатый формат. Для тех, кто хочет большего, у меня есть ещё два курса:
1️⃣ Основы многопоточности
Бесплатный!
Его задача — пройтись по базовым темам многопоточки и заполнить пробелы, если они есть:
▫️ Класс Thread
▫️ Экзекьюторы
▫️ Основные проблемы в многопоточной среде
▫️ Модификатор volatile
▫️ Ключевое слово synchronized
Темы не раскрыты даже на 50%, многие вещи сильно упрощены. Но чтобы понять базу и пройти несложные собеседования — самое то.
2️⃣ Многопоточное программирование на Java
Самый полный и практичный курс по java.util.concurrent и смежным темам🔥
Разбираемся, как писать эффективный и быстрый код и готовимся к собеседованиям. Изучаем best practices и популярные ошибки.
Самая мощная часть — это практика:
✅ Решаем типичные энтерпрайзные задачи
✅ Анализируем многопоточный код Spring, Kafka, Hadoop и других популярных фреймворков — смотрим интересные приёмы и ищем ошибки
✅ Сравниваем разные решения и оцениваем производительность
Такого вы не найдёте нигде. Курс не пересказывает документацию и Concurrency in practice. Он о том, как применять многопоточку в реальной жизни.
Курс нацелен на самостоятельное прохождение, для всех заданий есть подсказки, инструкции для самопроверки и разбор важных нюансов. Я вела курс с обратной связью 3 года и прекрасно понимаю, где возникают сложности.
Если что-то совсем непонятно, можно у меня спросить:)
Что ещё:
▫️ Прохождение занимает 2-3 месяца
▫️ Доступ сразу после оплаты
▫️ Есть рассрочка и оплата за счёт компании
▫️ Можно оформить налоговый вычет и вернуть в следующем году 13% стоимости
Почитать программу, отзывы и записаться → https://fillthegaps.ru/mt
На основе чего по умолчанию считается хэшкод объекта?
Anonymous Poll
22%
Случайное число от генератора внутри JVM
3%
Случайное число от операционной системы
64%
Адрес объекта в памяти
5%
Внутренние переменные потока
3%
Время создания объекта
2%
Последовательность по возрастанию
Как считается хэшкод по умолчанию?
На собеседованиях часто обсуждают методы
Если хочется посмотреть, как думает человек за пределами стандартного ответа, возможен такой диалог:
— Как считается хэшкод по умолчанию?
— Это адрес объекта в памяти
— А почему так?
— Адрес каждого объекта уникален, то что надо для хэшкода
— Сборщик мусора перемещает объекты внутри памяти. Как это влияет на значения хэшей?
— 😥
Тут можно предположить, что хэшкод считается один раз и приписывается к самому объекту. Это будет логичная мысль.
А вот вычисление хэша на основе адреса в памяти — популярный миф. В этом посте разберём, как на самом считается хэшкод под умолчанию.
В разных JVM реализации могут отличаться. Рассмотрим исходный код hashcode в OpenJDK. Там 6(!) стратегий вычисления хэшкода. Стратегия задаётся опциями VM:
При первом вызове хэш сохраняется внутри объекта и не меняется. Теперь к стратегиям:
🔸-XX:hashCode=0
Случайное число по алгоритму Lehmer RNG. Генератор один на всех, поэтому работает медленно
🔸-XX:hashCode=2
Чемпион по скорости, всегда возвращает 1:
Используется как отправная точка для тестов остальных стратегий
🔸-XX:hashCode=3
Обычная возрастающая последовательность:
🔸-XX:hashCode=4
Текущий адрес в памяти. Популярный, но неправильный ответ на собеседованиях. Отчасти в этом виновата спецификация: там адрес приводится как пример реализации. Работает быстро, но не даёт равномерного распределения и должного уровня уникальности
🔸-XX:hashCode=1
Адрес объекта в памяти и немного манипуляций с битами
🔸 Стратегия по умолчанию
Случайное число по алгоритму Xorshift RNG. Следующее значение вычисляется на основе предыдущего. Значения равномерно распределены. Работает быстро, тк у каждого потока свой генератор, и синхронизации между потоками нет
Рейтинг стратегий по скорости:
🏆 Вернуть единицу: 184 операций за микросекунду
🥈 Вариант по умолчанию: 176 оп/мск
🥉 Адрес в памяти-1: 160 оп/мск
▪️ Растущая последовательность: 14 оп/мск
▪️ Случайное число и глобальная переменная: 10 оп/мск
Интересно, что до java 8 самая медленная опция была вариантом по умолчанию.
Итого
✅ Реализация хэшкода зависит от JVM и VM-флажков
✅ В OpenJDK 6 стратегий вычисления хэшкода. По умолчанию используется генератор случайных чисел в рамках одного потока
✅ Расчёт на основе адреса памяти не очень хорош по итоговым характеристикам
✅ Общие переменные в методах снижают производительность при интенсивном использовании. Яркие примеры — стратегии хэшкода с общим генератором и последовательностью
На собеседованиях часто обсуждают методы
equals
и hashcode
. За что отвечают, как соотносятся между собой, когда переопределять, а когда не стоит.Если хочется посмотреть, как думает человек за пределами стандартного ответа, возможен такой диалог:
— Как считается хэшкод по умолчанию?
— Это адрес объекта в памяти
— А почему так?
— Адрес каждого объекта уникален, то что надо для хэшкода
— Сборщик мусора перемещает объекты внутри памяти. Как это влияет на значения хэшей?
— 😥
Тут можно предположить, что хэшкод считается один раз и приписывается к самому объекту. Это будет логичная мысль.
А вот вычисление хэша на основе адреса в памяти — популярный миф. В этом посте разберём, как на самом считается хэшкод под умолчанию.
В разных JVM реализации могут отличаться. Рассмотрим исходный код hashcode в OpenJDK. Там 6(!) стратегий вычисления хэшкода. Стратегия задаётся опциями VM:
-XX:+UnlockExperimentalVMOptions -XX:hashCode={число}
При первом вызове хэш сохраняется внутри объекта и не меняется. Теперь к стратегиям:
🔸-XX:hashCode=0
Случайное число по алгоритму Lehmer RNG. Генератор один на всех, поэтому работает медленно
🔸-XX:hashCode=2
Чемпион по скорости, всегда возвращает 1:
java.lang.Object@1
Используется как отправная точка для тестов остальных стратегий
🔸-XX:hashCode=3
Обычная возрастающая последовательность:
java.lang.Object@a4
java.lang.Object@a5
java.lang.Object@a6
🔸-XX:hashCode=4
Текущий адрес в памяти. Популярный, но неправильный ответ на собеседованиях. Отчасти в этом виновата спецификация: там адрес приводится как пример реализации. Работает быстро, но не даёт равномерного распределения и должного уровня уникальности
🔸-XX:hashCode=1
Адрес объекта в памяти и немного манипуляций с битами
🔸 Стратегия по умолчанию
Случайное число по алгоритму Xorshift RNG. Следующее значение вычисляется на основе предыдущего. Значения равномерно распределены. Работает быстро, тк у каждого потока свой генератор, и синхронизации между потоками нет
Рейтинг стратегий по скорости:
🏆 Вернуть единицу: 184 операций за микросекунду
🥈 Вариант по умолчанию: 176 оп/мск
🥉 Адрес в памяти-1: 160 оп/мск
▪️ Растущая последовательность: 14 оп/мск
▪️ Случайное число и глобальная переменная: 10 оп/мск
Интересно, что до java 8 самая медленная опция была вариантом по умолчанию.
Итого
✅ Реализация хэшкода зависит от JVM и VM-флажков
✅ В OpenJDK 6 стратегий вычисления хэшкода. По умолчанию используется генератор случайных чисел в рамках одного потока
✅ Расчёт на основе адреса памяти не очень хорош по итоговым характеристикам
✅ Общие переменные в методах снижают производительность при интенсивном использовании. Яркие примеры — стратегии хэшкода с общим генератором и последовательностью
В каких методах НЕправильно используется Optional?
Anonymous Poll
6%
Optional<Student> get(long id)
67%
Optional<List<Student>> all()
65%
List<Student> search(Optional<String> city)
63%
void init(Optional<String> conf)
10%
Optional<String> getAddress()
5 ошибок при использовании Optional
Например, функция поиска может найти нужный элемент, а может и не найти. В этой ситуации отлично подойдёт
С виду всё просто и понятно.
Тем не менее, на практике
1️⃣ Использовать Optional во входных параметрах и конструкторах
Приходится добавлять лишние обёртки и проверки.
✅ Лучше проверять параметры на null в начале метода:
2️⃣ Возвращать Optional при наличии дефолтного значения
3️⃣ Обрабатывать Optional не сразу
и тащить его далеко по коду. Идеально, если обработка происходит сразу после возвращения из метода:
4️⃣ Возвращать Optional для коллекций
Если элементов нет, верните пустой список:
5️⃣ Неоптимальная работа с примитивами
Для int, long и double есть специальные классы:
У них нет затрат на создание объекта, а код становится чуть короче и понятнее. Методов меньше, чем в классическом Optional, но при интенсивной работе с примитивами возможен прирост производительности.
Ответ на вопрос перед постом
Входные параметры лучше проверять внутри метода:
Лучше вернуть пустой список, если элементов нет:
Optional
— супер удобный инструмент, который появился в java 8. В документации его цель явно обозначена — показать, что результат вызова метода может отсутствовать.Например, функция поиска может найти нужный элемент, а может и не найти. В этой ситуации отлично подойдёт
Optional
:Optional<String> search();
С виду всё просто и понятно.
Тем не менее, на практике
Optional
часто используется некорректно, код получается сложным или плохо читаемым. Самые популярные ошибки:1️⃣ Использовать Optional во входных параметрах и конструкторах
❌ void init(Optional<String> value) {…}
Приходится добавлять лишние обёртки и проверки.
✅ Лучше проверять параметры на null в начале метода:
void init (String value) {
if (value == null) …
}
2️⃣ Возвращать Optional при наличии дефолтного значения
❌ return Optional.ofNullable(value).orElse("default");
✅ return value == null ? "default" : value;
3️⃣ Обрабатывать Optional не сразу
и тащить его далеко по коду. Идеально, если обработка происходит сразу после возвращения из метода:
Optiona<String> valueOpt = …
String value = valueOpt.orElse("default");
4️⃣ Возвращать Optional для коллекций
❌ public Optional<List<Integer>> search(…)
Если элементов нет, верните пустой список:
✅ public List<Integer> search(…) {
… return List.of();
}
5️⃣ Неоптимальная работа с примитивами
Для int, long и double есть специальные классы:
OptionalInt
, OptionalDouble
и OptionalLong
.У них нет затрат на создание объекта, а код становится чуть короче и понятнее. Методов меньше, чем в классическом Optional, но при интенсивной работе с примитивами возможен прирост производительности.
Ответ на вопрос перед постом
Входные параметры лучше проверять внутри метода:
❌ List<Student> search(Optional<String> city)
❌ void init(Optional<String> conf)
Лучше вернуть пустой список, если элементов нет:
❌ Optional<List<Student>> all()
Во что компилируются record?
Anonymous Poll
7%
В специальную структуру внутри JVM
16%
В класс-наследник от Record
5%
В массив, размер которого равен количеству полей
11%
В экземпляр класса Record
61%
В final класс
Новая фича Java 23: Derived Record
Внимательные читатели давно заметили, что я люблю разбирать джаву и разное API с точки зрения дизайна. Смотреть, как решается исходная проблема и насколько удобно и адекватно решение.
Сегодня сяду на любимого конька и расскажу о новом синтаксисе, который появится в java 23 — Derived Record.
Напомню, что records компилируются в final классы с final полями. Неизменяемые переменные — тренд последних лет. В этом подходе данные не меняются, а при модификации создаются новые объекты.
Недостаток очевиден — чтобы создать объект на основе предыдущего, надо написать довольно много кода. Возьмём простой пример — record Point с двумя полями: Х и Y:
Допустим, нам нужна точка p2, у которой Х в 2 раза больше, чем у p1:
Надо прочитать ВСЕ поля исходного объекта и передать их в конструктор с нужными модификациями. Даже в классе с двумя полями надо напрячь глаза, чтобы понять, какой параметр меняется. В реальной жизни полей больше, и получается максимально унылая простыня кода.
Вместо конструктора удобно использовать with методы:
Сразу видно параметр, которым отличаются точки🔥
With методы — отличное решение, но не идеальное.
Возьмём случай, когда между полями должны соблюдаться некоторые соотношения. Например, Х всегда должен быть больше Y.
With методы работают только с одним полем и не знают про дальнейшие изменения. Поэтому сложная валидация в них затруднительна. После вызова
получаем объект со сломанным инвариантом. Мы не знаем, вызовет ли пользователь потом withY, и не можем это проконтролировать.
Новый JEP работает с такими случаями и предлагает создавать объекты так:
🔸 Названия полей p1 берутся в качестве локальных переменных с теми же именами
🔸 Выполняются преобразования в скобках
🔸 После выполнения блока вызывается канонический конструктор Point, где находится валидация полей
🔸 Полученный объект присваивается объекту p4
В целом всё неплохо, но возникает вопрос целесообразности. На моей практике рекордс обычно используют как контейнер данных, который редко меняется. Если всё же меняется, то обычно это простая логика, куда бы идеально вписались with методы. Если бы records превращались в классы с готовыми with методами — было бы супер удобно.
Я редко встречала в рекордах сложную валидацию и жёсткие отношения между полями. Мне кажется, добавлять новый синтаксис для таких случаев избыточно. Если нужна проверка инвариантов, можно написать свой метод.
Поэтому вердикт новой фиче — сомнительно, но окэй. Я бы потратила человеко-часы на что-нибудь другое🙂 К слову, это уже вторая фича java 23, которая не вызвала у меня восторга. Первой были Gatherers, почитать можно тут
А как вам новый синтаксис в рекордах?
🔥 - огонь, очень полезно
👍 - не вау, но пусть будет
❤️ - просто спасибо за пост:)
Внимательные читатели давно заметили, что я люблю разбирать джаву и разное API с точки зрения дизайна. Смотреть, как решается исходная проблема и насколько удобно и адекватно решение.
Сегодня сяду на любимого конька и расскажу о новом синтаксисе, который появится в java 23 — Derived Record.
Напомню, что records компилируются в final классы с final полями. Неизменяемые переменные — тренд последних лет. В этом подходе данные не меняются, а при модификации создаются новые объекты.
Недостаток очевиден — чтобы создать объект на основе предыдущего, надо написать довольно много кода. Возьмём простой пример — record Point с двумя полями: Х и Y:
record Point(int x, int y) { }
Point p1 = new Point(1,1);
Допустим, нам нужна точка p2, у которой Х в 2 раза больше, чем у p1:
Point p2 = new Point(p1.x()*2, p1.y());
Надо прочитать ВСЕ поля исходного объекта и передать их в конструктор с нужными модификациями. Даже в классе с двумя полями надо напрячь глаза, чтобы понять, какой параметр меняется. В реальной жизни полей больше, и получается максимально унылая простыня кода.
Вместо конструктора удобно использовать with методы:
Point p2 = p1.withX(p1.x()*2);
Сразу видно параметр, которым отличаются точки🔥
With методы — отличное решение, но не идеальное.
Возьмём случай, когда между полями должны соблюдаться некоторые соотношения. Например, Х всегда должен быть больше Y.
With методы работают только с одним полем и не знают про дальнейшие изменения. Поэтому сложная валидация в них затруднительна. После вызова
Point p3 = p1.withX(p1.x() - 5);
получаем объект со сломанным инвариантом. Мы не знаем, вызовет ли пользователь потом withY, и не можем это проконтролировать.
Новый JEP работает с такими случаями и предлагает создавать объекты так:
Point p4 = p1 with {
x *= 2;
y *= 2;
};
🔸 Названия полей p1 берутся в качестве локальных переменных с теми же именами
🔸 Выполняются преобразования в скобках
🔸 После выполнения блока вызывается канонический конструктор Point, где находится валидация полей
🔸 Полученный объект присваивается объекту p4
В целом всё неплохо, но возникает вопрос целесообразности. На моей практике рекордс обычно используют как контейнер данных, который редко меняется. Если всё же меняется, то обычно это простая логика, куда бы идеально вписались with методы. Если бы records превращались в классы с готовыми with методами — было бы супер удобно.
Я редко встречала в рекордах сложную валидацию и жёсткие отношения между полями. Мне кажется, добавлять новый синтаксис для таких случаев избыточно. Если нужна проверка инвариантов, можно написать свой метод.
Поэтому вердикт новой фиче — сомнительно, но окэй. Я бы потратила человеко-часы на что-нибудь другое🙂 К слову, это уже вторая фича java 23, которая не вызвала у меня восторга. Первой были Gatherers, почитать можно тут
А как вам новый синтаксис в рекордах?
🔥 - огонь, очень полезно
👍 - не вау, но пусть будет
❤️ - просто спасибо за пост:)
Как называется соединение строк с подстановкой переменных вроде $x plus $y equals ${x + y}?
Anonymous Poll
21%
Интернирование
28%
Конкатенация
6%
Экстраполяция
9%
Интрополяция
36%
Интерполяция
Апдейт по фичам java 23
⚡️ Новость 1 ⚡️
Derived records не войдут в java 23. Причина прозаична и близка каждому из нас — её просто не успели доделать:)
Фича поедет в следующий релиз, и пощупать её можно будет зимой.
⚡️ Новость 2 ⚡️
Еще один кандидат в java 23, который не смог — это String templates. Но тут история другая.
Напомню, в чём идея темплейтов. Есть две стратегии работы со строками:
🔸 Конкатенация — собираем строку по частям:
Этот подход использует StringBuilder, метод concat и тд.
🔸 Интерполяция — замена переменных внутри шаблона:
В чистом виде в java такого нет. Отдаленно похожи Formatter и MessageFormat, но там вместо переменных какие-то %s и %d, а переменные стоят отдельно:
А так чистокровная интерполяция выглядит в Kotlin:
Новые String templates реализуют своеобразный вариант java интерполяции. В начале строки добавляется STR, переменные обрамляются в \{}
❓ Зачем они добавили префикс STR? Почему нельзя сделать как в котлине?
По 2 причинам:
1️⃣ Для обратной совместимости
На джаве написано много кода и библиотек. Некоторые из них используют формат с фигурными скобками, и будет обидно, если этот код перестанет компилироваться. Чтобы этого избежать, решили явно обозначать строки для интерполяции
2️⃣ Для других обработчиков
По задумке авторов другие процессоры могут делать больше, чем просто подстановку переменных. Например, собрать и провалидировать SQL запрос:
❓ Что пошло не так?
Разработчики собрали фидбэк и решили, что процессоры (STR, DB, etc) — это лишнее усложнение и очень странный API. Я с ними согласна, выглядит как работа со статическими полями, и вряд ли кому-то нужно что-то большее, чем работа со строками.
❓ Что в итоге с интерполяцией?
Brian Goetz (архитектор java) написал:
The remaining question that everyone is probably asking is: “so how do we do interpolation.” The answer there is “ordinary library methods”.
= в текущем виде фича не будет реализована, интерполяции как в других языках тоже не будет, всё остаётся как есть.
Напомню, что идея String templates появилась в 2021 году, прошлой осенью вышла preview версия. И только сейчас разработчики поняли, что фича так себе. Косяки бывают на всех проектах, это нормально:)
⚡️ Новость 1 ⚡️
Derived records не войдут в java 23. Причина прозаична и близка каждому из нас — её просто не успели доделать:)
Фича поедет в следующий релиз, и пощупать её можно будет зимой.
⚡️ Новость 2 ⚡️
Еще один кандидат в java 23, который не смог — это String templates. Но тут история другая.
Напомню, в чём идея темплейтов. Есть две стратегии работы со строками:
🔸 Конкатенация — собираем строку по частям:
String str = "Hello, " + name + "!";
Этот подход использует StringBuilder, метод concat и тд.
🔸 Интерполяция — замена переменных внутри шаблона:
String name = "Jake";
String str = "Hello, ${name}!";
В чистом виде в java такого нет. Отдаленно похожи Formatter и MessageFormat, но там вместо переменных какие-то %s и %d, а переменные стоят отдельно:
String.format("%d plus %d equals %d", x, y, x + y);
А так чистокровная интерполяция выглядит в Kotlin:
"$x plus $y equals ${x + y}"
Новые String templates реализуют своеобразный вариант java интерполяции. В начале строки добавляется STR, переменные обрамляются в \{}
int x = 10, y = 20;
String str = STR."\{x} + \{y} = \{x + y}";
// "10 + 20 = 30"
❓ Зачем они добавили префикс STR? Почему нельзя сделать как в котлине?
По 2 причинам:
1️⃣ Для обратной совместимости
На джаве написано много кода и библиотек. Некоторые из них используют формат с фигурными скобками, и будет обидно, если этот код перестанет компилироваться. Чтобы этого избежать, решили явно обозначать строки для интерполяции
2️⃣ Для других обработчиков
По задумке авторов другие процессоры могут делать больше, чем просто подстановку переменных. Например, собрать и провалидировать SQL запрос:
String sql = DB."select from users where id=/{id}";
❓ Что пошло не так?
Разработчики собрали фидбэк и решили, что процессоры (STR, DB, etc) — это лишнее усложнение и очень странный API. Я с ними согласна, выглядит как работа со статическими полями, и вряд ли кому-то нужно что-то большее, чем работа со строками.
❓ Что в итоге с интерполяцией?
Brian Goetz (архитектор java) написал:
The remaining question that everyone is probably asking is: “so how do we do interpolation.” The answer there is “ordinary library methods”.
= в текущем виде фича не будет реализована, интерполяции как в других языках тоже не будет, всё остаётся как есть.
Напомню, что идея String templates появилась в 2021 году, прошлой осенью вышла preview версия. И только сейчас разработчики поняли, что фича так себе. Косяки бывают на всех проектах, это нормально:)
IDEA: шорткаты для сверхзвуковой навигации по коду
🚀 Посмотреть список методов:
Быстро найти нужный метод или узнать, что вообще умеет класс
🚀 Найти класс или файл в проекте:
Откроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению:
Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов
🚀 Найти строку по номеру:
Очень удобные шорткаты, обязательно попробуйте🔥
🚀 Посмотреть список методов:
Ctrl + F12
Быстро найти нужный метод или узнать, что вообще умеет класс
🚀 Найти класс или файл в проекте:
Shift-Shift
Откроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению:
Ctrl + B
Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
Ctrl + Alt + ⬅️
Ctrl + Alt + ➡️
IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов
🚀 Найти строку по номеру:
Ctrl + G
Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строкуОчень удобные шорткаты, обязательно попробуйте🔥
Полезное в PostgreSQL, часть 1
Алгоритмы с литкода — это База. Но только для собеседований, на практике вы редко их встретите.
С SQL ситуация обратная. На собесах спросят максимум джойн и HAVING, а на практике всё гораздо интереснее.
Сегодня расскажу простые и полезные приёмы PostgreSQL, с которыми ваши скрипты станут симпатичнее💅
1️⃣ JOIN + USING
Если при джойне таблиц имена столбцов совпадают, вместо ON используйте USING:
2️⃣ INSERT + SELECT
Чтобы вставить в одну таблицу значения из другой, нет смысла вытаскивать данные отдельно. INSERT и SELECT прекрасно комбинируются:
3️⃣ RETURNING
Чтобы после вставки вернуть новые строки, отдельный SELECT не нужен. Добавьте RETURNING для нужных столбцов:
Обычно возвращают id, но можно вернуть несколько столбцов и даже *.
Для DELETE RETURNING тоже работает и возвращает удалённые строки.
4️⃣ Тестовые данные
Для генерации простейших тестовых данных через SQL вам пригодятся:
🔸
🔸
🔸
Дальше комбинируем. Например, так:
Одним запросом получаем 100 строк в базе!
Приёмы выше хоть и простые, но пригодятся на большинстве проектов. Пользуйтесь и ставьте огонёчек, если нужна вторая часть🔥
Алгоритмы с литкода — это База. Но только для собеседований, на практике вы редко их встретите.
С SQL ситуация обратная. На собесах спросят максимум джойн и HAVING, а на практике всё гораздо интереснее.
Сегодня расскажу простые и полезные приёмы PostgreSQL, с которыми ваши скрипты станут симпатичнее💅
1️⃣ JOIN + USING
Если при джойне таблиц имена столбцов совпадают, вместо ON используйте USING:
SELECT * FROM table1
INNER JOIN table2 USING (user_id);
2️⃣ INSERT + SELECT
Чтобы вставить в одну таблицу значения из другой, нет смысла вытаскивать данные отдельно. INSERT и SELECT прекрасно комбинируются:
INSERT INTO users (id, name)
SELECT user_id, fullname FROM customers;
3️⃣ RETURNING
Чтобы после вставки вернуть новые строки, отдельный SELECT не нужен. Добавьте RETURNING для нужных столбцов:
INSERT INTO users VALUE (…)
RETURNING id;
Обычно возвращают id, но можно вернуть несколько столбцов и даже *.
Для DELETE RETURNING тоже работает и возвращает удалённые строки.
4️⃣ Тестовые данные
Для генерации простейших тестовых данных через SQL вам пригодятся:
🔸
generate_series(a, b)
— последовательность целых чисел от a до b. Той же функцией генерятся даты в заданном диапазоне, синтаксис смотрите в документации🔸
random()
— случайное число от 0 до 1🔸
md5(а)
— хэш числа а. Помогает превратить результат random() в строку Дальше комбинируем. Например, так:
INSERT INTO t
SELECT id, // 1, 2,…, 100
'name' || id, // name1,…, name100
random(),
md5(random()::text)
FROM generate_series(1,100) id;
Одним запросом получаем 100 строк в базе!
Приёмы выше хоть и простые, но пригодятся на большинстве проектов. Пользуйтесь и ставьте огонёчек, если нужна вторая часть🔥
Полезное в PostgreSQL, часть 2
Прошлый пост собрал столько огоньков, что аж на душе потеплело, спасибо❤️
Продолжим наш ликбез по SQL. Сегодня расскажу про 3 похожие конструкции для вложенных запросов: CTE, View и Materialized View.
⭐️ Сommon Table Expression (СТЕ) выглядит так:
cte_name используется как источник данных
⭐️ VIEW и MATERIALIZED VIEW выглядят похоже:
Полученное вью также используется как источник данных:
В чём же разница?
1️⃣ Что именно хранится
VIEW — это просто сокращение запроса, результат выполнения не сохраняется. При каждом
MATERIALIZED VIEW сохраняет результат на момент выполнения. По сути создаётся временная таблица с копией данных.
Важный момент: если исходные данные поменяются, они не повлияют на данные в MATERIALIZED VIEW. Для актуализации данных надо отдельно вызвать команду REFRESH.
СТЕ как MATERIALIZED VIEW сохраняет результат выполнения и считается один раз.
2️⃣ Видимость
CTE не существует сам по себе, за ним обязательно должен следовать запрос, который его использует. Можно сказать, что область видимости и время жизни CTE — один запрос.
VIEW и MATERIALIZED VIEW доступны на уровне схемы, можно пользоваться много раз из разных мест. Удалять вью надо явно командой DROP.
Это основные отличия. Есть ещё несколько, но не будем углубляться:)
Примерные кейсы использования:
🔧 CTE — сделать сложные запросы более читаемыми
🔨 View — синоним для популярного запроса на выборку
🪛 Materialized view — снимок данных, которые долго считать с нуля, но к которым много обращений
Теперь закрепим знания из этого и предыдущего поста небольшой задачкой✍️
Есть 2 таблицы:
🔸 from_table со столбцами id, firstname, lastname.
🔸 to_table со столбцами id, name
Задача: перенести все строки из from_table в to_table, соединив firstname и lastname в одно поле name. После выполнения запроса from_table должен стать пустым.
Попробуйте выполнить задачу ОДНИМ запросом.
Онлайн Postgres: pgplayground
Исходный код для экспериментов:
✨ РЕШЕНИЕ ✨
Вспоминаем, что DELETE возвращает удалённые строки. Формируем из них СТЕ и передаём в INSERT:
Прошлый пост собрал столько огоньков, что аж на душе потеплело, спасибо❤️
Продолжим наш ликбез по SQL. Сегодня расскажу про 3 похожие конструкции для вложенных запросов: CTE, View и Materialized View.
⭐️ Сommon Table Expression (СТЕ) выглядит так:
WITH cte_name AS (
SELECT …
)
SELECT … FROM cte_name;
cte_name используется как источник данных
⭐️ VIEW и MATERIALIZED VIEW выглядят похоже:
CREATE (MATERIALIZED) VIEW view_name AS
SELECT … ;
Полученное вью также используется как источник данных:
SELECT … FROM view_name;
В чём же разница?
1️⃣ Что именно хранится
VIEW — это просто сокращение запроса, результат выполнения не сохраняется. При каждом
FROM view_name
запрос выполняется заново.MATERIALIZED VIEW сохраняет результат на момент выполнения. По сути создаётся временная таблица с копией данных.
Важный момент: если исходные данные поменяются, они не повлияют на данные в MATERIALIZED VIEW. Для актуализации данных надо отдельно вызвать команду REFRESH.
СТЕ как MATERIALIZED VIEW сохраняет результат выполнения и считается один раз.
2️⃣ Видимость
CTE не существует сам по себе, за ним обязательно должен следовать запрос, который его использует. Можно сказать, что область видимости и время жизни CTE — один запрос.
VIEW и MATERIALIZED VIEW доступны на уровне схемы, можно пользоваться много раз из разных мест. Удалять вью надо явно командой DROP.
Это основные отличия. Есть ещё несколько, но не будем углубляться:)
Примерные кейсы использования:
🔧 CTE — сделать сложные запросы более читаемыми
🔨 View — синоним для популярного запроса на выборку
🪛 Materialized view — снимок данных, которые долго считать с нуля, но к которым много обращений
Теперь закрепим знания из этого и предыдущего поста небольшой задачкой✍️
Есть 2 таблицы:
🔸 from_table со столбцами id, firstname, lastname.
🔸 to_table со столбцами id, name
Задача: перенести все строки из from_table в to_table, соединив firstname и lastname в одно поле name. После выполнения запроса from_table должен стать пустым.
Попробуйте выполнить задачу ОДНИМ запросом.
Онлайн Postgres: pgplayground
Исходный код для экспериментов:
CREATE TABLE from_table(id int, firstname text, lastname text);
INSERT INTO from_table VALUES(1, 'F1', 'L1');
CREATE TABLE to_table(id int, name text);
✨ РЕШЕНИЕ ✨
Вспоминаем, что DELETE возвращает удалённые строки. Формируем из них СТЕ и передаём в INSERT:
WITH deleted_rows AS (
DELETE FROM from_table
RETURNING id, firstname || ' ' || lastname
)
INSERT INTO to_table
SELECT * FROM deleted_rows;