Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
إظهار المزيد📈 نظرة تحليلية على قناة تيليجرام Java: fill the gaps
تُعد قناة Java: fill the gaps (@java_fillthegaps) في القطاع اللغوي الروسية لاعباً نشطاً. يضم المجتمع حالياً 12 548 مشتركاً، محتلاً المرتبة 10 113 في فئة التكنولوجيات والتطبيقات والمرتبة 52 819 في منطقة روسيا.
📊 مؤشرات الجمهور والحراك
منذ تأسيسه في невідомо، حقق المشروع نمواً سريعاً وجمع 12 548 مشتركاً.
بحسب آخر البيانات بتاريخ 08 يونيو, 2026، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -43، وفي آخر 24 ساعة بمقدار -3، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 34.73%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً N/A% من ردود الفعل نسبةً إلى إجمالي المشتركين.
- وصول المنشورات: يحصل كل منشور على متوسط 0 مشاهدة. وخلال اليوم الأول يجمع عادةً 0 مشاهدة.
- التفاعلات والاستجابة: يتفاعل الجمهور بانتظام؛ متوسط التفاعلات لكل منشور يبلغ 0.
- الاهتمامات الموضوعية: يركز المحتوى على مواضيع رئيسية مثل redis, hashmap, linkedhashmap, индекс, фича.
📝 الوصف وسياسة المحتوى
يصف المؤلف القناة بأنها مساحة للتعبير عن الآراء الذاتية:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
بفضل وتيرة التحديث المرتفعة (أحدث البيانات بتاريخ 09 يونيو, 2026) تحافظ القناة على حداثتها ومستوى وصول مرتفع. وتُظهر التحليلات تفاعلاً نشطاً من الجمهور، ما يجعلها نقطة تأثير مهمة ضمن فئة التكنولوجيات والتطبيقات.
peek() был единственным способом посмотреть элементы внутри стрима:
list.stream().filter(..)
.peek(e->println("filt:"+e))
.map(..)
.peek(e->println("map:"+e))
...
Недавно в Intellij IDEA вышел потрясающий апдейт, который выводит дебаг стримов на новый уровень:Stream.iterate(T, BinaryOperator)
🔸 Задать первый элемент, правило вывода последующего и условие остановки: Stream.iterate(T, Predicate, BinaryOperator) (java 9)
🔸 Генерировать независимые друг от друга элементы: Stream.generate(Supplier)
🔸 Из диапазона с включением граничных значений: IntStream.range(..)
🔸 Диапазон без граничных значений: IntStream.rangeClosed(..)
🔸 Из строк: BufferedReader.lines()
🔸 Из символов строки: CharSequence.chars()
Названия большинства методов преобразований говорят сами за себя: filter, distinct, limit, sorted. Есть менее понятные:
🔹 map: применить функцию к каждому элементу.
🔹 flatmap: положить элементы из "списка списков" в единый стрим. Также с его помощью можно уменьшить размерность массива.
🔹 takeWhile(Predicate): брать элементы из стрима, пока выполняется условие. Метод доступен c java 9.
🔹 dropWhile(Predicate): пропускать элементы, пока условие не нарушится. Метод доступен c java 9.
🔹 peek(Consumer): сделать что-то с каждым элементом стрима, не меняя исходный стрим. Часто используется при дебаге, например, вывести в консоль текущие элементы.
Вычисления выполняются не сразу, а только когда вызывается конечная операция. Конечная операция описывает желаемый результат. Результатом может быть:
◾️ Структура данных: toArray, collect.
◾️ Результат поиска: anyMatch, allMatch, nonMatch, findFirst, findAny.
◾️ Результат агрегации: min, max, count, reduce.
◾️ Сайд-эффект: forEach, forEachOrdered.
Стрим - не структура хранения данных. Он обходит источник данных (или сам его генерирует) и сохраняет промежуточные результаты. По этой причине нельзя копировать стримы и исполнять их несколько раз - пришлось бы контролировать этапы исполнения, консистентность данных и добавить синхронизацию для работы в многопоточной среде. При попытке переиспользования стрима выбрасывается IllegalStateException.
✅ Исходные данные не меняются.
✅ Стрим нельзя переиспользовать.
✅ Пока не вызвана конечная операция, стрим можно менять как угодно.
Пример из опроса выше вернёт 1.
list.remove(0) удаляет элемент с индексом 0, поэтому на момент старта вычислений stream.count() в источнике данных остаётся всего один элемент.
Если Вас раздражает, что метод list.remove принимает не сам элемент, а индекс, то некоторые методы Stream API Вас тоже разочаруют. Например, такой код компилируется:
Stream.of(-1, 0, 1).max(Math::max).get();Но в результате получается -1, потому что входной параметр метода
max - Comparator. Math.max по сигнатуре на него похож, поэтому получается неверный результат😒List list=new ArrayList();
list.add(1);
list.add(2);
Stream stream=list.stream();
list.remove(0);
println(stream.count());public void add(List list, Integer value)
{ list.add(value); }
✅ Минимальный расход памяти.
❌ Для запуска в многопоточной среде нужна синхронизация.
❌ Если метод прервался, то нужно выяснять, что он успел изменить, а что нет.
Подход ФП: создаём новый список на основе исходного и добавляем туда элемент.
public List add(List list, Integer value {
List newList=new List();
newList.addAll(list);
newList.add(value);
return list;
}
✅ Можно использовать в многопоточной среде.
✅ Если что-то пошло не так, просто ещё раз вызываем метод.
❌ Большой расход памяти.
3️⃣ Декларативный стиль написания кода.
Обход коллекции в цикле — это императивный подход. Для каждого элемента задаётся чёткая последовательность действий.
Stream API — декларативный. Благодаря большому количеству встроенных функций можно просто описать то, что нужно: отфильтровать, сгруппировать, просуммировать.
Лучшая читаемость - не единственное преимущество такого подхода. Чистые функции и неизменяемые данные сами по себе тоже не сделают программу эффективной. Но их использование в Stream API дают компилятору и JVM большие возможности для оптимизаций. Cамая мощная из них — опция parallel(), она автоматически распределяет данные по параллельным подзадачам.
Начиная с 8 версии java можно применять и другие идеи ФП — функции высших порядков, currying, pattern matching и т.д. Некоторые подходы были доступны и раньше, например, использовать хвостовую рекурсию вместо итерации. Но наибольшую практическую пользу можно извлечь из обработки больших коллекций с помощью Stream API.
Писать программы в функциональном стиле на чистой java можно, но на практике это пока не оптимально. Для обработки потоков данных больше подходят библиотеки для реактивного программирования и стрим-фреймворки.// todo: refactor
Такие артефакты часто остаются в коде, иногда даже дополняются или переносятся в абстракции.
✅ Если Вы знаете, что пишете костыль — не оборачивайте его в абстракции, не приукрашивайте. Пусть он будет максимально понятным и грубым. Идеально — заведите отдельную задачу и сразу же пропишите, как исправить ситуацию.
Цель этого поста — не показать, что абстракций делать нельзя, а принципы проектирования нужны только для собеседований.
В больших системах over-engineering неизбежен. Однажды она станет тем самым душным легаси, в котором никто не хочет разбираться. В наших силах только замедлить этот процесс. Помните, что главная цель кода — решать бизнес-задачи. А паттерны, принципы и лучшие практики — это только инструменты для достижения этой цели.Object проверяет только равенство ссылок - по умолчанию каждый экземпляр класса равен только самому себе. Часто рекомендуют для новых классов переопределять метод equals и проверять равенство по значимым полям.
Несмотря на кажущуюся простоту, в реализации equals есть несколько тонких моментов. Кода в этом методе мало, выглядит он просто, а тесты на равенство редко кто пишет. Тем не менее часто встречается ситуация:
Странное поведение системы ➡️ неделя дебага по всей бизнес-логике ➡️ ошибка в equals ➡️ гнев и разочарование.
Простой способ не ошибиться — не переопределять equals вообще. Иногда это уместно:
1️⃣ Для перечислений (enum)
2️⃣ Когда важны сами экземпляры, а не данные внутри них. Пример - классы Thread, RecursiveTask.
3️⃣ Сравнение объектов вообще не предполагается. Пример - класс Pattern.
4️⃣ В базовом классе уже есть equals и нам подходит эта реализация.
5️⃣ Класс имеет модификатор доступа private или по умолчанию и мы уверены, что метод equals не будет вызван.
В остальных случаях его лучше переопределить. Во многих проектах при этом используется аннотация @EqualsAndHashCode библиотеки lombok, ограничения при её использовании мы тоже рассмотрим.
Итак, при реализации equals нужно учитывать следующие свойства:
1️⃣ Рефлексивность — экземпляр должен быть равен сам себе.
2️⃣ Консистентность — если экземпляры равны, то без внешних воздействий они должны оставаться равны.
✅ Сравнивать только неизменяемые поля.
Чтобы обезопасить себя от проблем с equals по возможности используйте для ключей в Map не классы целиком, а их неизменяемые поля, например, id.
Если используете аннотацию @EqualsAndHashCode явно указывайте поля, по которым идёт сравнение. Это поможет избежать ошибок при добавлении в класс изменяемого поля.
✅ @EqualsAndHashCode(of={“x”,“y”})
3️⃣ Симметрия.
Если в системе находятся объекты базового класса и его наследника нужно чётко понимать, как они будут между собой сравниваться.
Возьмём класс Точка, где равенство проверяется по координатам.
И его наследника — класс ЦветнаяТочка, где сравниваются координаты и цвет.
Точка обычная=new Точка(1,1);
Точка красная=new ЦветнаяТочка(1,1,RED);
Тогда обычная.equals(красная) будет true, а результат красная.equals(обычная) зависит от требований к системе — нужно ли игнорировать цвет в этом случае или нет.
4️⃣ Транзитивность.
Это свойство тоже легко нарушить, если мы имеем дело с иерархией:
Точка красная=new ЦветнаяТочка(1,1,RED);
Точка обычная=new Точка(1,1);
Точка синяя=new ЦветнаяТочка(1,1,BLUE);
Если мы решили игнорировать цвет при сравнении разных типов, то допустима такая ситуация:
красная.equals(обычная) - true
обычная.equals(синяя) — true
красная.equals(синяя) - true 😥
✅ Для иерархий классов писать юнит-тесты на симметричность и транзитивность.
✅ Не использовать @EqualsAndHashCode для иерархий классов.
🤔 Если в системе используются объекты базового и произвольного типов рассмотрите вариант замены наследования на композицию.
5️⃣ Сравнение существующего объекта с null должно возвращать false.
✅ Добавить отдельную проверку if(o == null)
Итого:
1️⃣ Не переопределяйте equals, если сравнение не предполагается.
2️⃣ Используйте в equals только неизменяемые поля.
3️⃣ Для иерархии классов пишите тесты на симметричность и транзитивность.
4️⃣ Для ключей в Map желательно использовать неизменяемые поля, например, ID.
5️⃣ Не использовать @EqualsAndHashCode для иерархии классов.
6️⃣ При использовании @EqualsAndHashCode явно прописывайте поля.
7️⃣ Кратко проверить два поля на null и равенство можно через Objects.equals(this.a, а)
8️⃣ Проверяйте первыми поля, которые вероятнее всего отличаются.
9️⃣ Не забывайте про hashcode — используйте те же поля, что в методе equals.Runnable action = () -> System.out.println("hey");
new Thread(action).start();
🅱️ расширить класс Thread, переопределить метод run, создать экземпляр этого класса:
class MyThread extends Thread {...}
new MyThread().start();
2️⃣ Углублённый ответ.
new Thread(...) приведёт к созданию в heap экземпляра класса Thread и сопутствующих объектов —program counter, хранилище thread-local переменных, нескольких стеков, объектов синхронизации и т.д. Вызов метода start() вызывает нативный метод start0(). Через JNI исполнится метод JVM JVM_StartThread, реализованный на C++, который вызывает метод ОС для создания потока. Для Unix это pthread_create.
3️⃣ Практический ответ.
Создание потока - ресурсоёмкая операция, поэтому лучше создать пул потоков, которые можно переиспользовать.
ExecutorService executor = Executors.newCachedThreadPool();
Runnable action = () -> System.out.println("hey");
executor.submit(action);
4️⃣ Неожиданный ответ.
Понятие потока в программировании очень размыто, особенно с учётом русского языка. Thread - структура для параллельного выполнения задач с помощью планировщика и потоков ОС.
Поток данных - тоже поток. В случае Stream API это скорее статичная коллекция, но можно рассказать интервьюеру основные методы его создания:
list.stream()
Stream.of(...)
Stream.range(...)
Arrays.stream(...)
Stream.generate(...)
5️⃣ Хайповый ответ.
Раз мы заговорили о потоке как потоке данных, то здесь можно свернуть в тему реактивного программирования. Самые популярные библиотеки для java это RxJava2, Project Reactor и Akka Streams. А также интерфейсы Flow API в java 9 и Reactive Streams.
Для примера рассмотрим создание потока в Reactor.
Поток из 0 или 1 элемента называется Mono:
Mono.just(1)
Mono.fromCallable(...)
Mono.fromFuture(...)
Mono.fromSupplier(...)
Поток из N элементов называется Flux:
Flux.just(1, 2, 3)
Flux.fromIterable(...)
Flux.fromStream(...)
Flux.range(...)
6️⃣ Big data oriented ответ.
В мире big data есть много фреймворков для обработки потоков данных. Apache Spark Streaming, Storm, Flink, Samza, Kafka Streams - если Вы работали с чем-то из этого списка, то обязательно поделитесь с интервьером этим опытом.
7️⃣ Менеджерский ответ.
Рассмотрим поток с нетехнической точки зрения. Поток - состояние, когда человек полностью включен в свою деятельность, совершенно сконцентрирован на ней и испытывает удовольствие от работы.
Задача руководителя - создать сотрудникам подходящие для этого условия. Можно использовать множество методов: регулярная обратная связь, увеличения прозрачности процессов и т.д.
Ответ на банальный вопрос "как создать поток" может различаться в зависимости от опыта и текущей должности. Вопросы на собеседованиях - лишь стартовая точка для обсуждений, не стесняйтесь повернуть разговор на обсуждение Ваших компетенций и сильных сторон.Xloggc:/path/to/gc.log
-XX:+PrintGCDetails
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
Но вообще G1 адаптируется к нагрузке и прекрасно работает со значениями по умолчанию.
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
