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 при создании объектов. Здесь часто возникает путаница с билдером, и непонятно, что когда использовать. Этот вопрос я разберу отдельно в следующем посте🔥
🔥134👍65❤21👎6
Что лучше — 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 обычно не даёт преимуществ в ситуациях выше, и редко используется для создания объектов. Его сильная сторона — логические блоки из нескольких операций🔥
🔥120👍56❤11👎7
Паттерн 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 делает код проще и удобнее🔥
👍152🔥75❤23👎2
👩🏫Чему поучиться
Посты это прекрасный, но очень сжатый формат. Для тех, кто хочет большего:
1️⃣ Бот для подготовки к собеседованиям и прокачки навыков
Java, Spring, Kafka, Postgres, микросервисы и всё, что требует рынок от Middle/Senior разработчика
2️⃣ Курс Основы многопоточности
Бесплатный!
Его задача — пройтись по базовым темам многопоточки и заполнить пробелы, если они есть:
▫️ Класс Thread
▫️ Экзекьюторы
▫️ Основные проблемы в многопоточной среде
▫️ Модификатор volatile
▫️ Ключевое слово synchronized
Темы не раскрыты даже на 50%, многие вещи сильно упрощены. Но чтобы понять базу и пройти несложные собеседования — самое то.
3️⃣ Курс Многопоточное программирование на Java
Самый полный и практичный курс по java.util.concurrent и смежным темам🔥
Разбираемся, как писать эффективный и быстрый код и готовимся к собеседованиям. Изучаем best practices и популярные ошибки.
Самая мощная часть — это практика:
✅ Решаем типичные энтерпрайзные задачи
✅ Анализируем многопоточный код Spring, Kafka, Hadoop и других популярных фреймворков — смотрим интересные приёмы и ищем ошибки
✅ Сравниваем разные решения и оцениваем производительность
Такого вы не найдёте нигде. Курс не пересказывает документацию и Concurrency in practice. Он о том, как применять многопоточку в реальной жизни.
Курс нацелен на самостоятельное прохождение, для всех заданий есть подсказки, инструкции для самопроверки и разбор важных нюансов. Я вела курс с обратной связью 3 года и прекрасно понимаю, где возникают сложности.
Если что-то совсем непонятно, можно у меня спросить:)
Что ещё:
▫️ Прохождение занимает 2-3 месяца
▫️ Доступ сразу после оплаты
▫️ Есть рассрочка и оплата за счёт компании
▫️ Можно оформить налоговый вычет и вернуть в следующем году 13% стоимости
Почитать программу, отзывы и записаться → https://fillthegaps.ru/mt
Посты это прекрасный, но очень сжатый формат. Для тех, кто хочет большего:
1️⃣ Бот для подготовки к собеседованиям и прокачки навыков
Java, Spring, Kafka, Postgres, микросервисы и всё, что требует рынок от Middle/Senior разработчика
2️⃣ Курс Основы многопоточности
Бесплатный!
Его задача — пройтись по базовым темам многопоточки и заполнить пробелы, если они есть:
▫️ Класс Thread
▫️ Экзекьюторы
▫️ Основные проблемы в многопоточной среде
▫️ Модификатор volatile
▫️ Ключевое слово synchronized
Темы не раскрыты даже на 50%, многие вещи сильно упрощены. Но чтобы понять базу и пройти несложные собеседования — самое то.
3️⃣ Курс Многопоточное программирование на Java
Самый полный и практичный курс по java.util.concurrent и смежным темам🔥
Разбираемся, как писать эффективный и быстрый код и готовимся к собеседованиям. Изучаем best practices и популярные ошибки.
Самая мощная часть — это практика:
✅ Решаем типичные энтерпрайзные задачи
✅ Анализируем многопоточный код Spring, Kafka, Hadoop и других популярных фреймворков — смотрим интересные приёмы и ищем ошибки
✅ Сравниваем разные решения и оцениваем производительность
Такого вы не найдёте нигде. Курс не пересказывает документацию и Concurrency in practice. Он о том, как применять многопоточку в реальной жизни.
Курс нацелен на самостоятельное прохождение, для всех заданий есть подсказки, инструкции для самопроверки и разбор важных нюансов. Я вела курс с обратной связью 3 года и прекрасно понимаю, где возникают сложности.
Если что-то совсем непонятно, можно у меня спросить:)
Что ещё:
▫️ Прохождение занимает 2-3 месяца
▫️ Доступ сразу после оплаты
▫️ Есть рассрочка и оплата за счёт компании
▫️ Можно оформить налоговый вычет и вернуть в следующем году 13% стоимости
Почитать программу, отзывы и записаться → https://fillthegaps.ru/mt
🔥50👍30❤16
На основе чего по умолчанию считается хэшкод объекта?
Anonymous Poll
22%
Случайное число от генератора внутри JVM
3%
Случайное число от операционной системы
64%
Адрес объекта в памяти
5%
Внутренние переменные потока
3%
Время создания объекта
2%
Последовательность по возрастанию
👍26❤5
Как считается хэшкод по умолчанию?
На собеседованиях часто обсуждают методы
Если хочется посмотреть, как думает человек за пределами стандартного ответа, возможен такой диалог:
— Как считается хэшкод по умолчанию?
— Это адрес объекта в памяти
— А почему так?
— Адрес каждого объекта уникален, то что надо для хэшкода
— Сборщик мусора перемещает объекты внутри памяти. Как это влияет на значения хэшей?
— 😥
Тут можно предположить, что хэшкод считается один раз и приписывается к самому объекту. Это будет логичная мысль.
А вот вычисление хэша на основе адреса в памяти — популярный миф. В этом посте разберём, как на самом считается хэшкод под умолчанию.
В разных 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 стратегий вычисления хэшкода. По умолчанию используется генератор случайных чисел в рамках одного потока
✅ Расчёт на основе адреса памяти не очень хорош по итоговым характеристикам
✅ Общие переменные в методах снижают производительность при интенсивном использовании. Яркие примеры — стратегии хэшкода с общим генератором и последовательностью
🔥220👍81❤27👎4
В каких методах НЕправильно используется Optional?
Anonymous Poll
6%
Optional<Student> get(long id)
66%
Optional<List<Student>> all()
65%
List<Student> search(Optional<String> city)
62%
void init(Optional<String> conf)
10%
Optional<String> getAddress()
👍11❤2
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()🔥164👍103👎19❤16
Во что компилируются record?
Anonymous Poll
7%
В специальную структуру внутри JVM
16%
В класс-наследник от Record
5%
В массив, размер которого равен количеству полей
11%
В экземпляр класса Record
61%
В final класс
👍15❤4🔥4
Новая фича 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, почитать можно тут
А как вам новый синтаксис в рекордах?
🔥 - огонь, очень полезно
👍 - не вау, но пусть будет
❤️ - просто спасибо за пост:)
❤255👍145🔥58👎11
Как называется соединение строк с подстановкой переменных вроде $x plus $y equals ${x + y}?
Anonymous Poll
21%
Интернирование
28%
Конкатенация
6%
Экстраполяция
9%
Интрополяция
36%
Интерполяция
👍15❤4🔥4
Апдейт по фичам 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 версия. И только сейчас разработчики поняли, что фича так себе. Косяки бывают на всех проектах, это нормально:)
🔥89👍43❤13👎7
IDEA: шорткаты для сверхзвуковой навигации по коду
🚀 Посмотреть список методов:
Быстро найти нужный метод или узнать, что вообще умеет класс
🚀 Найти класс или файл в проекте:
Откроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению:
Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов
🚀 Найти строку по номеру:
Очень удобные шорткаты, обязательно попробуйте🔥
🚀 Посмотреть список методов:
Ctrl + F12Быстро найти нужный метод или узнать, что вообще умеет класс
🚀 Найти класс или файл в проекте:
Shift-ShiftОткроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению:
Ctrl + BДля переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
Ctrl + Alt + ⬅️
Ctrl + Alt + ➡️
IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов
🚀 Найти строку по номеру:
Ctrl + G
Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строкуОчень удобные шорткаты, обязательно попробуйте🔥
🔥218👍59❤21👎9
Полезное в 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 строк в базе!
Приёмы выше хоть и простые, но пригодятся на большинстве проектов. Пользуйтесь и ставьте огонёчек, если нужна вторая часть🔥
🔥665👍51❤26👎7
Полезное в 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;
🔥306👍35❤28
REST API
— неоднозначная тема, по которой часто много вопросов и споров. В этом посте кратко расскажу, что такое REST API и как он связан с REST. И самое интересное — покажу принципы REST API на реальных примерах.
Начнём сначала.
REST — это набор архитектурных принципов, которые описал Roy Fielding в диссертации 2000 года. Один из тезисов гласит, что взаимодействие между системами должно крутиться вокруг понятия ресурса.
REST API, в свою очередь, это набор рекомендаций для API: как составить URL-ы, что они принимают и возвращают. От REST здесь берётся только понятие ресурса. Остальное — частные интерпретации, в оригинальном документе ничего этого нет.
Под REST API обычно понимают следующий набор правил:
1️⃣ В основе пути — существительное во множественном числе
❌ https://www.youtube.com/watch?v=123
✅ https://rutube.ru/video/123
Множественное число встречается чаще, но на мой взгляд единственное тоже ок. Главное, чтобы в рамках проекта был единый стиль, и не смешивалось единственное и множественное
2️⃣ Иерархия ресурсов отражается в URL
Например, в магазине одежды есть раздел с футболками. Как это отразить в API:
❌ https://www.lamoda.ru/c/2478/clothes-futbolki/
✅ https://www.sportmaster.ru/catalog/zhenskaya_odezhda/futbolki/
3️⃣ Желаемые действия с ресурсом определяются через HTTP методы
Искусственный пример:
▫️ GET /users — вернуть список всех пользователей
▫️ GET /users/5 — получить пользователя с id=5
▫️ POST /users — добавить пользователя
▫️ PUT /users/5 — обновить пользователя с id=5 целиком
▫️ PATCH /users/5 — обновить часть полей у пользователя с id=5
Найти идеальный реальный пример у меня не получилось, но очень близок оказался HeadHunter API по работе с резюме. Там всё по канону, но для редактирования они используют PUT.
4️⃣ Вызов методов GET, DELETE, PUT, PATCH должен быть идемпотентным
Чтобы без проблем вызвать метод повторно, если что-то пошло не так
5️⃣ Методы PUT, POST and PATCH возвращают новый/обновлённый объект
6️⃣ Дополнительные параметры для метода GET указываются через ?, а тело метода остаётся пустым
GET /resumes?text=java&age_from=18 – поиск совершеннолетних кандидатов, в резюме которых встречается "java"
7️⃣ Информация для POST, PUT, PATCH запросов передаётся в теле метода
8️⃣ В качестве ответа возвращается соответствующий HTTP код
▫️ 1хх: информационные сообщения, чаще всего служебные. Для бизнес-логики не используются
▫️ 2xx: всё супер
▫️ 3xx: redirect
▫️ 4xx: ошибка на стороне клиента
▫️ 5xx: ошибка на стороне сервера
Какие плюсы у REST API?
Плюс на самом деле только один — в таком API проще разобраться. У Stepik REST API нет документации, но и так понятно, что GET /api/courses возвращает список курсов.
Как видно по примерам выше, некоторые компании придерживаются подобных правил, некоторые — нет. Rutube более REST API, чем Youtube, но для успеха продукта этого явно недостаточно🤭
— неоднозначная тема, по которой часто много вопросов и споров. В этом посте кратко расскажу, что такое REST API и как он связан с REST. И самое интересное — покажу принципы REST API на реальных примерах.
Начнём сначала.
REST — это набор архитектурных принципов, которые описал Roy Fielding в диссертации 2000 года. Один из тезисов гласит, что взаимодействие между системами должно крутиться вокруг понятия ресурса.
REST API, в свою очередь, это набор рекомендаций для API: как составить URL-ы, что они принимают и возвращают. От REST здесь берётся только понятие ресурса. Остальное — частные интерпретации, в оригинальном документе ничего этого нет.
Под REST API обычно понимают следующий набор правил:
1️⃣ В основе пути — существительное во множественном числе
❌ https://www.youtube.com/watch?v=123
✅ https://rutube.ru/video/123
Множественное число встречается чаще, но на мой взгляд единственное тоже ок. Главное, чтобы в рамках проекта был единый стиль, и не смешивалось единственное и множественное
2️⃣ Иерархия ресурсов отражается в URL
Например, в магазине одежды есть раздел с футболками. Как это отразить в API:
❌ https://www.lamoda.ru/c/2478/clothes-futbolki/
✅ https://www.sportmaster.ru/catalog/zhenskaya_odezhda/futbolki/
3️⃣ Желаемые действия с ресурсом определяются через HTTP методы
Искусственный пример:
▫️ GET /users — вернуть список всех пользователей
▫️ GET /users/5 — получить пользователя с id=5
▫️ POST /users — добавить пользователя
▫️ PUT /users/5 — обновить пользователя с id=5 целиком
▫️ PATCH /users/5 — обновить часть полей у пользователя с id=5
Найти идеальный реальный пример у меня не получилось, но очень близок оказался HeadHunter API по работе с резюме. Там всё по канону, но для редактирования они используют PUT.
4️⃣ Вызов методов GET, DELETE, PUT, PATCH должен быть идемпотентным
Чтобы без проблем вызвать метод повторно, если что-то пошло не так
5️⃣ Методы PUT, POST and PATCH возвращают новый/обновлённый объект
6️⃣ Дополнительные параметры для метода GET указываются через ?, а тело метода остаётся пустым
GET /resumes?text=java&age_from=18 – поиск совершеннолетних кандидатов, в резюме которых встречается "java"
7️⃣ Информация для POST, PUT, PATCH запросов передаётся в теле метода
8️⃣ В качестве ответа возвращается соответствующий HTTP код
▫️ 1хх: информационные сообщения, чаще всего служебные. Для бизнес-логики не используются
▫️ 2xx: всё супер
▫️ 3xx: redirect
▫️ 4xx: ошибка на стороне клиента
▫️ 5xx: ошибка на стороне сервера
Какие плюсы у REST API?
Плюс на самом деле только один — в таком API проще разобраться. У Stepik REST API нет документации, но и так понятно, что GET /api/courses возвращает список курсов.
Как видно по примерам выше, некоторые компании придерживаются подобных правил, некоторые — нет. Rutube более REST API, чем Youtube, но для успеха продукта этого явно недостаточно🤭
👍185🔥50❤34
Как отсортировать список ArrayList<Integer> list?
Anonymous Poll
45%
list.sort()
38%
list.sort(Comparator.naturalOrder())
17%
list.stream().sorted().toList()
47%
list = list.stream().sorted().toList()
🔥14👍7
Костыль в JDK и сортировка списков
Вести абстрактные разговоры о разработке легко и приятно. Можно два часа рассуждать, что такое хорошее API, но гораздо полезнее обсудить конкретные примеры. Сегодня разберём метод сортировки.
Если показать вопрос перед постом питонисту, он однозначно выберет list.sort(). Хотя бы потому что в питоне есть такой метод.
Класс Integer реализует интерфейс Comparable, сортировка чисел — базовая функциональность любого языка программирования. Так что метод sort() максимально логичен.
Однако в интерфейсе List нет такого метода, только
Для элементарной операции сортировки чисел приходится писать
Код с Comparator.naturalOrder() похож на какой-то костыль. Под капотом не происходит ничего особенного, реализация компаратора очень простая:
❓ Так зачем писать так сложно? Почему в интерфейсе List нет метода sort()?
Сейчас расскажу:)
Java создавался как язык для больших и долгоживущих приложений, и его основные ценности — стабильность и обратная совместимость.
C начала 2000-х в JDK есть метод Collections.sort(List). Статический метод, который меняет внутреннее состояние аргумента. Сейчас это порицается, но в те времена было норм.
В больших компаниях классы JDK часто расширяли удобными методами, в том числе сортировкой в функциональном стиле:
Спустя много лет стало понятно, что экземплярные методы сортировки — это классно, и надо добавить такой метод в JDK. Чтобы текущие реализации списков не сломались, это должен быть дефолтный метод в интерфейсе List.
Но есть проблема. Допустим, на проекте есть такой класс:
Допустим, в java 8 в интерфейс List добавили бы метод
Старый метод не может переопределить дефолтный. тк возвращаемые значения не совместимы. Поэтому проекты, которые определили свой функциональный sort в начале 2000-х, перестанут компилироваться. Пользователи будут недовольны😡
Многие проекты полагаются на свой sort, поэтому разработчики JDK не стали добавлять его в интерфейс. Метод sort(Comparator) использовался редко, поэтому теперь он с нами.
У Stream API нет проблем с совместимостью, так что для стримов есть прекрасный метод sorted(). Для коллекций метод sorted() есть в Kotlin💖
(обратите внимание на суффикс -ed, всё по правилам функционального подхода)
Ответ на вопрос перед постом: отсортировать список можно так:
Если вам понравился list.sort(), значит у вас хороший вкус на API. К сожалению, у java свои загоны, поэтому этого метода в JDK нет.
Вести абстрактные разговоры о разработке легко и приятно. Можно два часа рассуждать, что такое хорошее API, но гораздо полезнее обсудить конкретные примеры. Сегодня разберём метод сортировки.
Если показать вопрос перед постом питонисту, он однозначно выберет list.sort(). Хотя бы потому что в питоне есть такой метод.
Класс Integer реализует интерфейс Comparable, сортировка чисел — базовая функциональность любого языка программирования. Так что метод sort() максимально логичен.
Однако в интерфейсе List нет такого метода, только
void sort(Comparator<?super Е> c) {…}Для элементарной операции сортировки чисел приходится писать
list.sort(Comparator.naturalOrder())Код с Comparator.naturalOrder() похож на какой-то костыль. Под капотом не происходит ничего особенного, реализация компаратора очень простая:
(с1, с2) -> c1.compareTo(c2)❓ Так зачем писать так сложно? Почему в интерфейсе List нет метода sort()?
Сейчас расскажу:)
Java создавался как язык для больших и долгоживущих приложений, и его основные ценности — стабильность и обратная совместимость.
C начала 2000-х в JDK есть метод Collections.sort(List). Статический метод, который меняет внутреннее состояние аргумента. Сейчас это порицается, но в те времена было норм.
В больших компаниях классы JDK часто расширяли удобными методами, в том числе сортировкой в функциональном стиле:
CustomList sorted = list.sort();Спустя много лет стало понятно, что экземплярные методы сортировки — это классно, и надо добавить такой метод в JDK. Чтобы текущие реализации списков не сломались, это должен быть дефолтный метод в интерфейсе List.
Но есть проблема. Допустим, на проекте есть такой класс:
public class CustomList implements List {
public CustomList sort() {…}
}Допустим, в java 8 в интерфейс List добавили бы метод
default void sort() {…}Старый метод не может переопределить дефолтный. тк возвращаемые значения не совместимы. Поэтому проекты, которые определили свой функциональный sort в начале 2000-х, перестанут компилироваться. Пользователи будут недовольны😡
Многие проекты полагаются на свой sort, поэтому разработчики JDK не стали добавлять его в интерфейс. Метод sort(Comparator) использовался редко, поэтому теперь он с нами.
У Stream API нет проблем с совместимостью, так что для стримов есть прекрасный метод sorted(). Для коллекций метод sorted() есть в Kotlin💖
(обратите внимание на суффикс -ed, всё по правилам функционального подхода)
Ответ на вопрос перед постом: отсортировать список можно так:
✅ list.sort(Comparator.naturalOrder());
✅ list = list.stream().sorted().toList();Если вам понравился list.sort(), значит у вас хороший вкус на API. К сожалению, у java свои загоны, поэтому этого метода в JDK нет.
🔥192👍71❤15👎7
В классе Order есть поле sum (сумма заказа). Мы хотим отсортировать заказы от большей суммы к меньшей. Что написать внутри метода orders.sort(…)?
Anonymous Poll
34%
(o1, o2) -> (int) (o1.getSum() - o2.getSum())
66%
(o1, o2) -> (int) (o2.getSum() - o1.getSum())
👍27👎5❤3
Как написать компаратор
Продолжим тему прошлого поста про сортировки и функциональный подход. Итак, компаратор задаёт правило сравнения элементов между собой. Делает он это с помощью метода compare:
Если метод вернул
▫️ число больше нуля — первый элемент больше второго
▫️ 0 — элементы равны
▫️ число меньше нуля — первый меньше второго
Простейшая и популярная реализация — вычесть одно значение из другого:
❓ Что с этим не так?
Я всегда сомневаюсь, что из чего вычитать. Если вы отвечали на опрос дольше одной секунды, значит мы в одном лагере:) Компаратор — совсем не то место, где мозг должен спотыкаться.
В Java 8 в интерфейсе Comparator появился удобный метод:
Что классно:
✅ Не надо вспоминать, что из чего вычитать
✅ Легко сделать сравнение в обратном порядке:
✅ Можно учесть null:
✅ Удобно сортировать по нескольким полям:
Самостоятельно обрабатывать null и писать сложные сортировки очень утомительно. Помню, как с удовольствием удаляла из проекта компараторы на 20 строк после перехода на Java 8😊
Важные нюансы:
1️⃣ comparing*
В интерфейсе Comparator также доступны методы comparingInt, comparingLong и comparingDouble. Используются для полей примитивного типа, чтобы избежать лишнего боксинга. Если в классе Order
Long id → используем comparing(Order::getId)
long id → comparingLong(Order::getId)
Не указывайте тип лишний раз. Для работы с объектами подойдёт обычный comparing
2️⃣ Нетривиальная работа с null*
В обычных методах легко понять, что происходит:
comparing(A).reversed().thenComparing(Б)
=
отсортировать по полю А в обратном порядке, дубликаты отсортировать по Б
Методы null* выбиваются из этой схемы.
означает, что первыми будут null объекты, а существующие заказы отсортируются по сумме. Этот компаратор работает для такого кода:
Если в списке нет null объектов, но в поле sum возможен null, придётся писать так:
Сравнение по нескольким nullable полям выглядит совсем плохо. К счастью, на практике такие задачи встречаются редко.
Ответ на вопрос перед постом:
Но лучше использовать
Продолжим тему прошлого поста про сортировки и функциональный подход. Итак, компаратор задаёт правило сравнения элементов между собой. Делает он это с помощью метода compare:
public int compare(T o1, T o2) {…}Если метод вернул
▫️ число больше нуля — первый элемент больше второго
▫️ 0 — элементы равны
▫️ число меньше нуля — первый меньше второго
Простейшая и популярная реализация — вычесть одно значение из другого:
(o1, o2) -> (int) (o1.getSum() - o2.getSum())❓ Что с этим не так?
Я всегда сомневаюсь, что из чего вычитать. Если вы отвечали на опрос дольше одной секунды, значит мы в одном лагере:) Компаратор — совсем не то место, где мозг должен спотыкаться.
В Java 8 в интерфейсе Comparator появился удобный метод:
orders.sort(comparing(Order::getSum))Что классно:
✅ Не надо вспоминать, что из чего вычитать
✅ Легко сделать сравнение в обратном порядке:
comparing(Order::getSum).reversed()✅ Можно учесть null:
nullsFirst(comparing(Order::getSum))
nullLast(…)✅ Удобно сортировать по нескольким полям:
comparing(Order::getSum).thenComparing(Order::getId)Самостоятельно обрабатывать null и писать сложные сортировки очень утомительно. Помню, как с удовольствием удаляла из проекта компараторы на 20 строк после перехода на Java 8😊
Важные нюансы:
1️⃣ comparing*
В интерфейсе Comparator также доступны методы comparingInt, comparingLong и comparingDouble. Используются для полей примитивного типа, чтобы избежать лишнего боксинга. Если в классе Order
Long id → используем comparing(Order::getId)
long id → comparingLong(Order::getId)
Не указывайте тип лишний раз. Для работы с объектами подойдёт обычный comparing
2️⃣ Нетривиальная работа с null*
В обычных методах легко понять, что происходит:
comparing(A).reversed().thenComparing(Б)
=
отсортировать по полю А в обратном порядке, дубликаты отсортировать по Б
Методы null* выбиваются из этой схемы.
nullsFirst(comparing(Order::getSum))означает, что первыми будут null объекты, а существующие заказы отсортируются по сумме. Этот компаратор работает для такого кода:
orders.add(null); // эти элементы будут впередиorders.add(new Order(…)); // эти отсортируются по полю sum Если в списке нет null объектов, но в поле sum возможен null, придётся писать так:
…comparing(Order::getSum, nullsFirst(naturalOrder()));Сравнение по нескольким nullable полям выглядит совсем плохо. К счастью, на практике такие задачи встречаются редко.
Ответ на вопрос перед постом:
(o1, o2) -> (int) (o2.getSum() - o1.getSum())Но лучше использовать
comparing(Order::getSum).reversed() ✨🔥160👍60❤22👎3