es
Feedback
Java: fill the gaps

Java: fill the gaps

Ir al canal en Telegram

Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat

Mostrar más

📈 Análisis del canal de Telegram Java: fill the gaps

El canal Java: fill the gaps (@java_fillthegaps) en el segmento lingüístico de Ruso es un actor destacado. Actualmente la comunidad reúne a 12 552 suscriptores, ocupando la posición 10 101 en la categoría Tecnologías y Aplicaciones y el puesto 52 755 en la región Rusia.

📊 Métricas de audiencia y dinámica

Desde su creación el невідомо, el proyecto ha mostrado un crecimiento acelerado, reuniendo a 12 552 suscriptores.

Según los últimos datos del 05 junio, 2026, el canal mantiene una actividad estable. En los últimos 30 días la variación de miembros fue de -49, y en las últimas 24 horas de -4, conservando un alto alcance.

  • Estado de verificación: No verificado
  • Tasa de interacción (ER): El promedio de interacción de la audiencia es 34.71%. Durante las primeras 24 horas tras publicar, el contenido suele obtener N/A% de reacciones respecto al total de suscriptores.
  • Alcance de las publicaciones: Cada publicación recibe en promedio 0 visualizaciones. En el primer día suele acumular 0 visualizaciones.
  • Reacciones e interacción: La audiencia responde de forma activa: el promedio de reacciones por publicación es 0.
  • Intereses temáticos: El contenido se centra en temas clave como redis, hashmap, linkedhashmap, индекс, фича.

📝 Descripción y política de contenido

El autor describe el recurso como un espacio para expresar opiniones subjetivas:
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat

Gracias a la alta frecuencia de actualizaciones (últimos datos recibidos el 07 junio, 2026), el canal mantiene la vigencia y un amplio alcance. La analítica demuestra que la audiencia interactúa activamente con el contenido, lo que lo convierte en un punto de referencia dentro de la categoría Tecnologías y Aplicaciones.

12 552
Suscriptores
-424 horas
-257 días
-4930 días
Archivo de publicaciones
Статистика адвента, первые итоги На прошлой неделе открылось 7 тем: ❄️ Equals best practices ❄️ Hashcode best practices ❄️ Enums ❄️ Аннотации ❄️ Compact Strings ❄️ String pool ❄️ String deduplication Участников на текущий момент: 1229!!! Состав участников: ▫️ Леди — 52% ▫️ Джентельмены — 48% По грейдам: 🐣 Начинашки — 31% 🐥 Джуниоры — 25% 🦆 Мидлы — 35% 🦅 Сеньоры — 7% ⭐️ Суперзвёзды — 2% Каждый день стабильно заходят около 250 человек❤️ Планирую провести похожие опросы в середине адвента и ближе к концу. Очень интересно, как все значения будут меняться в динамике!

Java core адвент календарь 2023 Адвент-календарь — традиционный в Европе календарь для отслеживания времени до Рождества. Обычно это открытка или коробка с 24 окошками, на каждом из которых написано число от 1 до 24. Каждый день открываете одно окошко, и там лежит маленький подарок. В более широком смысле адвент-календари — это какие-то активности с 1 по 24 декабря. Давно хотела сделать такую штуку по Java тематике, и наконец сделала🥳 ⭐️ Java core advent ⭐️ Что будет: Каждый день открывается новая тема на тему java core. За основу взяла "золотые хиты" канала, и дополнила их новыми вопросами и практическими моментами. Даже если вы знаете все посты наизусть, всё равно найдёте что-то новое. Хорошая возможность заполнить пробелы или закрепить знания java core! Кому будет полезно: 🎄 Junior/Middle java разработчики — быть обязательно 🎄 Сеньоры — если чувствуете пробелы в java core 🎄 Тимлиды — скиньте ссылку младшим коллегам:) Что по датам: ❄️ 1-24 декабря открываются окошки с темами ❄️ 27 декабря всё закрывается Вышло, на мой взгляд, очень круто и полезно. Join! И поделитесь ссылкой с друзьями-джавистами, буду очень рада, если соберётся побольше людей:)

