Что будет в консоли при выполнении кода выше?
Anonymous Poll
21%
3
14%
4
25%
Ошибка компиляции
39%
Ошибка в рантайме
Загадка Set.of🔮
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
Set.of(1, 2, null)
получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
// 8
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
// 9
Set<String>set = Set.of(“a“, “b“);
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
Set<Long> adminIds = Set.of(user1.getId(), user2.getId());
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
Почему в Set.of нельзя добавить дубликаты и null?
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
Сколько операций на запись может одновременно проводить ConcurrentHashMap?
Anonymous Poll
13%
1
7%
16
11%
Количество бакетов / 16
54%
Количество бакетов
3%
Количество бакетов * 2
12%
Неограниченное количество
Необычный вопрос на собеседовании
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
public class MapController {
private final Map<String, Object> map = new ConcurrentHashMap<>();
@PostMapping
public void putValue(@RequestParam String k, @RequestParam Object v) {
map.put(k, v);
}
@GetMapping
public Object getValue(@RequestParam String k) {
return map.get(k);
}
}
Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
Почему однопоточный Redis такой быстрый?
В прошлом посте предложила вам задачку: сравнить Redis и велосипедик на основе ConcurrentHashMap + Spring MVC.
ConcurrentHashMap — многопоточный, и вроде должен быть лучше. Но именно однопоточный Redis является базовым выбором для кэша.
Как однопоточный Redis справляется с нагрузкой?
Секрет в том, как он работает с запросами. Есть 2 основные модели:
🌊 Каждый запрос обрабатывается в своем потоке (thread per request).
Такая модель используется, когда мы подключаем Spring MVC. Наш велосипедик тоже на ней работает.
У каждого потока свой стэк, переменные изолированы. Код легко писать, читать и дебажить. Идеальный вариант для сложных энтерпрайзных задач!
Но есть недостаток - число запросов в работе ограничено числом потоков в ОС. Обычно это несколько тысяч.
Из-за этой модели наш велосипед и проигрывает:
😒 Миллионы запросов просто не дойдут до ConcurrentHashMap, максимум несколько тысяч.
😒 Прочитать и записать в мэп - простые операции. Отправлять таких малышей в отдельный поток - как забивать краном гвозди. Очень большие накладные расходы на каждый запрос.
Redis использует другую модель:
🏃 EventLoop - малое число потоков бешено переключаются между запросами. В работу можно взять миллионы запросов!
Такая схема используется в реактивных серверах типа Netty, поддерживает многопоточность в JS и питоне.
Поэтому Redis и побеждает наш велосипед: возни с потоками нет, ограничений на запросы нет. Вся мощь процессора уходит на полезную работу, поэтому даже один поток справляется с большим объемом задач.
❓ Можно ли взять лучшее из двух миров? Использовать многопоточность вместе с EventLoop?
Можно! Один поток Redis не использует все доступные ядра процессора, поэтому добавить десяток потоков - вполне рабочая идея.
Такую схему используют KeyDB и DragonflyDB. На сайте публикуют бенчмарки, где они обходят Redis в 5-25 раз. 25 раз звучит слишком мощно, но про 5-10 раз можно верить.
❓ Почему чаще используется Redis, а не более быстрые альтернативы?
Потому что Redis появился в 2009, используется на сотнях проектов и закрепился в сознании как базовое решение для кэша. Подводные камни известны, инфраструктура налажена, куча статей и докладов.
KeyDB и DragonflyDB - свежие БД пирожки. Один вышел в 19 году, другой в 22. На конференциях особо не светились, громких кейсов внедрения пока нет.
Энтерпрайз мир тяжело принимает новые технологии. Плюс не всегда нужно лучшее решение, иногда достаточно хорошего😊
В прошлом посте предложила вам задачку: сравнить Redis и велосипедик на основе ConcurrentHashMap + Spring MVC.
ConcurrentHashMap — многопоточный, и вроде должен быть лучше. Но именно однопоточный Redis является базовым выбором для кэша.
Как однопоточный Redis справляется с нагрузкой?
Секрет в том, как он работает с запросами. Есть 2 основные модели:
🌊 Каждый запрос обрабатывается в своем потоке (thread per request).
Такая модель используется, когда мы подключаем Spring MVC. Наш велосипедик тоже на ней работает.
У каждого потока свой стэк, переменные изолированы. Код легко писать, читать и дебажить. Идеальный вариант для сложных энтерпрайзных задач!
Но есть недостаток - число запросов в работе ограничено числом потоков в ОС. Обычно это несколько тысяч.
Из-за этой модели наш велосипед и проигрывает:
😒 Миллионы запросов просто не дойдут до ConcurrentHashMap, максимум несколько тысяч.
😒 Прочитать и записать в мэп - простые операции. Отправлять таких малышей в отдельный поток - как забивать краном гвозди. Очень большие накладные расходы на каждый запрос.
Redis использует другую модель:
🏃 EventLoop - малое число потоков бешено переключаются между запросами. В работу можно взять миллионы запросов!
Такая схема используется в реактивных серверах типа Netty, поддерживает многопоточность в JS и питоне.
Поэтому Redis и побеждает наш велосипед: возни с потоками нет, ограничений на запросы нет. Вся мощь процессора уходит на полезную работу, поэтому даже один поток справляется с большим объемом задач.
❓ Можно ли взять лучшее из двух миров? Использовать многопоточность вместе с EventLoop?
Можно! Один поток Redis не использует все доступные ядра процессора, поэтому добавить десяток потоков - вполне рабочая идея.
Такую схему используют KeyDB и DragonflyDB. На сайте публикуют бенчмарки, где они обходят Redis в 5-25 раз. 25 раз звучит слишком мощно, но про 5-10 раз можно верить.
❓ Почему чаще используется Redis, а не более быстрые альтернативы?
Потому что Redis появился в 2009, используется на сотнях проектов и закрепился в сознании как базовое решение для кэша. Подводные камни известны, инфраструктура налажена, куча статей и докладов.
KeyDB и DragonflyDB - свежие БД пирожки. Один вышел в 19 году, другой в 22. На конференциях особо не светились, громких кейсов внедрения пока нет.
Энтерпрайз мир тяжело принимает новые технологии. Плюс не всегда нужно лучшее решение, иногда достаточно хорошего😊
⚡Требуется Java эксперт⚡
Последнее время плотно занимаюсь новым проектом. Цель проста - помочь джавистам профессионально расти и быть на коне в текущих сложных условиях.
Планы амбициозные, но на первом этапе буду работать над ультра востребованной темой - подготовке к собеседованиям. Хочу сделать этот процесс проще, лучше и приятнее. Заодно протестирую несколько концепций. Пойму, в ту ли сторону я смотрю и находит ли это отклик.
Для реализации мне нужны помощники, поэтому открываю вакансию: Java эксперт. Ищу людей, которые
⭐ Знают и любят джава разработку
⭐ Работали минимум на 3 разных проектах и видели всякое
⭐ Критически мыслят, видят логические несостыковки
⭐ Умеют работать с первоисточниками и проверять информацию. Не боятся простыни текста на английском
⭐ Большой плюс, если вы часто проводите собеседования и видите, где у людей случаются затыки
Что нужно делать: писать статьи по моему ТЗ. Сначала проработаем популярные темы типа HashMap, ACID, N+1, потом углубимся в другие нужные вещи.
Это будет серьезный умственный труд. Естественно, оплачиваемый.
Как присоединиться:
▫️ Заполнить анкету с небольшим тех.скринингом
▫️ Если все ок, я пришлю вам на почту оплачиваемое тестовое задание
Если вам интересно в этом поучаствовать, заполняйте анкету, не стесняйтесь. Если у вас есть знакомый, который подходит под описание, пришлите ему этот пост❤️
Последнее время плотно занимаюсь новым проектом. Цель проста - помочь джавистам профессионально расти и быть на коне в текущих сложных условиях.
Планы амбициозные, но на первом этапе буду работать над ультра востребованной темой - подготовке к собеседованиям. Хочу сделать этот процесс проще, лучше и приятнее. Заодно протестирую несколько концепций. Пойму, в ту ли сторону я смотрю и находит ли это отклик.
Для реализации мне нужны помощники, поэтому открываю вакансию: Java эксперт. Ищу людей, которые
⭐ Знают и любят джава разработку
⭐ Работали минимум на 3 разных проектах и видели всякое
⭐ Критически мыслят, видят логические несостыковки
⭐ Умеют работать с первоисточниками и проверять информацию. Не боятся простыни текста на английском
⭐ Большой плюс, если вы часто проводите собеседования и видите, где у людей случаются затыки
Что нужно делать: писать статьи по моему ТЗ. Сначала проработаем популярные темы типа HashMap, ACID, N+1, потом углубимся в другие нужные вещи.
Это будет серьезный умственный труд. Естественно, оплачиваемый.
Как присоединиться:
▫️ Заполнить анкету с небольшим тех.скринингом
▫️ Если все ок, я пришлю вам на почту оплачиваемое тестовое задание
Если вам интересно в этом поучаствовать, заполняйте анкету, не стесняйтесь. Если у вас есть знакомый, который подходит под описание, пришлите ему этот пост❤️
Самая унылая тема на собеседовании это
Anonymous Poll
30%
SOLID
27%
Методы Object
13%
Интерфейс против абстрактного класса
14%
ArrayList против LinkedList
17%
Пулы строк и Integer с задачками на ==
Принципы SOLID и групповой транс
Недавно слушала записи собеседований и обратила внимание на интересный феномен. Связан он, как вы уже догадались, с обсуждением SOLID.
Типичный диалог на собеседовании выглядит так:
👨💼: Расскажите про SOLID
👱♂️: [расшифровка каждой буквы]
👨💼: Используете в работе?
👱♂️: Конечно!
Хорошо, если на этом все заканчивается. Но бывает и продолжение: "расскажите принципы своими словами". На этом месте даже опытные и умные люди впадают в транс. Например, кандидат описывает принцип Open-closed:
👱♂️: Чтобы добавить новый метод, нужно не менять существующий класс, а создать новый
👨💼 : Угу, давайте дальше
Ни кандидат, ни собеседующий не создают новый класс для каждого метода. Почему один так отвечает, а другой принимает такой ответ — загадка.
Dependency Inversion часто объясняют как "нужно всё делать через интерфейс".
Это считается реализацией DI? Чем отличается от инкапсуляции? Для каждого класса нужно создавать интерфейс? Работа с любым классом напрямую — это нарушение принципа? Почему в названии есть инвершн, что инвертируется-то?
Вопросы здравые, но люди в трансе:) У других абстрактных тем вроде принципов ООП или паттернов такого эффекта нет.
SOLID — самая унылая тема на собеседовании. Формальная, абстрактная и скучная для всех сторон. Собеседование длится всего час-полтора, нет никакого смысла тратить время на ритуальный обмен фразами.
❓ Применяются ли принципы SOLID на практике?
▫️ Single Responsibility - да. Что неудивительно, это самый понятный и простой принцип.
▫️ Open-closed, Liskov и Interface Segregation тесно связаны с наследованием и сложными иерархиями классов. Для бизнес логики большинства систем это не очень актуально. Там максимум интерфейс и несколько реализаций.
▫️ Dependency Inversion - самый непонятный и недооцененный принцип. За 10+ лет в разработке я встречала мало людей, которые его поняли. Это не просто "делаем всё через интерфейс". Можно реализовать его даже без интерфейсов, суть вообще в другом.
К четвергу напишу пост и подробно объясню🔥
Недавно слушала записи собеседований и обратила внимание на интересный феномен. Связан он, как вы уже догадались, с обсуждением SOLID.
Типичный диалог на собеседовании выглядит так:
👨💼: Расскажите про SOLID
👱♂️: [расшифровка каждой буквы]
👨💼: Используете в работе?
👱♂️: Конечно!
Хорошо, если на этом все заканчивается. Но бывает и продолжение: "расскажите принципы своими словами". На этом месте даже опытные и умные люди впадают в транс. Например, кандидат описывает принцип Open-closed:
👱♂️: Чтобы добавить новый метод, нужно не менять существующий класс, а создать новый
👨💼 : Угу, давайте дальше
Ни кандидат, ни собеседующий не создают новый класс для каждого метода. Почему один так отвечает, а другой принимает такой ответ — загадка.
Dependency Inversion часто объясняют как "нужно всё делать через интерфейс".
Map<String, String> map = new HashMap<>();
Это считается реализацией DI? Чем отличается от инкапсуляции? Для каждого класса нужно создавать интерфейс? Работа с любым классом напрямую — это нарушение принципа? Почему в названии есть инвершн, что инвертируется-то?
Вопросы здравые, но люди в трансе:) У других абстрактных тем вроде принципов ООП или паттернов такого эффекта нет.
SOLID — самая унылая тема на собеседовании. Формальная, абстрактная и скучная для всех сторон. Собеседование длится всего час-полтора, нет никакого смысла тратить время на ритуальный обмен фразами.
❓ Применяются ли принципы SOLID на практике?
▫️ Single Responsibility - да. Что неудивительно, это самый понятный и простой принцип.
▫️ Open-closed, Liskov и Interface Segregation тесно связаны с наследованием и сложными иерархиями классов. Для бизнес логики большинства систем это не очень актуально. Там максимум интерфейс и несколько реализаций.
▫️ Dependency Inversion - самый непонятный и недооцененный принцип. За 10+ лет в разработке я встречала мало людей, которые его поняли. Это не просто "делаем всё через интерфейс". Можно реализовать его даже без интерфейсов, суть вообще в другом.
К четвергу напишу пост и подробно объясню🔥
Dependency Inversion
Сегодня расскажу, в чем суть принципа DI из SOLID, и почему с ним так много проблем. Вспомним формулировку:
✍️ Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
✍️ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Ставь огонёк, если даже не вчитывался в этот набор слов😊
Формулировка и правда полный отстой. Первая часть про какие-то модули, зависящие от каких-то абстракций. Вторая напоминает инкапсуляцию.
Роберт Мартин, он же дядюшка Боб, в целом молодец, но конкретно здесь плохо донёс свою идею. В оригинальной статье всё очень сумбурно. Интерфейсы добавляются на каждом шагу и отвлекают от сути dependency inversion. Ничего удивительного, что большинство людей не поняли, о чём речь.
Дядюшка Боб не смог, тетя Диана объяснит🤌
Все очень просто. У каждого класса есть своя область ответственности, single responsibility:
🔴 Кнопку нажимают, и она отправляет сигнал об этом. Ничего больше
💡 Лампа включается и выключается, когда ей говорят. Ничего больше
💏 Взаимодействие этих сущностей должно быть описано отдельно
Где здесь инверсия:
В нашем мире все описывается последовательно, причина -> следствие. Нажали кнопку -> включилась лампа. Перекладывая на код, в классе Кнопка будет поле Лампа:
В мире с Dependency inversion Кнопка и Лампа ничего не знают друг о друге, логика взаимодействия описана в классе Электросхема. Она ловит сигналы от Кнопки и отправляет команды Лампе:
Если у лампы появится новый режим, разбираться с этим будет электросхема, а не кнопка. Если кнопка поменяется на датчик движения, обработка поменяется только в электросхеме.
Интерфейсы можно добавить, чтобы жонглировать реализациями, но это не главное. Dependency inversion говорит о том, что логика взаимодействия компонентов должна находиться НЕ в самих компонентах.
Кстати, если перейти с уровня классов на уровень контроллеры/сервисы, получится Clean architecture. В основе та же идея: компоненты отдельно, взаимодействие отдельно.
Всё очень просто❤️
Сегодня расскажу, в чем суть принципа DI из SOLID, и почему с ним так много проблем. Вспомним формулировку:
✍️ Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
✍️ Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Ставь огонёк, если даже не вчитывался в этот набор слов😊
Формулировка и правда полный отстой. Первая часть про какие-то модули, зависящие от каких-то абстракций. Вторая напоминает инкапсуляцию.
Роберт Мартин, он же дядюшка Боб, в целом молодец, но конкретно здесь плохо донёс свою идею. В оригинальной статье всё очень сумбурно. Интерфейсы добавляются на каждом шагу и отвлекают от сути dependency inversion. Ничего удивительного, что большинство людей не поняли, о чём речь.
Дядюшка Боб не смог, тетя Диана объяснит🤌
Все очень просто. У каждого класса есть своя область ответственности, single responsibility:
🔴 Кнопку нажимают, и она отправляет сигнал об этом. Ничего больше
💡 Лампа включается и выключается, когда ей говорят. Ничего больше
💏 Взаимодействие этих сущностей должно быть описано отдельно
Где здесь инверсия:
В нашем мире все описывается последовательно, причина -> следствие. Нажали кнопку -> включилась лампа. Перекладывая на код, в классе Кнопка будет поле Лампа:
public class Button
private Lamp lamp;
public void push() {
// включить/выключить лампу
}
}
В мире с Dependency inversion Кнопка и Лампа ничего не знают друг о друге, логика взаимодействия описана в классе Электросхема. Она ловит сигналы от Кнопки и отправляет команды Лампе:
public class Circuit {
private Button button;
private Lamp lamp;
public void process() {
// получить сигнал от кнопки
// сказать лампе включиться / выключиться
}
}
Если у лампы появится новый режим, разбираться с этим будет электросхема, а не кнопка. Если кнопка поменяется на датчик движения, обработка поменяется только в электросхеме.
Интерфейсы можно добавить, чтобы жонглировать реализациями, но это не главное. Dependency inversion говорит о том, что логика взаимодействия компонентов должна находиться НЕ в самих компонентах.
Кстати, если перейти с уровня классов на уровень контроллеры/сервисы, получится Clean architecture. В основе та же идея: компоненты отдельно, взаимодействие отдельно.
Всё очень просто❤️
В БД складываются задачи, которые затем распределяются между несколькими сервисами. Сервис извлекает задачи и обрабатывает их примерно так👇
Какая проблема возможна в этом коде?
Какая проблема возможна в этом коде?
Популярная ошибка в блокировках, часть 1
Недавно делала ревью одного сервиса и нашла там ошибки, связанные с блокировками. Я встречала подобные ошибки и на других проектах, поэтому давайте разберём их вместе, будет полезно:)
Исходная цель: распределить задачи между сервисами. Решается очень просто:
✍️ Складываем задачи в отдельную таблицу
🙋 Каждый сервис выбирает из таблицы одну строку и ставит на неё блокировку. Благодаря блокировке другой сервис не может взять эту задачу в работу.
Примерно такая реализация и сделана на картинке выше. С виду вроде всё хорошо, но есть две проблемы. Первая - задача может быть обработана несколько раз💔
🤔 Почему?
Блокировка действует до конца транзакции. В коде выше транзакция заканчивается после получения нужной строки, затем блокировка снимается. И по сути блокировки нет. Одну задачу могут взять несколько сервисов и обработать её несколько раз.
Что делать:
✅ Поставить аннотацию Transactional над Scheduled методом. Тогда блокировка держится во время всей работы над задачей
Плюс: просто реализовать
Минус: если обработка сложная, мы долго и нерационально держим соединение с базой
✅ Альтернатива — отказаться от блокировок. Берём задачу в работу — меняем статус в БД на "in progress". Делается одним запросом с помощью RETURNING:
Плюс: нет длинной транзакции
Минус: если сервис упадёт, задача останется в БД со статусом "в работе", и в итоге не будет обработана. Нужно дополнительно следить за такими ситуациями.
Итого
Блокировки работают до конца работы транзакции. Если полагаетесь на блокировки - не отпускайте их раньше времени.
Это просто, но в большой кодовой базе легко упустить этот момент. Будьте внимательнее❤️ Вторую проблему опишу в следующем посте!
Недавно делала ревью одного сервиса и нашла там ошибки, связанные с блокировками. Я встречала подобные ошибки и на других проектах, поэтому давайте разберём их вместе, будет полезно:)
Исходная цель: распределить задачи между сервисами. Решается очень просто:
✍️ Складываем задачи в отдельную таблицу
🙋 Каждый сервис выбирает из таблицы одну строку и ставит на неё блокировку. Благодаря блокировке другой сервис не может взять эту задачу в работу.
Примерно такая реализация и сделана на картинке выше. С виду вроде всё хорошо, но есть две проблемы. Первая - задача может быть обработана несколько раз💔
🤔 Почему?
Блокировка действует до конца транзакции. В коде выше транзакция заканчивается после получения нужной строки, затем блокировка снимается. И по сути блокировки нет. Одну задачу могут взять несколько сервисов и обработать её несколько раз.
Что делать:
✅ Поставить аннотацию Transactional над Scheduled методом. Тогда блокировка держится во время всей работы над задачей
Плюс: просто реализовать
Минус: если обработка сложная, мы долго и нерационально держим соединение с базой
✅ Альтернатива — отказаться от блокировок. Берём задачу в работу — меняем статус в БД на "in progress". Делается одним запросом с помощью RETURNING:
UPDATE tasks SET status = 'in progress'
WHERE status = 'not processed'
RETURNING id, …;
Плюс: нет длинной транзакции
Минус: если сервис упадёт, задача останется в БД со статусом "в работе", и в итоге не будет обработана. Нужно дополнительно следить за такими ситуациями.
Итого
Блокировки работают до конца работы транзакции. Если полагаетесь на блокировки - не отпускайте их раньше времени.
Это просто, но в большой кодовой базе легко упустить этот момент. Будьте внимательнее❤️ Вторую проблему опишу в следующем посте!
Продолжение прошлого вопроса. В БД складываются задачи, которые затем распределяются между несколькими сервисами. Сервис извлекает задачи и обрабатывает их примерно так👇
Над Scheduled задачей добавилась аннотация Transactional.
Какая проблема возможна в этом коде?
Над Scheduled задачей добавилась аннотация Transactional.
Какая проблема возможна в этом коде?
Какая проблема возможна в коде выше?
Anonymous Poll
13%
Утечка соединений к базе данных
7%
Задача отметится как невыполненная, хотя была обработана
7%
Некоторые задачи будут пропущены
24%
Задача может быть обработана несколько раз
42%
Задачи в разных сервисах не обрабатываются параллельно
6%
Всё нормально
Популярная ошибка в блокировках, часть 2
Продолжим разбирать ошибки при распределении задач. В прошлом посте мы разобрали, что не нужно отпускать блокировку слишком рано, иначе одну задачку возьмут несколько сервисов. Сегодня разберём следующую проблему:
Задачи в разных сервисах не обрабатываются параллельно
Почему?
🧑💻 Сервис 1 выполняет запрос "найди задачу для обработки", получает строку 1 и блокирует её
🧑💻 Сервис 2 выполняет такой же запрос, получает в результате ту же строку. Но блокировку поставить не может, так как строка уже заблокирована.
💅 Сервис 2 ждёт, пока блокировка снимется
В итоге все сервисы ждут, пока сервис 1 закончит задачу 1 и отпустит блокировку. С другими задачами ситуация повторится - один сервис работает, остальные висят на блокировке.
Не самая эффективная командная работа🙈
Чтобы исправить ситуацию, сервисы должны пропускать заблокированные строки и брать себе задачу из "свободных". При взятии блокировки нужно добавить SKIP LOCKED.
Но средствами Spring Data SKIP LOCKED не сделать, поэтому напишем SQL запрос над методом в репозитории:
Теперь задачи обрабатываются параллельно несколькими сервисами🥳
P.S. Очень рада, что в прошлом опросе было много правильных ответов, вы умнички🥰
Продолжим разбирать ошибки при распределении задач. В прошлом посте мы разобрали, что не нужно отпускать блокировку слишком рано, иначе одну задачку возьмут несколько сервисов. Сегодня разберём следующую проблему:
Задачи в разных сервисах не обрабатываются параллельно
Почему?
🧑💻 Сервис 1 выполняет запрос "найди задачу для обработки", получает строку 1 и блокирует её
🧑💻 Сервис 2 выполняет такой же запрос, получает в результате ту же строку. Но блокировку поставить не может, так как строка уже заблокирована.
💅 Сервис 2 ждёт, пока блокировка снимется
В итоге все сервисы ждут, пока сервис 1 закончит задачу 1 и отпустит блокировку. С другими задачами ситуация повторится - один сервис работает, остальные висят на блокировке.
Не самая эффективная командная работа🙈
Чтобы исправить ситуацию, сервисы должны пропускать заблокированные строки и брать себе задачу из "свободных". При взятии блокировки нужно добавить SKIP LOCKED.
Но средствами Spring Data SKIP LOCKED не сделать, поэтому напишем SQL запрос над методом в репозитории:
@Query(value = "SELECT * FROM outbox
WHERE is_done = false
ORDER BY id ASC LIMIT 1
FOR UPDATE SKIP LOCKED",
nativeQuery = true
)
Optional<OutboxEntry> findFirstByIsDoneFalseOrderByIdAsc();
Теперь задачи обрабатываются параллельно несколькими сервисами🥳
P.S. Очень рада, что в прошлом опросе было много правильных ответов, вы умнички🥰
Можно ли переопределить метод getClass() в классе Object?
Anonymous Quiz
31%
Да, но не рекомендуется
25%
Да, но JVM будет всё равно использовать исходную native реализацию
44%
Нет, компилятор не разрешит
Критикую Object
В последнее время анализировала чудовищное количество API, изучала контракты и обязанности разных систем и сервисов. На этой волне и созрел сегодняшний пост.
Предлагаю вам взглянуть по-новому на класс Object. Удивиться, насколько он плох с точки зрения API🙈
Object — базовый класс для остальных классов. В родительский класс помещают методы, полезные для подклассов. Пишутся удобные реализации по умолчанию. Но Object явно не следует этим рекомендациям.
Возьмём метод hashCode. Для чего объекту нужен хэш?
Для некоторых алгоритмов и структур данных. Чаще всего хэш используется в HashMap или HashSet.
Но как часто объект становится ключом в HashMap? Часто ли в бизнес-логике используются хэши? Нужен ли hashCode каждому классу?
Вряд ли.
А ещё есть контракт equals/hashcode. Он висит невидимой тенью (хорошей практикой) над каждым разработчиком. Хочешь сравнивать объекты через equals — не забудь определить hashСode. Даже если хэш в коде не нужен.
Как можно по-другому?
Альтернативный путь использует compareTo. Его нет в наборе методов обжекта. Чтобы сравнить объекты или сложить их в TreeSet, мы либо реализуем Comparable, либо передаём логику сравнения через Comparator.
Такой подход отлично подошёл бы для хэша!
Захотим использовать объект внутри хэш-структуры — реализуем интерфейс или передадим лямбду в HashMap. Захотим посчитать хэш для контрольной суммы — просто реализуем метод, где это необходимо.
✅ Не думаем про хэш, когда он не нужен
✅ Компилятор укажет, где хэш не определен, но используется
В других языках, кстати, нет по умолчанию всеобщего хэша. В С++ используется compareTo-подход. В Go не всё может стать ключом, зато хэш для таблицы автоматом считается по всем полям. Мне оба варианта нравятся, работа с хэшем более явная и предсказуемая👌
❓ А что с другими методами Object?
К ним тоже вопросики. Нужны ли каждому классу методы wait и notify? Почему clone такой странный? Зачем нужен equals по умолчанию, если внутри просто ==? Хорошо хоть finalize отметили как deprecated.
Польза базовых hashCode, equals, toString похоже чисто синтаксическая. Показать, что именно можно переопределить.
Итого: лишние методы, слабые варианты по умолчанию. Поэтому общая оценка Object с точки зрения API - слабая троечка
PS
В целом java и её вселенная — конфетка. Критикую любя и чисто в образовательных целях. Посмотреть на привычные вещи под другим углом и подумать "как можно иначе" всегда интересно
В последнее время анализировала чудовищное количество API, изучала контракты и обязанности разных систем и сервисов. На этой волне и созрел сегодняшний пост.
Предлагаю вам взглянуть по-новому на класс Object. Удивиться, насколько он плох с точки зрения API🙈
Object — базовый класс для остальных классов. В родительский класс помещают методы, полезные для подклассов. Пишутся удобные реализации по умолчанию. Но Object явно не следует этим рекомендациям.
Возьмём метод hashCode. Для чего объекту нужен хэш?
Для некоторых алгоритмов и структур данных. Чаще всего хэш используется в HashMap или HashSet.
Но как часто объект становится ключом в HashMap? Часто ли в бизнес-логике используются хэши? Нужен ли hashCode каждому классу?
Вряд ли.
А ещё есть контракт equals/hashcode. Он висит невидимой тенью (хорошей практикой) над каждым разработчиком. Хочешь сравнивать объекты через equals — не забудь определить hashСode. Даже если хэш в коде не нужен.
Как можно по-другому?
Альтернативный путь использует compareTo. Его нет в наборе методов обжекта. Чтобы сравнить объекты или сложить их в TreeSet, мы либо реализуем Comparable, либо передаём логику сравнения через Comparator.
Такой подход отлично подошёл бы для хэша!
Захотим использовать объект внутри хэш-структуры — реализуем интерфейс или передадим лямбду в HashMap. Захотим посчитать хэш для контрольной суммы — просто реализуем метод, где это необходимо.
✅ Не думаем про хэш, когда он не нужен
✅ Компилятор укажет, где хэш не определен, но используется
В других языках, кстати, нет по умолчанию всеобщего хэша. В С++ используется compareTo-подход. В Go не всё может стать ключом, зато хэш для таблицы автоматом считается по всем полям. Мне оба варианта нравятся, работа с хэшем более явная и предсказуемая👌
❓ А что с другими методами Object?
К ним тоже вопросики. Нужны ли каждому классу методы wait и notify? Почему clone такой странный? Зачем нужен equals по умолчанию, если внутри просто ==? Хорошо хоть finalize отметили как deprecated.
Польза базовых hashCode, equals, toString похоже чисто синтаксическая. Показать, что именно можно переопределить.
Итого: лишние методы, слабые варианты по умолчанию. Поэтому общая оценка Object с точки зрения API - слабая троечка
PS
В целом java и её вселенная — конфетка. Критикую любя и чисто в образовательных целях. Посмотреть на привычные вещи под другим углом и подумать "как можно иначе" всегда интересно
Можно ли в конструкторе использовать return?
Anonymous Quiz
17%
Да, можно вызвать return;
9%
Да, можно вызвать return this;
13%
Да, можно вернуть this или объект наследника текущего класса
61%
Нельзя
Java 25: новый формат конструктора
Сегодня расскажу про новую фичу в осенней джаве. Суть простая - this и super не обязательно должны идти первой строкой в конструкторе.
Зачем это нужно? Чтобы упростить валидацию.
Сейчас, чтобы добавить проверку аргументов, приходится оборачивать аргументы в методы:
С новым JEP эти костыли не нужны, нужные проверки пишем в начале конструктора:
Области кода вокруг this/super называются очень литературно: пролог и эпилог🥰
В пролог нельзя вставить любой код:
❌ Нельзя обращаться к переменным родителя
❌ Нельзя вызывать нестатические методы
❌ Нельзя вызвать return
✅ Можно присвоить поля текущего класса
Особо не разгуляешься, всё же основной сценарий фичи — валидация входных параметров.
Ещё из интересного:
1️⃣ В JVM не пришлось ничего менять
Потому что правила "this обязательно первый" в JVM нет. Это ограничение только на уровне языка, чтобы упростить работу компилятора:)
2️⃣ Меняется ответ на частый собесный вопрос "в каком порядке инициализируются переменные". Раньше порядок для нестатических полей был такой:
Поля Parent - Конструктор Parent - Поля Child - Конструктор Child
В Java 25 поля наследника можно инициализировать ДО вызова конструктора родителя:
Общая схема с этими прологами-эпилогами очень усложняется.
3️⃣ Фича называется Flexible Constructor Bodies. Я не смогла придумать адекватный перевод, поэтому очень интересно, как её переведут в статьях-обзорах:)
Сегодня расскажу про новую фичу в осенней джаве. Суть простая - this и super не обязательно должны идти первой строкой в конструкторе.
Зачем это нужно? Чтобы упростить валидацию.
Сейчас, чтобы добавить проверку аргументов, приходится оборачивать аргументы в методы:
class Employee extends Person {
private static int verifyAge(int value) {
if (age < 18)
throw new IllegalArgumentException(...);
return value;
}
Employee(int age) {
super(verifyAge(age));
}
}
С новым JEP эти костыли не нужны, нужные проверки пишем в начале конструктора:
class Employee extends Person {
Employee(int age) {
if (age < 18)
throw new IllegalArgumentException(...);
super(age);
}
}
Области кода вокруг this/super называются очень литературно: пролог и эпилог🥰
public Person {
// prologue
super();
// epilogue
}
В пролог нельзя вставить любой код:
❌ Нельзя обращаться к переменным родителя
❌ Нельзя вызывать нестатические методы
❌ Нельзя вызвать return
✅ Можно присвоить поля текущего класса
Особо не разгуляешься, всё же основной сценарий фичи — валидация входных параметров.
Ещё из интересного:
1️⃣ В JVM не пришлось ничего менять
Потому что правила "this обязательно первый" в JVM нет. Это ограничение только на уровне языка, чтобы упростить работу компилятора:)
2️⃣ Меняется ответ на частый собесный вопрос "в каком порядке инициализируются переменные". Раньше порядок для нестатических полей был такой:
Поля Parent - Конструктор Parent - Поля Child - Конструктор Child
В Java 25 поля наследника можно инициализировать ДО вызова конструктора родителя:
Employee(int age, String officeID) {
this.officeID = officeID;
super(age);
}
Общая схема с этими прологами-эпилогами очень усложняется.
3️⃣ Фича называется Flexible Constructor Bodies. Я не смогла придумать адекватный перевод, поэтому очень интересно, как её переведут в статьях-обзорах:)