👍4
Что такое ConcurrentHashMap и чем он лучше Hashtable? 🤓
Ответ:
ConcurrentHashMap из пакета java.util.concurrent — это потокобезопасная реализация Map, оптимизированная для многопоточной среды.
В отличие от Hashtable, которая синхронизирует все методы (блокируя всю таблицу), ConcurrentHashMap использует более тонкую блокировку.
В Java 7 — сегментирование, в Java 8+ блокируются отдельные элементы корзины (или используется Compare-And-Swap). Это позволяет параллельно выполнять чтение и запись в разные сегменты/корзины. Он также не выбрасывает ConcurrentModificationException при итерации.
#собеседование
Ответ:
В отличие от Hashtable, которая синхронизирует все методы (блокируя всю таблицу), ConcurrentHashMap использует более тонкую блокировку.
В Java 7 — сегментирование, в Java 8+ блокируются отдельные элементы корзины (или используется Compare-And-Swap). Это позволяет параллельно выполнять чтение и запись в разные сегменты/корзины. Он также не выбрасывает ConcurrentModificationException при итерации.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 10 марта
ℹ️ Кто родился в этот день
Евге́ний Ла́заревич Роша́л (род. 10 марта 1972, Челябинск) — российский программист, автор файлового менеджера FAR Manager, формата сжатия RAR, архиваторов RAR и WinRAR.
🌐 Знаковые события
1977 — несколько групп астрономов США, Австралии, Индии, ЮАР открыли кольца Урана.
1982 — редкий парад планет — все девять планет (Меркурий, Венера, Земля, Марс, Юпитер, Сатурн, Уран, Нептун и Плутон) собрались по одну сторону от Солнца в секторе с углом 95 градусов (то есть, максимальная разность гелиоцентрических эклиптических долгот планет составила 95 градусов).
2006 — автоматическая межпланетная станция Mars Reconnaissance Orbiter достигла Марса.
#Biography #Birth_Date #Events #10марта
Евге́ний Ла́заревич Роша́л (род. 10 марта 1972, Челябинск) — российский программист, автор файлового менеджера FAR Manager, формата сжатия RAR, архиваторов RAR и WinRAR.
1977 — несколько групп астрономов США, Австралии, Индии, ЮАР открыли кольца Урана.
1982 — редкий парад планет — все девять планет (Меркурий, Венера, Земля, Марс, Юпитер, Сатурн, Уран, Нептун и Плутон) собрались по одну сторону от Солнца в секторе с углом 95 градусов (то есть, максимальная разность гелиоцентрических эклиптических долгот планет составила 95 градусов).
2006 — автоматическая межпланетная станция Mars Reconnaissance Orbiter достигла Марса.
#Biography #Birth_Date #Events #10марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 8. Stream API и функциональный стиль в Java
Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон
equals/hashCode в операциях distinct, groupingBy, toSet
Stream API не существует в вакууме. Он опирается на фундаментальные контракты Java-объектов, установленные классом Object: equals для логического сравнения, hashCode для хеширования, compareTo для упорядочивания.
Нарушение этих контрактов не всегда приводит к немедленным ошибкам компиляции или очевидным исключениям. Вместо этого порождаются "мистические" баги: данные исчезают, дублируются, группируются неправильно — и всё это проявляется недетерминированно, часто только под нагрузкой или при смене окружения.
Рассмотрим класс Book, разработанный без должного внимания к контрактам:
По умолчанию equals и hashCode наследуются от Object, реализуя идентичность по ссылке: два объекта равны только если это один и тот же экземпляр в памяти. Для доменной модели, где логическая эквивалентность определяется содержимым, это поведение некорректно.
groupingBy и распределение по бакетам
Коллектор groupingBy использует HashMap для организации групп. При добавлении элемента вычисляется хеш-код ключа, определяющий бакет (сегмент внутреннего массива HashMap). Если в бакете уже есть элементы, выполняется проверка equals для разрешения коллизий.
Проблема возникает при использовании ключа без корректного hashCode:
Без переопределённого hashCode book1 и book2 имеют разные хеш-коды (по умолчанию System.identityHashCode), вычисляемые из адресов памяти. Они попадают в разные бакеты HashMap. Внутри каждого бакета equals возвращает false, потому что сравнение по ссылке даёт отрицательный результат. Результат: две записи в Map вместо одной, счётчики показывают 2 для каждой "уникальной" книги.
Баг проявляется недетерминированно. При небольшом числе элементов HashMap может поместить оба объекта в один бакет случайно (из-за пересечения хешей по модулю размера таблицы), и equals обнаружит их различность, но коллизия разрешится корректно — повезло. При росте данных таблица расширяется, бакеты становятся уже, вероятность коллизии падает, баг становится стабильным. При смене JVM (разная версия, разная реализация Object.hashCode) хеш-коды меняются, и поведение меняется без изменения кода.
distinct: фильтрация дубликатов без гарантий
Операция distinct() использует LinkedHashSet для отслеживания уникальных элементов. Она полагается на тот же контракт equals/hashCode:
Без корректных методов distinct не удаляет логические дубликаты, только ссылочные. Если book1 и book2 — разные объекты с одинаковым содержимым, оба попадут в результат. Память растёт, кэширование не работает.
Особенно опасно это в распределённых системах: объект десериализуется в разных узлах, создавая разные экземпляры в памяти. Без equals/hashCode по значению они воспринимаются как разные сущности.
toSet и потеря элементов
Collectors.toSet() возвращает HashSet, основанный на хеш-таблице. Некорректный hashCode ведёт к дублированию:
HashSet добавляет элемент, если в соответствующем бакете нет равного (по equals). Разные хеш-коды → разные бакеты → оба элемента добавлены. Множество перестаёт быть множеством.
#Java #для_новичков #beginner #stream_api #equals_hashCode
Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон
equals/hashCode в операциях distinct, groupingBy, toSet
Stream API не существует в вакууме. Он опирается на фундаментальные контракты Java-объектов, установленные классом Object: equals для логического сравнения, hashCode для хеширования, compareTo для упорядочивания.
Нарушение этих контрактов не всегда приводит к немедленным ошибкам компиляции или очевидным исключениям. Вместо этого порождаются "мистические" баги: данные исчезают, дублируются, группируются неправильно — и всё это проявляется недетерминированно, часто только под нагрузкой или при смене окружения.
Рассмотрим класс Book, разработанный без должного внимания к контрактам:
public class Book {
private final String title;
private final String author;
private final int year;
// Конструктор, геттеры
// equals и hashCode НЕ переопределены!
}По умолчанию equals и hashCode наследуются от Object, реализуя идентичность по ссылке: два объекта равны только если это один и тот же экземпляр в памяти. Для доменной модели, где логическая эквивалентность определяется содержимым, это поведение некорректно.
groupingBy и распределение по бакетам
Коллектор groupingBy использует HashMap для организации групп. При добавлении элемента вычисляется хеш-код ключа, определяющий бакет (сегмент внутреннего массива HashMap). Если в бакете уже есть элементы, выполняется проверка equals для разрешения коллизий.
Проблема возникает при использовании ключа без корректного hashCode:
// Две логически идентичные книги
Book book1 = new Book("1984", "George Orwell", 1949);
Book book2 = new Book("1984", "George Orwell", 1949);
// Создаём поток с дублированием
List<Book> library = Arrays.asList(book1, book1, book2, book2);
// Группировка по книге как ключу
Map<Book, Long> countByBook = library.stream()
.collect(groupingBy(Function.identity(), counting()));
System.out.println(countByBook.size()); // Ожидаем 1, получаем...?
Без переопределённого hashCode book1 и book2 имеют разные хеш-коды (по умолчанию System.identityHashCode), вычисляемые из адресов памяти. Они попадают в разные бакеты HashMap. Внутри каждого бакета equals возвращает false, потому что сравнение по ссылке даёт отрицательный результат. Результат: две записи в Map вместо одной, счётчики показывают 2 для каждой "уникальной" книги.
Баг проявляется недетерминированно. При небольшом числе элементов HashMap может поместить оба объекта в один бакет случайно (из-за пересечения хешей по модулю размера таблицы), и equals обнаружит их различность, но коллизия разрешится корректно — повезло. При росте данных таблица расширяется, бакеты становятся уже, вероятность коллизии падает, баг становится стабильным. При смене JVM (разная версия, разная реализация Object.hashCode) хеш-коды меняются, и поведение меняется без изменения кода.
distinct: фильтрация дубликатов без гарантий
Операция distinct() использует LinkedHashSet для отслеживания уникальных элементов. Она полагается на тот же контракт equals/hashCode:
List<Book> uniqueBooks = library.stream()
.distinct()
.collect(toList());
Без корректных методов distinct не удаляет логические дубликаты, только ссылочные. Если book1 и book2 — разные объекты с одинаковым содержимым, оба попадут в результат. Память растёт, кэширование не работает.
Особенно опасно это в распределённых системах: объект десериализуется в разных узлах, создавая разные экземпляры в памяти. Без equals/hashCode по значению они воспринимаются как разные сущности.
toSet и потеря элементов
Collectors.toSet() возвращает HashSet, основанный на хеш-таблице. Некорректный hashCode ведёт к дублированию:
Set<Book> bookSet = library.stream()
.collect(toSet());
System.out.println(bookSet.size()); // Ожидаем 1, получаем 2
HashSet добавляет элемент, если в соответствующем бакете нет равного (по equals). Разные хеш-коды → разные бакеты → оба элемента добавлены. Множество перестаёт быть множеством.
#Java #для_новичков #beginner #stream_api #equals_hashCode
👍5
Иммутабельность ключей: бомба замедленного действия
Даже при корректных equals/hashCode опасность подстерегает в изменяемых ключах. Рассмотрим сценарий с составным ключом-автором:
HashMap хранит ключ в бакете, соответствующем хеш-коду на момент вставки. При изменении полей name или country hashCode объекта меняется, но позиция в таблице — нет. get(key) вычисляет новый хеш, ищет в другом бакете, не находит совпадения, возвращает null. Данные не потеряны физически (объект Author и список Book существуют в памяти), но недоступны через Map.
Это "утечка" памяти логического характера: структура данных разрастается, но поиск перестаёт работать. Баг проявляется далеко от места мутации, диагностика требует глубокого понимания работы хеш-таблиц.
Правильная реализация контрактов
Для Book корректные equals и hashCode должны учитывать все значимые поля:
Ключевые требования к контракту:
Рефлексивность: x.equals(x) всегда true
Симметричность: x.equals(y) тогда и только тогда, когда y.equals(x)
Транзитивность: если x.equals(y) и y.equals(z), то x.equals(z)
Консистентность: многократные вызовы дают один результат (требует иммутабельности)
Согласованность с hashCode: равные объекты имеют равные хеш-коды
Последнее требование критично для HashMap/HashSet. Нарушение (equals true, но hashCode разный) делает коллекции неработоспособными.
Рекорды как решение
Java Records (14+, стабильно в 16+) автоматически генерируют корректные equals, hashCode, toString по всем компонентам, гарантируя иммутабельность:
Использование records для ключей groupingBy, элементов distinct, элементов toSet устраняет класс ошибок, связанных с контрактами. Единственное ограничение: компоненты records также должны быть иммутабельными и с корректными контрактами. Если Author в Book — изменяемый класс, проблема сохраняется.
Диагностика в production
При подозрении на нарушение контрактов:
Логирование хеш-кодов: вывести System.identityHashCode(obj) и obj.hashCode() для "одинаковых" объектов. Различие указывает на проблему.
Проверка equals: убедиться, что obj1.equals(obj2) возвращает true для логически эквивалентных объектов.
Анализ мутаций: установить точки останова на сеттеры ключей, используемых в Map.
Использование LinkedHashMap/LinkedHashSet: сохраняют порядок вставки, облегчая отладку.
#Java #для_новичков #beginner #stream_api #equals_hashCode
Даже при корректных equals/hashCode опасность подстерегает в изменяемых ключах. Рассмотрим сценарий с составным ключом-автором:
public class Author {
private String name; // Не final! Сеттер есть!
private String country;
// equals и hashCode по всем полям
@Override public int hashCode() {
return Objects.hash(name, country);
}
}
// Группировка книг по автору
Map<Author, List<Book>> byAuthor = library.stream()
.collect(groupingBy(Book::author));
// Позже в коде...
Author key = byAuthor.keySet().iterator().next();
key.setName("Modified Name"); // Мутация ключа!
// Попытка доступа
List<Book> books = byAuthor.get(key); // null?!HashMap хранит ключ в бакете, соответствующем хеш-коду на момент вставки. При изменении полей name или country hashCode объекта меняется, но позиция в таблице — нет. get(key) вычисляет новый хеш, ищет в другом бакете, не находит совпадения, возвращает null. Данные не потеряны физически (объект Author и список Book существуют в памяти), но недоступны через Map.
Это "утечка" памяти логического характера: структура данных разрастается, но поиск перестаёт работать. Баг проявляется далеко от места мутации, диагностика требует глубокого понимания работы хеш-таблиц.
Правильная реализация контрактов
Для Book корректные equals и hashCode должны учитывать все значимые поля:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return year == book.year &&
Objects.equals(title, book.title) &&
Objects.equals(author, book.author);
}
@Override
public int hashCode() {
return Objects.hash(title, author, year);
}
Ключевые требования к контракту:
Рефлексивность: x.equals(x) всегда true
Симметричность: x.equals(y) тогда и только тогда, когда y.equals(x)
Транзитивность: если x.equals(y) и y.equals(z), то x.equals(z)
Консистентность: многократные вызовы дают один результат (требует иммутабельности)
Согласованность с hashCode: равные объекты имеют равные хеш-коды
Последнее требование критично для HashMap/HashSet. Нарушение (equals true, но hashCode разный) делает коллекции неработоспособными.
Рекорды как решение
Java Records (14+, стабильно в 16+) автоматически генерируют корректные equals, hashCode, toString по всем компонентам, гарантируя иммутабельность:
public record Book(String title, String author, int year) {}
// Автоматически: final поля, конструктор, геттеры, equals, hashCode, toStringИспользование records для ключей groupingBy, элементов distinct, элементов toSet устраняет класс ошибок, связанных с контрактами. Единственное ограничение: компоненты records также должны быть иммутабельными и с корректными контрактами. Если Author в Book — изменяемый класс, проблема сохраняется.
Диагностика в production
При подозрении на нарушение контрактов:
Логирование хеш-кодов: вывести System.identityHashCode(obj) и obj.hashCode() для "одинаковых" объектов. Различие указывает на проблему.
Проверка equals: убедиться, что obj1.equals(obj2) возвращает true для логически эквивалентных объектов.
Анализ мутаций: установить точки останова на сеттеры ключей, используемых в Map.
Использование LinkedHashMap/LinkedHashSet: сохраняют порядок вставки, облегчая отладку.
#Java #для_новичков #beginner #stream_api #equals_hashCode
👍7
Что выведет код?
#Tasks
import java.util.*;
import java.util.stream.Collectors;
public class Task100326 {
public static void main(String[] args) {
List<Item100326> items = Arrays.asList(
new Item100326(1, "A"),
new Item100326(1, "B"),
new Item100326(2, "C"),
new Item100326(2, "D")
);
Set<Item100326> set = new LinkedHashSet<>(items);
Map<Integer, List<Item100326>> grouped = items.stream()
.collect(Collectors.groupingBy(Item100326::getId));
System.out.print(set.size() + " ");
System.out.print(grouped.get(1).size() + " ");
System.out.print(grouped.get(2).get(0).getName());
}
static class Item100326 {
private int id;
private String name;
Item100326(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() { return id; }
public String getName() { return name; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item100326 item = (Item100326) o;
return id == item.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
}
#Tasks
👍3
👍3
Что такое аннотации (Annotations) и как создать свою? 🤓
Ответ:
Аннотации — это форма метаданных, добавляемая в код (классы, методы, поля) для передачи информации компилятору, инструментам сборки или фреймворкам во время выполнения.
Они начинаются с @.
Стандартные аннотации: @Override , @Deprecated , @SuppressWarnings .
Чтобы создать свою аннотацию, используют ключевое слово @interface . Можно задать мета-аннотации: @Retention (когда доступна: SOURCE, CLASS, RUNTIME), @Target (к чему применять), @Inherited и др.
Аннотации могут содержать элементы (методы без параметров и тела).
#собеседование
Ответ:
Они начинаются с @.
Стандартные аннотации:
Чтобы создать свою аннотацию, используют ключевое слово
Аннотации могут содержать элементы (методы без параметров и тела).
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 11 марта
ℹ️ Кто родился в этот день
Джозеф Карл Робнетт Ликлайдер (англ. Joseph Carl Robnett Licklider; 11 марта 1915, Сент-Луис, штат Миссури, США — 26 июня 1990, Арлингтон, штат Массачусетс, США) — известный в научной и ИТ-среде как J.C.R. или «Лик» («Lick») — американский учёный. Ранние работы были посвящены психоакустике, последующие работы — сфере информационных технологий. Один из создателей сети ARPANET, прототипа Интернета.
В 1962—1964 годы работал в ARPA, заложил основы ARPANET. Высказал идею необходимости создания объединения компьютеров в сеть со свободным доступом любого человека из любого места мира к её ресурсам. Ликлайдера называют духовным отцом всемирной сети, человеком, посеявшим семена Интернета.
Вклад Ликлайдера в возникновение Интернета огромен, он состоит из идей и принципов, а не из изобретений и технологий. Ликлайдер предвидел необходимость объединения в сеть компьютеров, имеющих простые пользовательские интерфейсы. Его идеи предвосхитили компьютерную графику, интерфейсы, работающие по принципу указания и выбора (point-and-click), цифровые библиотеки, электронную коммерцию (e-commerce), дистанционное банковское обслуживание (online banking), а также программное обеспечение, размещаемое в сети. В США его считают «Джонни Эпплсидом программирования».
Вэни́вар[a] Буш (англ. Vannevar Bush /væˈniːvɑr/, 11 марта 1890, Эверетт, Массачусетс — 28 июня 1974, Белмонт, Массачусетс) — американский ученый, инженер, разработчик аналоговых компьютеров, методолог и организатор научных исследований и научного сообщества. Советник по науке при президенте Рузвельте. Автор статьи «Как мы можем мыслить», в которой предложил прообраз гипертекстового устройства Memex (интересное дальновидение - мое прим.).
Урбе́н Жан Жозе́ф Леверье́ (фр. Urbain Jean Joseph Le Verrier; 11 марта 1811, Сен-Ло — 23 сентября 1877, Париж) — французский математик, занимавшийся небесной механикой, бо́льшую часть своей жизни проработавший в Парижской обсерватории. Его наиболее известным достижением является предсказание существования планеты Нептун, сделанное с помощью математического анализа астрономических наблюдений. По предложению Франсуа Араго он выполнил вычисления для объяснения несоответствий между наблюдаемой орбитой Урана и той, которая должна быть согласно законам Кеплера и Ньютона.
🌐 Знаковые события
1878 — Французской Академии продемонстрирован фонограф, но изобретение было объявлено шарлатанством.
#Biography #Birth_Date #Events #11марта
Джозеф Карл Робнетт Ликлайдер (англ. Joseph Carl Robnett Licklider; 11 марта 1915, Сент-Луис, штат Миссури, США — 26 июня 1990, Арлингтон, штат Массачусетс, США) — известный в научной и ИТ-среде как J.C.R. или «Лик» («Lick») — американский учёный. Ранние работы были посвящены психоакустике, последующие работы — сфере информационных технологий. Один из создателей сети ARPANET, прототипа Интернета.
В 1962—1964 годы работал в ARPA, заложил основы ARPANET. Высказал идею необходимости создания объединения компьютеров в сеть со свободным доступом любого человека из любого места мира к её ресурсам. Ликлайдера называют духовным отцом всемирной сети, человеком, посеявшим семена Интернета.
Вклад Ликлайдера в возникновение Интернета огромен, он состоит из идей и принципов, а не из изобретений и технологий. Ликлайдер предвидел необходимость объединения в сеть компьютеров, имеющих простые пользовательские интерфейсы. Его идеи предвосхитили компьютерную графику, интерфейсы, работающие по принципу указания и выбора (point-and-click), цифровые библиотеки, электронную коммерцию (e-commerce), дистанционное банковское обслуживание (online banking), а также программное обеспечение, размещаемое в сети. В США его считают «Джонни Эпплсидом программирования».
Вэни́вар[a] Буш (англ. Vannevar Bush /væˈniːvɑr/, 11 марта 1890, Эверетт, Массачусетс — 28 июня 1974, Белмонт, Массачусетс) — американский ученый, инженер, разработчик аналоговых компьютеров, методолог и организатор научных исследований и научного сообщества. Советник по науке при президенте Рузвельте. Автор статьи «Как мы можем мыслить», в которой предложил прообраз гипертекстового устройства Memex (интересное дальновидение - мое прим.).
Урбе́н Жан Жозе́ф Леверье́ (фр. Urbain Jean Joseph Le Verrier; 11 марта 1811, Сен-Ло — 23 сентября 1877, Париж) — французский математик, занимавшийся небесной механикой, бо́льшую часть своей жизни проработавший в Парижской обсерватории. Его наиболее известным достижением является предсказание существования планеты Нептун, сделанное с помощью математического анализа астрономических наблюдений. По предложению Франсуа Араго он выполнил вычисления для объяснения несоответствий между наблюдаемой орбитой Урана и той, которая должна быть согласно законам Кеплера и Ньютона.
1878 — Французской Академии продемонстрирован фонограф, но изобретение было объявлено шарлатанством.
#Biography #Birth_Date #Events #11марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #014]
Тема: Инъекция через конструктор предпочтительнее инъекции в поле. Она делает зависимости явными, поля immutable и упрощает тестирование. В новых версиях Spring @Autowired можно не ставить, если конструктор один.
Проблема: Инъекция зависимостей через поле (@Autowired над полем) — самый быстрый способ написать код, но он создает скрытые проблемы.
Поля не могут быть final, что нарушает иммутабельность и позволяет создавать объекты в частично инициализированном состоянии.
При тестировании невозможно подставить mock через конструктор — приходится использовать рефлексию (PowerMock, ReflectionTestUtils) или поднимать контекст Spring.
Также это скрывает обязательные зависимости: глядя на класс, непонятно, без каких компонентов он не может работать.
Решение: Используйте конструктор для инъекции зависимостей.
Все поля помечайте как private final — они инициализируются один раз при создании бина и гарантированно не равны null после этого.
Объяснение: Constructor injection гарантирует, что все необходимые зависимости будут предоставлены в момент создания объекта.
Пометка полей как final защищает от случайного изменения и делает класс потокобезопасным (если сами зависимости потокобезопасны).
Проверка Objects.requireNonNull() в конструкторе обеспечивает раннее обнаружение ошибок конфигурации.
При использовании одного конструктора Spring автоматически использует его для инъекции — аннотация @Autowired становится опциональной, что соответствует принципу "convention over configuration".
Для циклических зависимостей используется @Lazy.
Разработчик всегда предпочитает constructor injection, оставляя field injection только для очень специфичных случаев (например, некоторые тестовые фреймворки).
#Java #советы
Тема: Инъекция через конструктор предпочтительнее инъекции в поле. Она делает зависимости явными, поля immutable и упрощает тестирование. В новых версиях Spring @Autowired можно не ставить, если конструктор один.
Проблема: Инъекция зависимостей через поле (@Autowired над полем) — самый быстрый способ написать код, но он создает скрытые проблемы.
Поля не могут быть final, что нарушает иммутабельность и позволяет создавать объекты в частично инициализированном состоянии.
При тестировании невозможно подставить mock через конструктор — приходится использовать рефлексию (PowerMock, ReflectionTestUtils) или поднимать контекст Spring.
Также это скрывает обязательные зависимости: глядя на класс, непонятно, без каких компонентов он не может работать.
Решение: Используйте конструктор для инъекции зависимостей.
Все поля помечайте как private final — они инициализируются один раз при создании бина и гарантированно не равны null после этого.
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import java.util.Objects;
//Антипаттерн: field injection
@Service
public class BadService {
@Autowired
private UserRepository userRepository; // не final, можно забыть проинициализировать
@Autowired
private EmailService emailService; // скрытая зависимость
public void registerUser(User user) {
// может быть NullPointerException, если забыли протестировать
userRepository.save(user);
emailService.sendWelcome(user);
}
// невозможно создать объект в тесте без Spring
}
//Правильно: constructor injection
@Service
public class GoodService {
private final UserRepository userRepository;
private final EmailService emailService;
private final AuditService auditService;
// С одним конструктором @Autowired не нужен (Spring 4.3+)
public GoodService(UserRepository userRepository,
EmailService emailService,
@Lazy AuditService auditService) {
this.userRepository = Objects.requireNonNull(userRepository,
"UserRepository must not be null");
this.emailService = Objects.requireNonNull(emailService);
this.auditService = auditService; // @Lazy — прокси будет создан при первом вызове
}
public void registerUser(User user) {
userRepository.save(user);
emailService.sendWelcome(user);
auditService.log("User registered: " + user.getId());
}
}
// Для циклических зависимостей — @Lazy
@Service
class A {
private final B b;
public A(@Lazy B b) { // B создастся лениво
this.b = b;
}
}
@Service
class B {
private final A a;
public B(A a) {
this.a = a;
}
}
Объяснение: Constructor injection гарантирует, что все необходимые зависимости будут предоставлены в момент создания объекта.
Пометка полей как final защищает от случайного изменения и делает класс потокобезопасным (если сами зависимости потокобезопасны).
Проверка Objects.requireNonNull() в конструкторе обеспечивает раннее обнаружение ошибок конфигурации.
При использовании одного конструктора Spring автоматически использует его для инъекции — аннотация @Autowired становится опциональной, что соответствует принципу "convention over configuration".
Для циклических зависимостей используется @Lazy.
Разработчик всегда предпочитает constructor injection, оставляя field injection только для очень специфичных случаев (например, некоторые тестовые фреймворки).
#Java #советы
👍4
Что выведет код?
#Tasks
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class Task110326 {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Task110326.class)) {
UserService110326 service = context.getBean(UserService110326.class);
System.out.println(service.getUser());
}
}
}
@Component
class UserService110326 {
private UserRepository110326 userRepository;
@Autowired
public UserService110326(UserRepository110326 userRepository) {
this.userRepository = userRepository;
}
public String getUser() {
return userRepository.findUser();
}
}
@Component
class UserRepository110326 {
private DatabaseService110326 databaseService;
@Autowired
public UserRepository110326(DatabaseService110326 databaseService) {
this.databaseService = databaseService;
}
public String findUser() {
return "User from " + databaseService.getDataSource();
}
}
@Component
class DatabaseService110326 {
public String getDataSource() {
return "MainDB";
}
}
#Tasks
👍2
Варианты ответа:
Anonymous Quiz
56%
User from MainDB
11%
User from null
11%
Исключение NoSuchBeanDefinitionException
22%
Исключение NullPointerException
👍1
Что такое SOLID принципы? 🤓
Ответ:
SOLID — это пять основных принципов объектно-ориентированного проектирования.
S — Single Responsibility Principle (Принцип единственной ответственности): у класса должна быть только одна причина для изменения.
O — Open/Closed Principle (Открытости/закрытости): классы открыты для расширения, но закрыты для изменения.
L — Liskov Substitution Principle (Подстановки Лисков): наследники не должны нарушать поведение базового класса.
I — Interface Segregation Principle (Разделения интерфейса): много специализированных интерфейсов лучше, чем один общий.
D — Dependency Inversion Principle (Инверсии зависимостей): зависимости должны строиться на абстракциях, а не на конкретных классах.
#собеседование
Ответ:
S — Single Responsibility Principle (Принцип единственной ответственности): у класса должна быть только одна причина для изменения.
O — Open/Closed Principle (Открытости/закрытости): классы открыты для расширения, но закрыты для изменения.
L — Liskov Substitution Principle (Подстановки Лисков): наследники не должны нарушать поведение базового класса.
I — Interface Segregation Principle (Разделения интерфейса): много специализированных интерфейсов лучше, чем один общий.
D — Dependency Inversion Principle (Инверсии зависимостей): зависимости должны строиться на абстракциях, а не на конкретных классах.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологий сегодня — 12 марта
ℹ️ Кто родился в этот день
Пётр Алекса́ндрович Фре́зе (29 февраля [12 марта] 1844, Санкт-Петербург, Российская империя — 24 апреля 1918, Граново, Вышневолоцкий уезд, Тверская губерния, РСФСР) — российский изобретатель, один из конструкторов первого российского автомобиля.
🌐 Знаковые события
1889 — Элмон Строуджер из США запатентовал автоматическую телефонную станцию.
1974 — станция «Марс-6» села на Марсе, впервые передав на Землю данные об атмосфере и почве этой планеты.
#Biography #Birth_Date #Events #12марта
Пётр Алекса́ндрович Фре́зе (29 февраля [12 марта] 1844, Санкт-Петербург, Российская империя — 24 апреля 1918, Граново, Вышневолоцкий уезд, Тверская губерния, РСФСР) — российский изобретатель, один из конструкторов первого российского автомобиля.
1889 — Элмон Строуджер из США запатентовал автоматическую телефонную станцию.
1974 — станция «Марс-6» села на Марсе, впервые передав на Землю данные об атмосфере и почве этой планеты.
#Biography #Birth_Date #Events #12марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Раздел 8. Stream API и функциональный стиль в Java
Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон
Иммутабельность данных в потоке — не прихоть, а необходимость
Stream API построен на функциональной парадигме, где данные текут через преобразования, сохраняя свою сущность неизменной. Этот принцип — иммутабельность (immutability) — не эстетическая прихоть, а архитектурный фундамент, обеспечивающий предсказуемость, безопасность параллелизма и корректность ленивых вычислений.
Когда мы говорим "поток обрабатывает данные, а не изменяет их", мы противопоставляем два ментальных модуса: трансформацию как порождение нового и мутацию как изменение существующего. Императивный код часто смешивает их: цикл читает элемент, модифицирует его поля, кладёт в результат. Потоковый код разделяет: исходные данные остаются неизменными, каждая операция порождает новое представление.
Контракт неизменности источника
Исходная коллекция, породившая поток, не должна модифицироваться во время обхода. Это правило кажется очевидным, но нарушается из-за недопонимания ленивости:
Поведение неопределённо. Если поток использует Iterator (коллекция не SIZED), модификация после создания потока, но до начала обхода выбросит ConcurrentModificationException при проверке modCount. Если поток использует Spliterator с характеристикой SIZED (например, ArrayList), и терминальная операция предвыделяет размер, добавление элемента приведёт к ArrayIndexOutOfBoundsException или пропуску элементов.
Ещё опаснее модификация внутри потоковой операции:
Удаление элементов из коллекции во время её обхода — классическая ошибка, приводящая к исключению или пропуску элементов из-за сдвига индексов.
Правильный подход — создание новой коллекции вместо модификации существующей:
Иммутабельность промежуточных результатов
Не только источник, но и объекты в потоке должны быть неизменяемыми для корректности.
Рассмотрим изменяемый объект в map:
forEach здесь используется не для агрегации, а для побочного эффекта — инкремента счётчика. Исходные объекты модифицированы, что делает код сложным для отладки и тестирования. Если позже тот же список обрабатывается другим потоком, результаты зависят от порядка выполнения.
Рефакторинг к иммутабельности:
Здесь "обновление" — порождение новой версии объекта, а не модификация существующего. Старые данные сохраняются, новые создаются, история не разрушается.
#Java #для_новичков #beginner #stream_api
Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон
Иммутабельность данных в потоке — не прихоть, а необходимость
Stream API построен на функциональной парадигме, где данные текут через преобразования, сохраняя свою сущность неизменной. Этот принцип — иммутабельность (immutability) — не эстетическая прихоть, а архитектурный фундамент, обеспечивающий предсказуемость, безопасность параллелизма и корректность ленивых вычислений.
Когда мы говорим "поток обрабатывает данные, а не изменяет их", мы противопоставляем два ментальных модуса: трансформацию как порождение нового и мутацию как изменение существующего. Императивный код часто смешивает их: цикл читает элемент, модифицирует его поля, кладёт в результат. Потоковый код разделяет: исходные данные остаются неизменными, каждая операция порождает новое представление.
Контракт неизменности источника
Исходная коллекция, породившая поток, не должна модифицироваться во время обхода. Это правило кажется очевидным, но нарушается из-за недопонимания ленивости:
List<Book> library = new ArrayList<>(fetchBooks());
Stream<Book> stream = library.stream(); // Поток создан, но не активирован
// Позже, до терминальной операции:
library.add(new Book("New", "Author", 2024)); // Модификация источника!
// Теперь терминальная операция:
List<Book> result = stream.collect(toList()); // ConcurrentModificationException?
Поведение неопределённо. Если поток использует Iterator (коллекция не SIZED), модификация после создания потока, но до начала обхода выбросит ConcurrentModificationException при проверке modCount. Если поток использует Spliterator с характеристикой SIZED (например, ArrayList), и терминальная операция предвыделяет размер, добавление элемента приведёт к ArrayIndexOutOfBoundsException или пропуску элементов.
Ещё опаснее модификация внутри потоковой операции:
// Катастрофа: модификация источника изнутри потока
library.stream()
.filter(b -> b.year() < 1950)
.forEach(library::remove); // ConcurrentModificationException гарантирован
Удаление элементов из коллекции во время её обхода — классическая ошибка, приводящая к исключению или пропуску элементов из-за сдвига индексов.
Правильный подход — создание новой коллекции вместо модификации существующей:
// Правильно: фильтрация через collect, не removeIf в потоке
List<Book> modernBooks = library.stream()
.filter(b -> b.year() >= 1950)
.collect(toList());
// Или без потока, если модификация исходной нужна:
library.removeIf(b -> b.year() < 1950); // Метод коллекции, не Stream API
Иммутабельность промежуточных результатов
Не только источник, но и объекты в потоке должны быть неизменяемыми для корректности.
Рассмотрим изменяемый объект в map:
public class MutableBook {
private String title;
private int readCount; // Мутабельное поле!
public void incrementRead() { readCount++; }
}
// Антипаттерн: мутация внутри потока
List<MutableBook> books = fetchBooks();
books.stream()
.filter(b -> b.getTitle().startsWith("A"))
.forEach(MutableBook::incrementRead); //Модифицируем исходные объекты!
// Теперь исходный список books изменён — побочный эффектforEach здесь используется не для агрегации, а для побочного эффекта — инкремента счётчика. Исходные объекты модифицированы, что делает код сложным для отладки и тестирования. Если позже тот же список обрабатывается другим потоком, результаты зависят от порядка выполнения.
Рефакторинг к иммутабельности:
public record Book(String title, int readCount) {
public Book withIncrementedRead() {
return new Book(title, readCount + 1); //Новый экземпляр
}
}
// Правильно: создание новых объектов
List<Book> updatedBooks = books.stream()
.filter(b -> b.title().startsWith("A"))
.map(Book::withIncrementedRead)
.collect(toList());
// Исходный список books неизменёнЗдесь "обновление" — порождение новой версии объекта, а не модификация существующего. Старые данные сохраняются, новые создаются, история не разрушается.
#Java #для_новичков #beginner #stream_api
👍6
Контролируемые побочные эффекты
Абсолютный запрет на побочные эффекты невозможен в реальных системах. Логирование, метрики, кэширование, внешние вызовы — всё это требует взаимодействия с внешним миром. Различие между допустимым и недопустимым — в контролируемости и изоляции.
Критерии контролируемого побочного эффекта:
Идемпотентность: повторный вызов с теми же входными данными даёт тот же результат (или эквивалентный с точки зрения системы).
Потокобезопасность: при параллельном выполнении эффект корректен без дополнительной синхронизации.
Изолированность: эффект не влияет на другие элементы потока или внешнее состояние, используемое в потоке.
Необходимость: эффект является целью операции, не побочным продуктом.
Пример: атомарный счётчик для отладки
AtomicInteger обеспечивает потокобезопасность инкремента. Эффект изолирован — счётчик не влияет на обработку элементов. Он идемпотентен с точки зрения бизнес-логики (конечный результат results не зависит от счётчика). Но это всё ещё антипаттерн: peek предназначен для отладки, а не для бизнес-логики, и его поведение может измениться в будущих версиях JDK.
Более чистый подход — отделение агрегации от обработки:
Или использование teeing для подсчёта параллельно с сбором результатов.
Пример: логирование
Логирование — необходимый побочный эффект. Он потокобезопасен, если auditLog потокобезопасен. Он изолирован — не влияет на обработку заказа. Но размещение в peek рискованно: при оптимизации конвейера peek может быть пропущен или вызван менее раз, чем ожидается.
Надёжнее:
Теперь логирование гарантировано для каждого прошедшего фильтр элемента, хотя map с побочным эффектом всё ещё нарушает чистоту функции.
Граница допустимого
Правило практики: побочные эффекты допустимы только в терминальных операциях, работающих с уже потокобезопасными внешними системами. forEach для отправки сообщений в Kafka, collect в ConcurrentHashMap, reduce с атомарными операциями — приемлемы. Побочные эффекты в промежуточных операциях (map, filter, flatMap) — почти всегда ошибка.
Исключение — кэширование вычислений внутри операции, но и здесь предпочтительны чистые функции с мемоизацией вне потока.
#Java #для_новичков #beginner #stream_api
Абсолютный запрет на побочные эффекты невозможен в реальных системах. Логирование, метрики, кэширование, внешние вызовы — всё это требует взаимодействия с внешним миром. Различие между допустимым и недопустимым — в контролируемости и изоляции.
Критерии контролируемого побочного эффекта:
Идемпотентность: повторный вызов с теми же входными данными даёт тот же результат (или эквивалентный с точки зрения системы).
Потокобезопасность: при параллельном выполнении эффект корректен без дополнительной синхронизации.
Изолированность: эффект не влияет на другие элементы потока или внешнее состояние, используемое в потоке.
Необходимость: эффект является целью операции, не побочным продуктом.
Пример: атомарный счётчик для отладки
AtomicInteger processedCount = new AtomicInteger(0);
List<Result> results = items.parallelStream()
.map(this::heavyProcessing)
.peek(r -> processedCount.incrementAndGet()) // Контролируемый эффект
.collect(toList());
System.out.println("Обработано: " + processedCount.get());
AtomicInteger обеспечивает потокобезопасность инкремента. Эффект изолирован — счётчик не влияет на обработку элементов. Он идемпотентен с точки зрения бизнес-логики (конечный результат results не зависит от счётчика). Но это всё ещё антипаттерн: peek предназначен для отладки, а не для бизнес-логики, и его поведение может измениться в будущих версиях JDK.
Более чистый подход — отделение агрегации от обработки:
// Лучше: явная агрегация результата
class ProcessingResult {
final List<Result> results;
final int count;
ProcessingResult(List<Result> results, int count) {
this.results = results;
this.count = count;
}
}
ProcessingResult finalResult = items.parallelStream()
.collect(() -> new ProcessingResult(new ArrayList<>(), 0),
(acc, item) -> {
acc.results.add(process(item));
acc.count++;
},
(left, right) -> {
left.results.addAll(right.results);
left.count += right.count;
});
Или использование teeing для подсчёта параллельно с сбором результатов.
Пример: логирование
orders.stream()
.filter(o -> o.amount().compareTo(THRESHOLD) > 0)
.peek(o -> auditLog.record("Крупный заказ", o.id())) // Побочный эффект
.map(this::processLargeOrder)
.collect(toList());
Логирование — необходимый побочный эффект. Он потокобезопасен, если auditLog потокобезопасен. Он изолирован — не влияет на обработку заказа. Но размещение в peek рискованно: при оптимизации конвейера peek может быть пропущен или вызван менее раз, чем ожидается.
Надёжнее:
List<ProcessedOrder> processed = orders.stream()
.filter(o -> o.amount().compareTo(THRESHOLD) > 0)
.map(o -> {
auditLog.record("Крупный заказ", o.id()); // Явный эффект в map
return processLargeOrder(o);
})
.collect(toList());
Теперь логирование гарантировано для каждого прошедшего фильтр элемента, хотя map с побочным эффектом всё ещё нарушает чистоту функции.
Граница допустимого
Правило практики: побочные эффекты допустимы только в терминальных операциях, работающих с уже потокобезопасными внешними системами. forEach для отправки сообщений в Kafka, collect в ConcurrentHashMap, reduce с атомарными операциями — приемлемы. Побочные эффекты в промежуточных операциях (map, filter, flatMap) — почти всегда ошибка.
Исключение — кэширование вычислений внутри операции, но и здесь предпочтительны чистые функции с мемоизацией вне потока.
#Java #для_новичков #beginner #stream_api
👍5
Что выведет код?
#Tasks
import java.util.*;
import java.util.stream.*;
public class Task120326 {
public static void main(String[] args) {
List<Person120326> people = new ArrayList<>(Arrays.asList(
new Person120326("Alice", 25),
new Person120326("Bob", 30),
new Person120326("Charlie", 20)
));
List<String> names = people.stream()
.map(p -> p.name)
.sorted((a, b) -> {
people.get(0).age = 999;
return a.compareTo(b);
})
.collect(Collectors.toList());
System.out.println(people);
System.out.println(names);
}
static class Person120326 {
String name;
int age;
Person120326(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return name + ":" + age;
}
}
}
#Tasks
👍2
Что такое Dependency Injection (DI) и Inversion of Control (IoC)? 🤓
Ответ:
Inversion of Control (IoC) — это общий принцип, при котором фреймворк управляет потоком программы и объектами, а не наоборот.
Dependency Injection (DI) — это конкретная реализация IoC, при которой объекты получают свои зависимости извне (через конструктор, сеттер или интерфейс), а не создают их сами.
Это уменьшает связанность кода, повышает тестируемость (легко подставить моки) и гибкость.
Главный контейнер DI в Java-мире — это Spring IoC-контейнер.
#собеседование
Ответ:
Dependency Injection (DI) — это конкретная реализация IoC, при которой объекты получают свои зависимости извне (через конструктор, сеттер или интерфейс), а не создают их сами.
Это уменьшает связанность кода, повышает тестируемость (легко подставить моки) и гибкость.
Главный контейнер DI в Java-мире — это Spring IoC-контейнер.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологий сегодня — 13 марта
ℹ️ Кто родился в этот день
Джон Хазбрук Ван Флек (англ. John Hasbrouck van Vleck; 13 марта 1899, Мидлтаун — 27 октября 1980, Кембридж, Массачусетс) — американский физик, лауреат Нобелевской премии по физике 1977 года. Работы по квантовой теории атомной структуры, магнетизму, теории валентности, изучению атомных и молекулярных спектров, ферро- и ферримагнитному резонансу. Один из создателей современных представителей о магнетизме вещества. Разработал квантовомеханическую теорию диа- и парамагнетизма (1926—1928), получил парамагнитную добавку к диамагнитной восприимчивости несимметричных атомов и молекул, названную ванфлековским парамагнетизмом (1927). Развил теорию внутрикристаллического (лигандного) поля. Дал детальную трактовку антиферромагнетизма (1941), термодинамическую теорию молекулярного поля для антиферромагнетиков, развил гейзенберговскую модель локализованных спинов и предложил эффективный спиновый гамильтониан.
🌐 Знаковые события
1781 — Английский астроном Вильям Гершель с помощью собственноручно изготовленного телескопа открыл седьмую планету Солнечной системы — Уран. Правда, первоначально он принял её за комету. Когда выяснилось, что это неизвестная ранее планета, он получил медаль Королевского общества и должность придворного астронома. Сам Гершель назвал планету в честь своего высокого покровителя короля Георга III «Звездой Георга», короткое время она носила имя первооткрывателя, пока немецкий астроном Иоганн Боде не придумал для неё название Уран.
1930 — американский астроном Клайд Томбо объявил в докладе об открытии им 18 февраля девятой планеты Солнечной системы. Доклад был приурочен к 75-летию основателя и первого директора крупнейшей частной обсерватории в США, Персиваля Лоуэлла, который многие годы потратил на поиски предсказанной им девятой планеты. На следующий день 14 марта Венеция Бёрни, одиннадцатилетняя школьница из Оксфорда, предложит имя для новой планеты, и в мае будет опубликовано официальное имя — Плутон.
1989 — была изобретена Всемирная паутина (World Wide Web, WWW), более известная как Интернет. Изобретателем Интернета считается английский учёный Тим Бернерс-Ли и его коллеги, работавшие в Европейском совете по ядерным исследованиям (CERN), передали начальнику своего отдела документ, озаглавленный «Информационный менеджмент: некоторые предложения», в котором были заложены основные принципы WWW.
#Biography #Birth_Date #Events #13марта
Джон Хазбрук Ван Флек (англ. John Hasbrouck van Vleck; 13 марта 1899, Мидлтаун — 27 октября 1980, Кембридж, Массачусетс) — американский физик, лауреат Нобелевской премии по физике 1977 года. Работы по квантовой теории атомной структуры, магнетизму, теории валентности, изучению атомных и молекулярных спектров, ферро- и ферримагнитному резонансу. Один из создателей современных представителей о магнетизме вещества. Разработал квантовомеханическую теорию диа- и парамагнетизма (1926—1928), получил парамагнитную добавку к диамагнитной восприимчивости несимметричных атомов и молекул, названную ванфлековским парамагнетизмом (1927). Развил теорию внутрикристаллического (лигандного) поля. Дал детальную трактовку антиферромагнетизма (1941), термодинамическую теорию молекулярного поля для антиферромагнетиков, развил гейзенберговскую модель локализованных спинов и предложил эффективный спиновый гамильтониан.
1781 — Английский астроном Вильям Гершель с помощью собственноручно изготовленного телескопа открыл седьмую планету Солнечной системы — Уран. Правда, первоначально он принял её за комету. Когда выяснилось, что это неизвестная ранее планета, он получил медаль Королевского общества и должность придворного астронома. Сам Гершель назвал планету в честь своего высокого покровителя короля Георга III «Звездой Георга», короткое время она носила имя первооткрывателя, пока немецкий астроном Иоганн Боде не придумал для неё название Уран.
1930 — американский астроном Клайд Томбо объявил в докладе об открытии им 18 февраля девятой планеты Солнечной системы. Доклад был приурочен к 75-летию основателя и первого директора крупнейшей частной обсерватории в США, Персиваля Лоуэлла, который многие годы потратил на поиски предсказанной им девятой планеты. На следующий день 14 марта Венеция Бёрни, одиннадцатилетняя школьница из Оксфорда, предложит имя для новой планеты, и в мае будет опубликовано официальное имя — Плутон.
1989 — была изобретена Всемирная паутина (World Wide Web, WWW), более известная как Интернет. Изобретателем Интернета считается английский учёный Тим Бернерс-Ли и его коллеги, работавшие в Европейском совете по ядерным исследованиям (CERN), передали начальнику своего отдела документ, озаглавленный «Информационный менеджмент: некоторые предложения», в котором были заложены основные принципы WWW.
#Biography #Birth_Date #Events #13марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3