IDEA: замена кода и сто шагов назад (тихо на пальцах) Недавно посмотрела доклад с конференции Devoxx и узнала две полезные штуки для дебага. О них и расскажу в посте. 1️⃣ Откат на предыдущий фрейм У каждого потока есть стек вызовов. Оказывается, по нему можно перемещаться! Чтобы сделать шаг назад, щёлкните в дебаггере область слева от метода. Внизу поста скриншот — рядом с методом должна появиться стрелка 2️⃣ Замена исполняемого кода В дебаге нажать Shift-Shift и ввести Reload Changed Classes или Run → Debugging Actions → Reload Changed Classes Нельзя заменять код в том методе, где остановился дебаггер. В любом другом — можно Фичи отлично работают вместе для простых правок, которые сложно воспроизвести. Например, ошибка воспроизводится редко, на специфичном стенде или при участии других компонентов. План действий такой: 🔸 Зайти в удалённый дебаг, найти ошибку 🔸 Вернуться на пару фреймов назад 🔸 Поправить ошибку, сделать замену класса 🔸 Проверить, что всё ок При этом сервис продолжит работать с исправленным классом, ну разве не красота🥰

Что такое effectively final и что с ним делать Начну с правильного ответа на вопрос выше. В точке Б мы получим предупреждение компилятора: local variables referenced from a lambda expression must be final or effectively final В этом посте обсудим, что означает effectively final, о чём молчит спецификация и как менять переменные внутри лямбд. Про модификатор final всё понятно — он запрещает изменение переменной
final int count = 100;

count всегда будет равен 100. Каждый, кто напишет
count = 200;

будет осуждён компилятором. Для ссылок схема такая же:
final User admin = User.createAdmin();

Ссылка admin всегда будет указывать на объект User с параметрами админа. Никто не может её переприсвоить:
❌ admin = new User(…)

Effectively final называется переменная, значение которой не меняется после инициализации. По сути это тот же final, но без ключевого слова. Чтобы компилятор не ругался, надо выполнить два условия: 1️⃣ Локальная переменная однозначно определена до начала лямбда-выражения Так не скомпилируется:
int x;
if (…) х = 10

Вот так норм:
int x;
if (…) х = 10; else х = 15;

2️⃣ Переменная не меняется внутри лямбды и после неё
int х = 10;
…лямбда…
❌ х = 15

User user = …
…лямбда…
❌ user = userRepository.findByName(…)
✅ user.setTIN(…)

❓ Зачем нужно такое ограничение? JLS 15.27.2 говорит, что ограничение помогает избежать многопоточных проблем: The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems С первого взгляда звучит разумно. Основное применение лямбд — в рамках Stream API. В Stream API есть опция parallel(), которая запускает выполнение в разных потоках. Там и возникнут concurrency problems. Но я не принимаю это объяснение, потому что: 🤔 С каких пор компилятор волнуют многопоточные проблемы? Вся многопоточка отдана под контроль разработчика с начала времён 🤔 Если локальная переменная станет полем класса, то компилятор перестанет ругаться. При этом вероятность concurrency problems увеличится в разы Моя гипотеза: требование final/effectively final связано с особенностями реализации лямбд и ограничением модели памяти. Это технические сложности в JVM и ничего больше. Отсутствие многопоточных проблем, о которых говорится в JLS, это всего лишь следствие, а не причина. ❓ Как же менять переменные внутри лямбд? 1️⃣ Сделать переменную полем класса:
int count;
public void m() {
   list.forEach(v -> count++);
}

Не лучший вариант, переменная доступна теперь другим потокам. Concurrency problems! 2️⃣ Использовать Atomic обёртку Для примитивов:
AtomicInteger count = new AtomicInteger(0);
list.forEach(v -> count.incrementAndGet())

Для ссылок:
AtomicReference<User> user = new AtomicReference<>();
…map(i -> user.set(…))

3️⃣ Использовать массив с одним элементом
int[] res = new int[] {0};
list.forEach(v -> res[0]++);

Популярный вариант, который подходит и для примитивов, и для ссылок. Но мне больше нравится вариант с Atomic:)

Что выведется в консоль в результате работы программы?
Anonymous voting

Что выведется в консоль в результате работы программы?

Boxing и unboxing В java 2 типа сущностей — примитивы и ссылочные типы. К первой группе относятся int, long, boolean и остальные типы с маленькой буквы. В таких переменных хранится само значение. Набор действий с примитивами ограничен, зато вычисления происходят с космической скоростью. К ссылочным типам относится всё остальное: объекты, массивы, интерфейсы и тд. Такие переменные хранят указатель на участок памяти, где находится объект. Объекты занимают больше места, чем примитивы, зато функционал гораздо шире. Работать с коллекциями приятнее, чем с массивами, поэтому в джава сделали костыль workaround для примитивных типов — классы-обёртки (Integer, Long, …) и боксинг/анбоксинг. В целом это удобно, но появляются проблемы: ❌ Неосознанный boxing/unboxing и лишняя трата памяти и времени ❌ NPE в неожиданных местах ❌ Неоднозначная работа с == ❌ Трудности по работе с массивами Что происходит в коде выше? Сигнатура метода asList выглядит так:
List<T> asList(T… a)

