Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
نمایش بیشتر📈 تحلیل کانال تلگرام Java: fill the gaps
کانال Java: fill the gaps (@java_fillthegaps) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 12 399 مشترک است و جایگاه 9 827 را در دسته فناوری و برنامهها و رتبه 51 938 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 12 399 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 26 اوت, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -57 و در ۲۴ ساعت گذشته برابر -1 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 0% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً N/A% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 0 بازدید دریافت میکند. در اولین روز معمولاً 0 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 0 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند redis, hashmap, linkedhashmap, индекс, фича تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 27 اوت, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
Objects.hashCode(Object)
Objects.hash(Object…)
Человек, который видит их в первый раз, обязательно спросит
🤔 Чем отличается hash и hashCode?
🤔 Почему они выдают разные результаты?
(ответ на вопрос перед постом: hash3 отличается от остальных хэшей)
В хорошем апи у пользователя минимум вопросов, как им пользоваться. В идеале даже смотреть документацию на нужно. Это, наверное, самый главный принцип проектирования.
В нашем случае приходится лезть в доку и исходный код👎
Что увидим внутри:
▫️ hashCode принимает 1 аргумент - Object, и возвращает его хэш
▫️ hash принимает массив объектов переменной длины, итоговый хэш считается на основе переданных полей. Чтобы учесть порядок, hash производит дополнительные вычисления, даже для одного поля. Поэтому итоговые хэши отличаются.
Супер, я разобралась, но зачем мне в это погружаться? Я хочу просто посчитать хэш объекта, а не вникать в разницу 2 методов. Тратить даже 5 минут на такую простейшую задачу отвратительно. Bad user experience во всей красе.
В случае хэшей исправить ситуацию элементарно, просто дать методам одинаковые имена:
Objects.hashCode(Object)
Objects.hashCode(Object…)
Если передать один обьект, java не запутается и вызовет нужный метод. Плюсы очевидны:
✅ Не надо думать, какой метод вызвать
✅ Хэшкод обьекта будет одинаков в любом месте кода
Почему так не сделано изначально?
Я пыталась найти ответ, смотрела старые исходники и доки. Не нашла ни одной причины, почему имена разные и результаты не согласованы.
Видимо автор решил, что и так сойдёт.
Оставим это на совести разработчиков JDK, а себе заберём следующие выводы:
▫️ В хорошем апи пользователю не нужно смотреть документацию, чтобы сделать базовые вещи
▫️ Если в апи есть неочевидные моменты, их надо поправить, а не писать в доке warning
▫️ Если пользователь пошел смотреть исходники, чтобы разобраться - это полный провал
Чем сложнее система, тем важнее вкладываться в простоту. Простоту понимания, чтения, поддержки. Даже собственный код через пару месяцев выглядит как чужой. Понятные названия переменных, классов и методов, непротиворечивость - база для комфортной работы❤️
Это уже не первый пост, где я разбираю неудачные методы в JDK. Если вам хочется больше, предлагаю почитать
🤦 Критикую метод HashMap из java 20
🤦 Критикую методы StreamAPI в java 22
🤦 Критикую методы BigDecimal-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
Работает пока только для Linux, иногда показывает небольшой прирост скорости. Так что имеет смысл попробовать!
В GraalVM есть ещё одно интересное направление, и о нем расскажу в следующем посте🔥Курс великолепный, не пожалел ни одного рубля, что потратил. Это уникальный курс в своем сегменте, особенно на русском рынке. Всем советую, на курсе вы найдете все ответы на интересующие вас вопросы. Более того из курса вы сможете узнать то, что просто нет в открытом доступе нигде, исключительно авторские наработки. Однозначно советую всем бэкэнд разработчикам, даже если вы не особо используете многопточку - это очень поможет вам в понимании многопоточного кода фреймворков и вообще сильно улучшит ваш кругозор.Так что с многопоточкой всё давно решено, все ссылки в закрепе. Остальные ответы ещё разгребаю, буду писать посты по мере сил. Ещё раз спасибо, очень ценю вашу помощь❤️
SELECT * FROM t ORDER BY …
LIMIT 100
Для второй и последующих добавляем offset:
SELECT * FROM t ORDER BY …
LIMIT 100 OFFSET 100
Offset показывает, сколько строк пропустить.
В чем подвох: кажется, что при offset 100 постгрес сразу прыгнет на 101 строку, но это не так. Он честно пройдет 100 строк, и потом отдаст следующие 100.
❌ В случае большого оффсета такая операция будет работать долго даже при наличии индекса
✅ Просто реализовать. Минимум действий со стороны клиента и поддержка в фреймворках.
🌸 Keyset пагинация. Она же cursor, она же seek.
Здесь мы используем where в паре с индексом и сразу прыгаем на нужную позицию. Первую страницу получаем так:
SELECT * FROM t ORDER BY name
LIMIT 10
Если записи отсортированы по имени, запоминаем имя последнего элемента. Пусть это будет х. Чтобы получить вторую страницу, добавляем where:
SELECT * FROM t ORDER BY name
WHERE name > x
LIMIT 10
✅ Работает быстрее, чем вариант с оффсетом. Чем больше оффсет, тем значительнее разница
❌ Сложнее в реализации, надо запоминать поле в последней пачке
❌ Нельзя перейти к произвольной странице, только последовательная выдача данных
Когда что использовать?
Во многих докладах/статьях часто встречается мысль, что оффсет это ужас, и надо всегда использовать кейсет. На практике все не так категорично:
▫️ Если на сайте infinite scrolling или пользователь вряд ли будет много смотреть, вполне ок использовать простую оффсет пагинацию.
▫️ Если данные выкачиваются большими пачками для обработки, аналитики, миграции и тд, берём keyset пагинацию.
▫️ Если надо выгрузить много данных и прыгать по ним туда-сюда или у вас другой сложный случай, придется экспериментировать с индексами и запросами. Этот доклад хорошо показывает, что пагинация - далеко не тривиальная задача.
А поставить огонек качественному контенту очень просто, поэтому это нужно обязательно сделать🔥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() ✨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 нет.