Метод ждёт на вход объекты ссылочных типов. В случае stringArr всё ок, передаются 4 ссылки на объект String, и создаётся список с 4 элементами. Во второй части ситуация менее однозначная. Для массивов и коллекций не работает автоматическое приведение типов и боксинг/анбоксинг: ❌ List<Child> не приводится автоматически к List<Parent> ❌ Массив int не приводится автоматически к массиву Integer В метод уже приходит ссылка — ссылка на массив примитивов. JVM всё устраивает, и она создаёт List из ссылок на массив. Получится List<int[]>, в котором будет один элемент — ссылка на {1,2,3}. Массив с числом не сравнить, поэтому ответ на вопрос перед постом: true false ⭐️ Заметка 1: немного смуты здесь вносит var. Если писать целиком
List<Integer> res = Arrays.asList(intArray)

то компилятор сразу укажет на несоответствие типов ⭐️ Заметка 2: сигнатура contains выглядит так:
boolean contains(Object o)

Метод примет что угодно — строку, примитив (здесь выполнится боксинг) или экземпляр StringBuilder. Поэтому ошибок компиляции нет ❓ Как получить нормальный список из массива примитивов?
var intList = Arrays.stream(intArray).boxed().collect(toList());

Закончу на оптимистичной ноте. В рамках Project Valhalla в JVM добавят три новых типа данных: value objects, primitive classes, specialized generics. В двух словах об этом не рассказать, но есть шанс, что через 10 лет код выше будет работать, как ожидается🤭

Что напечатается в консоли после выполнения кода выше?
Anonymous voting

Что напечатается в консоли после выполнения этого кода?

Асинхронность, параллельность, многопоточность Опишу простыми словами разницу между этими и близкими терминами. Многоядерный Относится к процессору. У процессора 4-16 ядер, каждое работает независимо от других. Если ядер 8, то в каждый момент процессор работает над 8 задачами. Во многих процессорах есть технология hyper-threading, когда на 1 ядре выполняются 2 задачи. Тогда на 8 ядрах могут одновременно выполняться 16 задач. Многопоточный Относится к языку программирования. Это возможность изолировать задачи в разных потоках. У каждого потока свои локальные переменные, область видимости и исполняемый код. Очень удобно:) Если у процессора 8 ядер, в java приложении в каждый момент выполняются не больше 8 потоков (= не больше 8 задач). В других языках дело обстоит по-другому, подробнее в этом посте. Многопоточность — свойство языка, но в жизни часто упоминают "многопоточный код". Это код, в котором задачи из разных потоков взаимодействуют между собой. Например, запросы увеличивают общую переменную — счётчик запросов. Или задача делится на подзадачи, и они выполняются в разных потоках. Когда в вакансии пишут про знания многопоточки, то имеют в виду мастерское владение java.util.concurrent, знание возможных многопоточных проблем и лучших практик. Concurrency Относится к системе в целом. Система называется concurrent, если в ней выполняются несколько задач и актуальны проблемы: 🔹 как поделить системные ресурсы между задачами 🔹 как координировать задачи между собой 🔹 как корректно работать с общими ресурсами 🔹 как сделать так, чтобы ничего не сломалось при увеличении нагрузки В наши дни сложно найти что-то НЕ concurrent, все веб-сервисы попадают в эту категорию. На практике под concurrent кодом подразумевается, что проблемы выше решает не только фреймворк, но и разработчик. Параллельный Относится к задачам. Параллельно = одновременно. Процессор с 8 ядрами выполняет в каждый момент времени 8 задач = процессор параллельно выполняет 8 задач. В жизни термин употребляется не так строго. Допустим, нужно обработать 10 млн элементов. Если делать это последовательно, то будет работать одно ядро процессора, а остальные 7 (если ядер 8) — простаивать. При параллельной обработке задача разбивается на 10 частей по 1 млн, и каждая подзадача отправляется в отдельный поток. Вычислениями занимаются больше ядер, и общий результат посчитается быстрее. Значит ли это, что все 10 задач выполняются одновременно? Нет. Если у процессора 8 ядер, то в один момент выполняется максимум 8 задач. Но подобную схему всё равно называют параллельной обработкой Асинхронный Относится к общению между потоками, классами или сервисами. Синхронный означает, что участник 1 останавливает свою работу и ждёт результата от участника 2: ▫️ поток отправил запрос в БД и ждёт ответ ▫️ сервис отправил HTTP-запрос в другой сервис и ждёт ответ ▫️ поток отправил задачу в executor и ждёт результат через join Часто используют слово "блокирующий" как синоним синхронного запроса Асинхронный — когда участник 1 отправил запрос и НЕ ждёт ответ. Результат либо не нужен, либо участник 2 сам инициирует общение, когда результат готов: ▫️ поток отправил задачу в executor и не вызывает у задачи join ▫️ сервис отправляет сообщение в месседж брокер Многие инструменты выглядят как синхронные, но под капотом работают асинхронно. Например, метод sendAsync в HttpCLient или реактивные драйвера БД. 🎁 Бонус — чтобы понять, что могут спросить на собесах, воспользуйтесь формулой: Может ли (термин 1) быть/не быть (термин 2)? Например, ❓ Возможна ли многопоточная программа без параллельности? да ❓ А параллельная без многопоточности? нет ❓ Может ли однопоточная программа быть асинхронной? да ❓ Возможны ли многопоточные проблемы в программе, запущенной на одноядерном процессоре? да

Синтаксис Go, часть 2: неудачные моменты В изучении нового языка, фреймворка или библиотеки очень важны анализ и сравнение. В чём разница подходов, где какие плюсы и минусы, что для каких кейсов подойдёт. Если планируете расти дальше сеньора, то такие навыки пригодятся. В этом посте опишу 3 особенности Go. Их сложно отнести к достоинствам, но они помогают по-новому оценить привычные java конструкции 1️⃣ Неудобная работа с ошибками Как и в java, в go ошибки делятся на две категории: 🔹 error — ожидаемые ошибки, с которыми можно справиться. Например, файл не найден или у даты неверный формат Многие методы возвращают пару результат-ошибка:
result, err := process() 

Сразу после вызова метода проверяем, всё ли ок:
if err != nil { обработка ошибки }

Что не нравится: ❌ Если метод возвращает разные ошибки, то разработчику нужно самому найти в исходном коде возможные варианты и написать что-то вроде “если ошибка типа А, то … , если типа Б, то …” ❌ Большинство ошибок в стандартной библиотеке — обычный error с текстом. Обрабатывать в таком виде очень неудобно ❌ Компилятор не требует обработки ошибок 🔹 panic — непоправимые ошибки, например, выход на пределы массива. Аналог RuntimeException, паника поднимается по стеку вызовов, пока не встретит обработчик. Если не встретит, программа завершается. Что не нравится: ❌ Большинство паник содержат просто строчку с текстом ❌ Обработчики ловят всё подряд Эти недостатки вижу не только я. Для Go версии 2 (сейчас 1.19) идут активные обсуждения, как сделать работу с ошибками лучше. 2️⃣ Оригинальное форматирование В большинстве языков для перевода даты в строку и обратно используются шаблоны типа yyyy-MM-dd Go выбрал другой путь. Задать формат — значит написать, как выглядит в этом представлении 2 января 2006 года, время 15:04:05. Пример: чтобы отформатировать переменную dateTime в виде "сначала время через двоеточие, потом дата через дефис" пишем
currentTime.Format("15:04:05 02-01-2006"))

Почему такая дата? Потому что в американском формате это 01.02 03:04:05 06 -07 (месяц, число, время, год, часовой пояс) Какие преимущества у такого оригинального форматирования? Абсолютно никаких. К счастью, есть сторонние библиотеки, работающие с привычным yyyy-MM-dd 3️⃣ Неудобная работа с наборами элементов Коллекции в java — прекрасные абстракции для обработки данных. Например, ArrayList — коллекция, в основе которой лежит массив с конечной длиной. ArrayList скрывает часть сложности — если нужно добавить элемент в середину, то все манипуляции с массивом ArrayList берёт на себя. А с появлением Stream API работать с данными — сплошное удовольствие🥰 В Go абстракция над массивом называется slice (срез), у неё есть буфер и довольно специфичное поведение. (если вы заинтригованы, рекомендую этот текст и это видео) Целевые кейсы по работе с данными в Go скорее всего отличаются от джавовских. Поэтому и набор методов другой. Например, в стандартной библиотеке нет метода contains. Если надо — пиши сам:
for _, a := range s {
   if a == e { return true }
}

putIfAbsent, indexOf, isEmpty, метод вставки в начало/середину — всего этого нет. Абстракции в Go, кажется, хорошо подходят для оконных функций (посчитать среднее за 5 минут, максимальное за час) или случаев, когда исходные данные не меняются. Но для энтерпрайзных ситуаций это выглядит сложно, неудобно и ненадёжно. ❓ Что дальше? Для меня в обучении интересно не только прокачать hard skills, но и набраться новых идей. Даже простое изучение синтаксиса привело к каким-то мыслям, так что продолжу разбираться с Go. В планах изучить основные библиотеки, паттерны и лучшие практики. Посмотреть на внутрянку, бенчмарки, изучить продакшн кейсы. Об этом писать не буду, канал всё-таки про джаву. Но если встречу что-нибудь интересное, чего нет в java инфополе, то обязательно поделюсь:)

Синтаксис Go, часть 1: полезные штуки, которых нет в Java Некоторое время назад я начала изучать Go. Мотивация очень простая: 🔸 Популярность. Go занимает 4 место в Европе среди языков бэкенда. У Озона, Ламоды, ВК, Авито и других больших ребят есть сервисы на Go 🔸 Интерес. От языка, созданного гуглом, жду интересных идей и подходов к старым проблемам 🔸 А вдруг го лучше джавы? Может новые микросервисы писать на Go? Вдруг пора менять стэк и заводить канал Go: fill the gaps? Хочется разобраться и составить мнение на этот счёт Первый шаг в изучении языка — синтаксис и стандартные библиотеки. Я человек простой, и тоже иду по этому пути. Для большинства конструкций в Go можно легко найти аналоги в java. В этом посте я рассказажу об особенностях го, у которых НЕТ прямых аналогов в джаве. Если бы я писала пост летом, то первым пунктом стали бы горутины, киллер-фича Go. В java 19 вышли виртуальные потоки, которые на первый взгляд похожи на горутины. В нюансах я когда-нибудь разберусь, а сейчас расскажу, чего в джаве точно нет: 1️⃣ Можно вернуть несколько значений из функции
name, count := processUser(user)

Подобные штуки доступны и в других языках, например, в Python. Вернуть два значения — сверхпопулярный кейс, во многих java проектах для этих целей используют Map.Entry или создают класс Pair. До сих пор не понимаю, почему в джаве нельзя вернуть пару. Технически это не должно быть сложно, можно сделать что-то среднее между дженериками и LambdaMetaFactory. Или добавить класс Pair в стандартную библиотеку. 2️⃣ Нет наследования Только интерфейсы и композиция. Никаких проблем с абстрактными классами и сложными иерархиями. Одобряю👌 3️⃣ Объект можно передать по ссылке и по значению В java всё однозначно: ▪️ void m(int value) — примитив копируется и манипуляции с value не отразятся на переданной переменной ▪️ void m(User user) — ссылка копируется, но указывает на тот же объект В Go вариантов больше: ▫️ func m(value int) — примитив копируется как в джаве ▫️ func m(value *int) — передаём ссылку на примитив, внутри метода ей можно присвоить другое значение ▫️ func m(value User) — в метод передаётся полная копия объекта ▫️ func m(value *User) — передаём исходную ссылку на объект. Её можно переприсвоить новому объекту, и сам объект, конечно, можно менять 4️⃣ Оператор select для получения самого быстрого результата от асинхронных задач. Как это выглядит: допустим, мы отправили три задачи в асинхронное исполнение. Пишем:
select {
  результат задачи 1: код А
  результат задачи 2: код Б
  результат задачи 3: код Ц 
}

Какая задача первой вернёт результат, такой код и выполнится. При этом нам сразу доступен результат завершённой задачи. Самый близкий java аналог — конструкция
CompletableFuture.anyOf(задача1, задача2, задача3).thenRun(код)

Код в thenRun выполнится, когда одна из задач завершится. Затем нужно пройтись по всем объектам CompletableFuture, чтобы выяснить, какая именно задача завершилась, и забрать у неё результат. В go эту задачу выполнить гораздо проще. За кадром осталось много конструкций, которые выглядят по-другому, но я пока не поняла, чем они лучше аналогов в java. Возможно, когда перейду к изучению лучших практик, плюсы станут более весомыми. А может и нет:) Но не всё так радужно, и в следующем посте опишу особенности Go, которые мне НЕ понравились😈

Синтаксис Go, часть 1: полезные штуки, которых нет в Java Некоторое время назад я начала изучать Go. Мотивация очень простая: 🔸 Популярность. Go занимает 4 место в Европе среди языков бэкенда. У Озона, Ламоды, ВК, Авито и других больших ребят есть сервисы на Go 🔸 Интерес. От языка, созданного гуглом, жду интересных идей и подходов к старым проблемам 🔸 А вдруг го лучше джавы? Может новые микросервисы писать на Go? Вдруг пора менять стэк и заводить канал Go: fill the gaps? Хочется разобраться и составить мнение на этот счёт Первый шаг в изучении языка — синтаксис и стандартные библиотеки. Я человек простой, и тоже иду по этому пути. Для большинства конструкций в Go можно легко найти аналоги в java. В этом посте я рассказажу об особенностях го, у которых НЕТ прямых аналогов в джаве. Если бы я писала пост летом, то первым пунктом стали бы горутины, киллер-фича Go. В java 19 вышли виртуальные потоки, которые на первый взгляд похожи на горутины. В нюансах я когда-нибудь разберусь, а сейчас расскажу, чего в джаве точно нет: 1️⃣ Можно вернуть несколько значений из функции
name, count := processUser(user)

Подобные штуки доступны и в других языках, например, в Python. Вернуть два значения — сверхпопулярный кейс, во многих java проектах для этих целей используют Map.Entry или создают класс Pair. До сих пор не понимаю, почему в джаве нельзя вернуть пару. Технически это не должно быть сложно, можно сделать что-то среднее между дженериками и LambdaMetaFactory. Или добавить класс Pair в стандартную библиотеку. 2️⃣ Нет наследования Только интерфейсы и композиция. Никаких проблем с абстрактными классами и сложными иерархиями. Одобряю👌 3️⃣ Объект можно передать по ссылке и по значению В java всё однозначно: ▪️ void m(int value) — примитив копируется и манипуляции с value не отразятся на переданной переменной ▪️ void m(User user) — ссылка копируется, но указывает на тот же объект В Go вариантов больше: ▫️ func m(value int) — примитив копируется как в джаве ▫️ func m(value *int) — передаём ссылку на примитив, внутри метода ей можно присвоить другое значение ▫️ func m(value User) — в метод передаётся полная копия объекта ▫️ func m(value *User) — передаём исходную ссылку на объект. Её можно переприсвоить новому объекту, и сам объект, конечно, можно менять 4️⃣ Оператор select для получения самого быстрого результата от асинхронных задач. Как это выглядит: допустим, мы отправили три задачи в асинхронное исполнение. Пишем:
select {
  результат задачи 1: код А
  результат задачи 2: код Б
  результат задачи 3: код Ц 
}

Какая задача первой вернёт результат, такой код и выполнится. При этом нам сразу доступен результат завершённой задачи. Самый близкий java аналог — конструкция
CompletableFuture.anyOf(задача1, задача2, задача3).thenRun(код)

Код в thenRun выполнится, когда одна из задач завершится. Затем нужно пройтись по всем объектам CompletableFuture, чтобы выяснить, какая именно задача завершилась, и забрать у неё результат. В go эту задачу выполнить гораздо проще. За кадром осталось много конструкций, которые выглядят по-другому, но я пока не поняла, чем они лучше аналогов в java. Возможно, когда перейду к изучению лучших практик, плюсы станут более весомыми. А может и нет:) Но не всё так радужно, и в следующем посте опишу особенности Go, которые мне НЕ понравились😈 За кадром осталось много конструкций, которые выглядят по-другому, но я пока не поняла, чем они лучше аналогов в java. Возможно, когда перейду к изучению лучших практик, плюсы станут более весомыми. А может и нет:) Но не всё так радужно, и в следующем посте опишу особенности Go, которые мне НЕ понравились😈

Spring Security: основная архитектура Времена сложные, но надеюсь вернуться в стабильное написание постов. У многих разработчиков Spring Security — самый непонятный и нелюбимый модуль. Секрет успеха в работе с Security лежит в понимании двух вещей: 🔶 Используемый механизм безопасности Вариантов и комбинаций много — БД, LDAP, JWT, разные схемы oAuth и SSO. При реализации должно быть чёткое понимание, что где хранится, куда передаётся, кто, как и что валидирует 🔶 Базовая архитектура Spring Security Об этом и будет пост. Опишу простыми словами, что происходит в классической (не реактивной) архитектуре. Надеюсь, это поможет увидеть общую картину. Глобально сфера Security делится на две большие части: 1️⃣ Путь запроса до контроллера и обратно Схема такая: ▪️ Сервер получает запрос ▪️ Проводит его через цепочку фильтров, у каждого из которых своя задача: ▫️ CsrfFilter проверяет CSRF токен ▫️ SessionManagementFilter разбирается с сессией И так далее, всего около 10-15 фильтров (количество зависит от конфига) ▪️ Запрос попадает в контроллер ▪️ Выполняется специфичная логика проекта ▪️ Контроллер возвращает ответ ▪️ Запрос проходит по той же цепочке фильтров, но в обратном порядке. Фильтры выставят у ответа нужные заголовки, обнулят/сохранят сессию и тд ▪️ Ответ возвращается пользователю Что тут важно: ▫️Проверка прав, заголовков и основные секьюрити штуки обрабатываются в фильтрах ДО вызова контроллера ▫️Каждый фильтр может менять запрос/ответ или контекст, поэтому порядок фильтров имеет значение ▫️За конфигурацию фильтров отвечает метод конфига WebSecurityConfigurerAdapter#configure(HttpSecurity http) Чтобы увидеть процесс наглядно, поставьте брейкпойнт в методе FilterChainProxy#doFilterInternal. Там увидите все фильтры, можете погулять по ним и отследить, что происходит с запросом 2️⃣ Авторизация/аунтефикация скрывается за фасадом — бином AuthenticationManager. Чтобы авторизовать пользователя, в коде проекта нужно вызвать метод
authenticationManager.authenticate

Может в фильтре, может в контроллере, зависит от механизма безопасности. Метод authenticate пройдётся по всем заданным источникам авторизации. Результат можно положить в SecurityContextHolder, и он будет доступен из любого места в коде. Также бросится событие AuthenticationSuccessEvent, но обычно его игнорируют:) Источники авторизации называются *Provider (например, DaoAuthenticationProvider) и определяются в конфиге. В итоге всё что нужно от разработчика это: ☝️определить провайдеры ✌️ вызвать authenticate в нужном месте Посмотреть список текущих провайдеров: брейкпойнт в методе ProviderManager#authenticate. Задать список провайдеров: определить в конфиге WebSecurityConfigurerAdapter метод configure(AuthenticationManagerBuilder auth) Что с этим знанием делать на практике Если вам дали задачу на настройку Spring Security, не бросайтесь сразу гуглить spring security jwt/oauth/ldap example Велик шанс сделать что-нибудь не то. Лучше сделать так: Шаг 1 (самый важный): разобраться в механизме безопасности, который нужно реализовать. Что, откуда и как передаётся, где и как валидируется и сохраняется Шаг 2: прикинуть, как эти требования ложатся на архитектуру секьюрити. Что сделать в фильтрах, а что отдельным методом Шаг 3 (желательный): обсудить полученное решение со старшим товарищем Теперь можно гуглить и выбрать подходящий туториал. Поняв, что именно искать, вы в итоге потратите меньше времени и сохраните высокую самооценку🙂

Как устроена многопоточность в разных языках В этом посте упрощённо опишу, как происходит работа с потоками в разных языках. Для сравнения возьму 4 популярных в Европе бэкенд языка — Python, JavaScript, Java и Go. Начнём с основ. Любой бэк работает на каком-то железе. Основная цель при разработке — задействовать ресурсы процессора на максимум. Если у процессора 8 ядер, то одновременно могут выполняться 8 задач. Как это достигается: Python и JavaScript В этих языках в каждый момент времени выполняется только одна задача. В этом смысле языки можно назвать однопоточными. Когда задача запускается "в другом потоке", она логически изолируется от текущей. Например, запрос 1 выполняется в потоке Т1, запрос 2 — в потоке Т2. У каждого запроса теперь своя область видимости и локальные переменные. Эти логические потоки попеременно получают доступ к одному потоку ОС, а значит и к одному ядру. Один экземпляр сервиса нагружает только одно ядро процессора. Чтобы задействовать 8 ядер, запускают 8 экземпляров сервиса + балансировщик Плюсы: ✔️ Нет многопоточных проблем ✔️ Код получается линейный, его легко тестировать и дебажить Минусы: 🙁 Нет общей памяти между сервисами. Для обмена и накопления данных активно используются кэши, месседж брокеры и БД 🙁 Как следствие — чуть более сложная инфраструктура Java Потоки в джаве соотносятся с потоками ОС в отношении 1 к 1, поэтому в каждый момент времени может идти работа над 8 задачами (если ядер 8). Плюсы: ✔️ Один сервис вместо 8 — не нужен дополнительный балансировщик ✔️Общая память между потоками — можно переиспользовать данные и компоненты ✔️ Больше вариантов работы — на выбор даётся классический (thread-per-request) и реактивный стиль. А скоро добавятся виртуальные потоки 🥳 ✔️ Шикарная библиотека java.util.concurrent с инструментами на любой вкус Минусы: 🙁 Код становится сложнее 🙁 Сложно тестировать и дебажить 🙁 Многопоточные сложности: гонки, дедлоки, проблемы с видимостью и атомарностью 🙁 Сложно добиться оптимальной загрузки процессора. Для разных задач нужно подбирать разные параметры и решения Go Потоки в go (горутины) соотносятся с потоками ОС как многие ко многим. Сервису нужно гораздо меньше потоков ОС, чем в java. Благодаря особенностям реализации горутины показывают лучшую производительность в сервисах с большим количеством блокирующих вызовов. В итоге получается отличный микс классической и реактивной архитектуры. Плюсы: ✔️ Многопоточный код в некоторых случаях проще, чем в джаве ✔️ Один сервис и общая память между потоками ✔️ Отличная работа с блокирующими вызовами Минусы: 🙁 Всё ещё актуальны многопоточные сложности из пункта про джаву Это очень базовое описание механизмов работы с потоками. Работающий бэк можно написать на любом языке выше. В тех же Node.js и Django давно есть модули, облегчающие работу с несколькими экземплярами и передачей данных. Но в джаве модель работы с потоками самая сложная и разнообразная. И правильный ответ на вопрос перед постом: для java соотношение 1:1, для python — N:1

А какое соотношение потоков с потоками ОС в python?
Anonymous voting

Как потоки в java соотносятся с потоками операционной системы?
Anonymous voting

Связывание методов и бездумный копипаст Вопрос выше связан с темой связывания методов. Начну с неё, а потом немного поворчу на интернет. Итак, связывание бывает: ▫️ Позднее (динамическое) — решение, какой метод вызвать, принимается во время работы программы ▫️ Раннее (статическое) — решение принимается на этапе компиляции. В рантайме нет лишних движений, и скорость вызова таких методов чуть выше Для статических методов работает (сюрприз) статическое связывание. Статические методы в классах Parent и Child не переопределяют друг друга и относятся к разным классам. Над методом в классе Child нельзя поставить Override и вызвать внутри super.getName(); Поэтому правильный ответ в опросе выше — "Parent". Метод определяется во время компиляции по типу указателя. ❓ Что из этого можно вынести? Если посмотреть на getName, то непонятно, статический он или обычный, учитывается тип экземпляра или нет. Это затрудняет чтение кода и считается плохой практикой. Настолько плохой, что Intellij IDEA даже не показывает статические методы в выпадающем списке для объекта. Поэтому best practice — вызывать статические методы, обращаясь к классу:
Parent.getName()

Теперь о грустном. Вопрос про связывание часто входит в списки java interview questions, но почти все статьи содержат неверную информацию. Пишут, что ▪️ Статическое связывание используют private, final, static методы и конструкторы ▪️ Динамическое — методы интерфейсов и перегруженные методы Кажется логичным, но давайте проверим. Для разных типов связывания используется разные инструкции байткода. Посмотреть их можно через консоль
javap -c -v LovelyService.class

или в IDEA: View → Show Bytecode Нас интересуют инструкции invoke*. Немного поиграв с кодом можно увидеть, что для public, protected, private и final методов используется invokevirtual — динамическая типизация. Статические методы используют инструкцию invokestatic. Сразу возникает вопрос: ❓ Почему для private и final методов используется динамическое связывание? Ведь метод точно не переопределяется и это известно во время компиляции Для final ответ кроется в спецификации java, пункт 13.4.17. Суть такая: final метод может однажды перестать быть final, и кто-то может его переопределить. Когда класс, который переопределил метод, будет взаимодействовать со старым байткодом, то ничего не должно сломаться. Правила работы с private методами описаны в спецификации JVM, пункт 5.4.6. Причина использования invokevirtual не указана, но подозреваю, что ситуация как у final ❓ Зачем это знать? Для написания кода это абсолютно не важно. Но это яркий пример некорректной информации, которая бездумно копируется в джуниорские опросники👎 А вот на курсе многопоточки мы постоянно лазаем по исходникам java.util.concurrent, поэтому инфа абсолютно живая и актуальная. Минутка рекламы, но почему бы и нет:) Присоединяйтесь: https://fillthegaps.ru/mt6

Что выведется в консоль?
Anonymous voting

Что выведется в консоль?