История технологий сегодня — 04 марта
ℹ️ Кто родился в этот день
Джеффри («Джефф») Колин Тутилл (4 марта 1922 г. – 26 октября 2017 г.) — английский компьютерный учёный который был инженером-электронщиком и специалистом по информатике, работавшим на кафедре электротехники Манчестерского университета с Фредди Уильямсом и Томом Килберном над разработкой Manchester Baby, «первого в мире полностью электронного компьютера с хранимой программой».
Гео́ргий Анто́нович Га́мов (также известен как Джордж Гамов, англ. George Gamow; 20 февраля (4 марта) 1904 года, Одесса — 19 августа 1968, Боулдер) — советский и американский физик-теоретик, астрофизик и популяризатор науки. Гамов известен своими работами по квантовой механике, атомной и ядерной физике, астрофизике, космологии, биологии, создатель уравнения, объясняющего теорию туннельного эффекта. Он является автором первой количественной теории альфа-распада, одним из основоположников теории «горячей Вселенной» и одним из пионеров применения ядерной физики к вопросам эволюции звёзд. Он впервые чётко сформулировал проблему генетического кода. Широкую известность Гамову принесли его научно-популярные произведения, в которых живым и доступным языком рассказывается о современных научных представлениях.
🌐 Знаковые события
1977 — в Лос-Аламосе установлен суперкомпьютер Cray-1.
#Biography #Birth_Date #Events #04марта
Джеффри («Джефф») Колин Тутилл (4 марта 1922 г. – 26 октября 2017 г.) — английский компьютерный учёный который был инженером-электронщиком и специалистом по информатике, работавшим на кафедре электротехники Манчестерского университета с Фредди Уильямсом и Томом Килберном над разработкой Manchester Baby, «первого в мире полностью электронного компьютера с хранимой программой».
Гео́ргий Анто́нович Га́мов (также известен как Джордж Гамов, англ. George Gamow; 20 февраля (4 марта) 1904 года, Одесса — 19 августа 1968, Боулдер) — советский и американский физик-теоретик, астрофизик и популяризатор науки. Гамов известен своими работами по квантовой механике, атомной и ядерной физике, астрофизике, космологии, биологии, создатель уравнения, объясняющего теорию туннельного эффекта. Он является автором первой количественной теории альфа-распада, одним из основоположников теории «горячей Вселенной» и одним из пионеров применения ядерной физики к вопросам эволюции звёзд. Он впервые чётко сформулировал проблему генетического кода. Широкую известность Гамову принесли его научно-популярные произведения, в которых живым и доступным языком рассказывается о современных научных представлениях.
1977 — в Лос-Аламосе установлен суперкомпьютер Cray-1.
#Biography #Birth_Date #Events #04марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Раздел 8. Stream API и функциональный стиль в Java
Глава 4: Искусство агрегации. Коллекторы и группировки
groupingBy и многоуровневая агрегация
Коллектор groupingBy — один из наиболее выразительных инструментов Stream API. Он трансформирует поток элементов в Map, где ключи — значения классификатора, а значения — списки элементов, соответствующих каждому ключу. Это операция GROUP BY из SQL, перенесённая в мир коллекций, но с большей гибкостью благодаря композиции downstream коллекторов.
Простейшая форма группировки создаёт Map<K, List<T>>:
Здесь Book::author — функция классификации, извлекающая ключ. Результат: ассоциативный массив, где каждому автору соответствует список его книг. Порядок книг в списках сохраняет порядок встречи в исходном потоке (если поток ORDERED).
Downstream коллекторы: агрегация внутри группы
Вторая перегрузка groupingBy принимает downstream коллектор — рецепт обработки элементов каждой группы. Это позволяет заменить список на любую другую свёртку: счётчик, множество, статистику, опциональный экстремум.
Подсчёт элементов:
counting() — коллектор, возвращающий Long с количеством элементов. Результат не хранит сами книги, только их число по авторам.
Трансформация перед агрегацией:
mapping — адаптер коллектора, применяющий функцию к каждому элементу перед передачей downstream коллектору. Здесь Book превращается в String (название), и результаты собираются в список. Результат: автор → список названий, без промежуточных объектов Book.
Поиск экстремума:
maxBy возвращает Optional<T>, потому что группа может быть пустой (теоретически, при фильтрации до группировки). comparing(Book::year) создаёт компаратор для извлечения максимума. Для получения "самой старой книги" используется minBy или comparing(Book::year).reversed().
Обработка Optional в результате требует осторожности:
```
// Извлечение значения с дефолтом
Map<String, Book> newestOrNullByAuthor = library.stream()
.collect(groupingBy(
Book::author,
collectingAndThen(
maxBy(comparing(Book::year)),
opt -> opt.orElse(null) // или orElseThrow, orElseGet
)
));
collectingAndThen оборачивает downstream коллектор, применяя финишную функцию к его результату. Здесь Optional<Book> превращается в Book или null.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
Глава 4: Искусство агрегации. Коллекторы и группировки
groupingBy и многоуровневая агрегация
Коллектор groupingBy — один из наиболее выразительных инструментов Stream API. Он трансформирует поток элементов в Map, где ключи — значения классификатора, а значения — списки элементов, соответствующих каждому ключу. Это операция GROUP BY из SQL, перенесённая в мир коллекций, но с большей гибкостью благодаря композиции downstream коллекторов.
Простейшая форма группировки создаёт Map<K, List<T>>:
Map<String, List<Book>> booksByAuthor = library.stream()
.collect(groupingBy(Book::author));
Здесь Book::author — функция классификации, извлекающая ключ. Результат: ассоциативный массив, где каждому автору соответствует список его книг. Порядок книг в списках сохраняет порядок встречи в исходном потоке (если поток ORDERED).
Downstream коллекторы: агрегация внутри группы
Вторая перегрузка groupingBy принимает downstream коллектор — рецепт обработки элементов каждой группы. Это позволяет заменить список на любую другую свёртку: счётчик, множество, статистику, опциональный экстремум.
Подсчёт элементов:
Map<String, Long> bookCountByAuthor = library.stream()
.collect(groupingBy(Book::author, counting()));
counting() — коллектор, возвращающий Long с количеством элементов. Результат не хранит сами книги, только их число по авторам.
Трансформация перед агрегацией:
Map<String, List<String>> titlesByAuthor = library.stream()
.collect(groupingBy(
Book::author,
mapping(Book::title, toList())
));
mapping — адаптер коллектора, применяющий функцию к каждому элементу перед передачей downstream коллектору. Здесь Book превращается в String (название), и результаты собираются в список. Результат: автор → список названий, без промежуточных объектов Book.
Поиск экстремума:
Map<String, Optional<Book>> newestByAuthor = library.stream()
.collect(groupingBy(
Book::author,
maxBy(comparing(Book::year))
));
maxBy возвращает Optional<T>, потому что группа может быть пустой (теоретически, при фильтрации до группировки). comparing(Book::year) создаёт компаратор для извлечения максимума. Для получения "самой старой книги" используется minBy или comparing(Book::year).reversed().
Обработка Optional в результате требует осторожности:
```
// Извлечение значения с дефолтом
Map<String, Book> newestOrNullByAuthor = library.stream()
.collect(groupingBy(
Book::author,
collectingAndThen(
maxBy(comparing(Book::year)),
opt -> opt.orElse(null) // или orElseThrow, orElseGet
)
));
collectingAndThen оборачивает downstream коллектор, применяя финишную функцию к его результату. Здесь Optional<Book> превращается в Book или null.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
👍5
Многоуровневая группировка: вложенные Map
groupingBy можно вкладывать сам в себя, создавая иерархические структуры Map<K1, Map<K2, V>>:
Читаемость страдает от глубокой вложенности, но структура данных точно отражает бизнес-логику: жанр содержит авторов, автор содержит книги.
Альтернатива — составной ключ:
Выбор между вложенными Map и составными ключами зависит от паттерна доступа: если часто нужны все книги жанра независимо от автора, вложенность предпочтительнее; если доступ всегда по паре жанр-автор, составной ключ проще.
Сложные downstream агрегации
Комбинация адаптеров коллекторов позволяет строить изощрённые агрегации без промежуточных коллекций:
Этот пример демонстрирует глубину композиции: groupingBy → collectingAndThen → teeing → вложенный teeing. Читаемость низкая, но вычисление эффективно: один проход по потоку, без промежуточных структур.
teeing: параллельная агрегация
Коллектор teeing (Java 12+) решает задачу вычисления нескольких независимых метрик за один проход. Он принимает два downstream коллектора и функцию слияния их результатов.
Вложенность teeing позволяет агрегировать более двух метрик, но быстро становится громоздкой. Для трёх и более метрик предпочтительнее кастомный коллектор или несколько проходов (если поток позволяет повторное чтение).
Преимущество teeing в ленивости и параллелизме: оба downstream коллектора получают один и тот же поток элементов, но поддерживают независимое внутреннее состояние. При параллельном выполнении их комбайнеры работают независимо, финальное слияние происходит только на уровне teeing.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
groupingBy можно вкладывать сам в себя, создавая иерархические структуры Map<K1, Map<K2, V>>:
// Группировка сначала по жанру, затем по автору
Map<Genre, Map<String, List<Book>>> byGenreThenAuthor = library.stream()
.collect(groupingBy(
Book::genre,
groupingBy(Book::author)
));
Читаемость страдает от глубокой вложенности, но структура данных точно отражает бизнес-логику: жанр содержит авторов, автор содержит книги.
Альтернатива — составной ключ:
record GenreAuthor(Genre genre, String author) {}
Map<GenreAuthor, List<Book>> byCompositeKey = library.stream()
.collect(groupingBy(
b -> new GenreAuthor(b.genre(), b.author())
));Выбор между вложенными Map и составными ключами зависит от паттерна доступа: если часто нужны все книги жанра независимо от автора, вложенность предпочтительнее; если доступ всегда по паре жанр-автор, составной ключ проще.
Сложные downstream агрегации
Комбинация адаптеров коллекторов позволяет строить изощрённые агрегации без промежуточных коллекций:
// Для каждого автора: множество жанров, средняя цена, самая старая книга
record AuthorStats(Set<Genre> genres, double avgPrice, Book oldest) {}
Map<String, AuthorStats> statsByAuthor = library.stream()
.collect(groupingBy(
Book::author,
collectingAndThen(
teeing(
teeing(
mapping(Book::genre, toSet()), // Набор жанров
averagingDouble(Book::price), // Средняя цена
Pair::new // Временная пара
),
minBy(comparing(Book::year)), // Самая старая книга
(pair, oldestOpt) -> new AuthorStats(
pair.first(),
pair.second(),
oldestOpt.orElseThrow()
)
),
Function.identity()
)
));
Этот пример демонстрирует глубину композиции: groupingBy → collectingAndThen → teeing → вложенный teeing. Читаемость низкая, но вычисление эффективно: один проход по потоку, без промежуточных структур.
teeing: параллельная агрегация
Коллектор teeing (Java 12+) решает задачу вычисления нескольких независимых метрик за один проход. Он принимает два downstream коллектора и функцию слияния их результатов.
record PriceStats(double min, double max, double average) {}
PriceStats stats = products.stream()
.collect(teeing(
minBy(comparing(Product::price)),
teeing(
maxBy(comparing(Product::price)),
averagingDouble(Product::price),
(maxOpt, avg) -> new Pair<>(maxOpt, avg)
),
(minOpt, pair) -> new PriceStats(
minOpt.map(Product::price).orElse(0.0),
pair.first().map(Product::price).orElse(0.0),
pair.second()
)
));Вложенность teeing позволяет агрегировать более двух метрик, но быстро становится громоздкой. Для трёх и более метрик предпочтительнее кастомный коллектор или несколько проходов (если поток позволяет повторное чтение).
Преимущество teeing в ленивости и параллелизме: оба downstream коллектора получают один и тот же поток элементов, но поддерживают независимое внутреннее состояние. При параллельном выполнении их комбайнеры работают независимо, финальное слияние происходит только на уровне teeing.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
👍5
partitionBy: группировка по предикату
Специализированный родственник groupingBy — partitioningBy. Он разделяет поток ровно на две группы по булевому предикату, возвращая Map<Boolean, List<T>>:
В отличие от groupingBy, partitioningBy гарантирует наличие обеих ключей в Map (даже если одна группа пуста).
Это удобно для алгоритмов, требующих обе ветви:
Производительность и аллокации
Многоуровневая агрегация через groupingBy создаёт значительное давление на GC: каждая группа — отдельный список, каждый уровень вложенности — дополнительные Map. Для больших потоков (миллионы элементов, тысячи групп) это критично.
Оптимизации:
Использование примитивных специализаций: groupingByInt, groupingByLong для ключей-примитивов (через mapToInt + boxed, если необходимо).
Предварительная фильтрация: уменьшение входного потока до группировки снижает число создаваемых контейнеров.
Кастомные коллекторы с примитивными аккумуляторами: вместо List<Book> использовать IntSummaryStatistics для подсчёта суммарных страниц, если сами объекты не нужны.
parallelStream с осторожностью: группировка требует слияния Map от разных потоков, что дорого при большом числе групп. Эффективна только при тяжёлой downstream агрегации и редких ключах.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
Специализированный родственник groupingBy — partitioningBy. Он разделяет поток ровно на две группы по булевому предикату, возвращая Map<Boolean, List<T>>:
Map<Boolean, List<Book>> partitioned = library.stream()
.collect(partitioningBy(b -> b.year() >= 2000));
List<Book> modernBooks = partitioned.get(true); // 2000 и позже
List<Book> classicBooks = partitioned.get(false); // До 2000
В отличие от groupingBy, partitioningBy гарантирует наличие обеих ключей в Map (даже если одна группа пуста).
Это удобно для алгоритмов, требующих обе ветви:
Map<Boolean, Long> counts = library.stream()
.collect(partitioningBy(
b -> b.year() >= 2000,
counting() // downstream коллектор поддерживается
));
long modernCount = counts.get(true);
long classicCount = counts.get(false); // Никогда null, минимум 0
Производительность и аллокации
Многоуровневая агрегация через groupingBy создаёт значительное давление на GC: каждая группа — отдельный список, каждый уровень вложенности — дополнительные Map. Для больших потоков (миллионы элементов, тысячи групп) это критично.
Оптимизации:
Использование примитивных специализаций: groupingByInt, groupingByLong для ключей-примитивов (через mapToInt + boxed, если необходимо).
Предварительная фильтрация: уменьшение входного потока до группировки снижает число создаваемых контейнеров.
Кастомные коллекторы с примитивными аккумуляторами: вместо List<Book> использовать IntSummaryStatistics для подсчёта суммарных страниц, если сами объекты не нужны.
parallelStream с осторожностью: группировка требует слияния Map от разных потоков, что дорого при большом числе групп. Эффективна только при тяжёлой downstream агрегации и редких ключах.
#Java #для_новичков #beginner #stream_api #Collectors #groupingBy
👍4
Что выведет код?
#Tasks
import java.util.*;
import java.util.stream.*;
public class Task040326 {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "apricot", "blueberry", "cherry");
Map<Integer, Long> result = words.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
System.out.println(result);
}
}
#Tasks
👍3
Варианты ответа:
Anonymous Quiz
78%
{5=1, 6=2, 7=1, 9=1}
17%
{5=2, 6=1, 7=1, 9=1}
6%
{5=1, 6=1, 7=2, 9=1}
0%
{5=1, 6=2, 7=2, 9=0}
👍2
Какие существуют виды ClassLoader'ов и как работает делегирование? 🤓
Ответ:
В Java три встроенных ClassLoader'а, работающих по принципу делегирования (parent-delegation model).
1) Bootstrap ClassLoader (загрузчик начальной загрузки) — самый верхний, загружает ядро JDK из rt.jar.
2) Extension ClassLoader — загружает классы из jre/lib/ext.
3) System/Application ClassLoader — загружает классы из classpath приложения. Когда запрашивается загрузка класса, загрузчик сначала делегирует запрос родителю. Если родитель не может найти класс, только тогда загрузчик пытается загрузить его сам. Это обеспечивает безопасность (системные классы не подменяются)..
#собеседование
Ответ:
1) Bootstrap ClassLoader (загрузчик начальной загрузки) — самый верхний, загружает ядро JDK из rt.jar.
2) Extension ClassLoader — загружает классы из jre/lib/ext.
3) System/Application ClassLoader — загружает классы из classpath приложения. Когда запрашивается загрузка класса, загрузчик сначала делегирует запрос родителю. Если родитель не может найти класс, только тогда загрузчик пытается загрузить его сам. Это обеспечивает безопасность (системные классы не подменяются)..
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
8. Логирование и ELK-стек: как расследуют инциденты в production
Мы прошли огромный путь по созданию Observability для микросервисов: у нас уже есть метрики в Prometheus/Grafana (чтобы знать, ЧТО происходит) и трассировка в Jaeger (чтобы знать, ГДЕ искать проблему). Но когда случается инцидент, мы всё ещё «тыкаемся как слепые котята», потому что не знаем главного — ПОЧЕМУ это произошло.
В этом видео мы закроем последний, самый важный пробел — научимся работать с логами.
Из этого видео вы узнаете:
🔵 Почему чтение логов по контейнерам — это боль и архаизм. Чем структурированные логи отличаются от текстовых и почему без них вы не расследуете инциденты, а гадаете на кофейной гуще.
🔵 MDC — сердце структурированных логов. Что такое Mapped Diagnostic Context, как он работает (спойлер: это ThreadLocal) и как наступить на грабли с асинхронностью. Разберем паттерн TaskDecorator, который спасет ваши нервы и данные.
🔵 JSON-логи без боли. Настройка logstash-logback-encoder, правильное форматирование стектрейсов (ShortenedThrowableConverter) и два способа доставки логов: TCP против stdout + Filebeat.
🔵 ELK-стек под капотом. Разворачиваем Elasticsearch, Logstash и Kibana в Docker. Схема работы, компоненты и их роль.
🔵 Расследование инцидента. Живая демонстрация: от графика в Grafana до конкретной строчки лога с ошибкой через Jaeger и Kibana. Увидите, как traceId связывает три столпа Observability воедино.
Исходный код проекта на GitHub очень ждет Ваших звезд☺️
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
❗️ ❗️ ❗️ Огромная просьба - если Вам понравилась моя работа, распространите эту серию по всем доступным вам местам: телеграм, discord и прочим каналам. Буду крайне благодарен ❗️ ❗️ ❗️
Мы прошли огромный путь по созданию Observability для микросервисов: у нас уже есть метрики в Prometheus/Grafana (чтобы знать, ЧТО происходит) и трассировка в Jaeger (чтобы знать, ГДЕ искать проблему). Но когда случается инцидент, мы всё ещё «тыкаемся как слепые котята», потому что не знаем главного — ПОЧЕМУ это произошло.
В этом видео мы закроем последний, самый важный пробел — научимся работать с логами.
Из этого видео вы узнаете:
Исходный код проекта на GitHub очень ждет Ваших звезд
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍2
История технологий сегодня — 05 марта
ℹ️ Кто родился в этот день
Владимир Константинович Ле́вин (5 марта 1929, Москва — 4 февраля 2026) — советский и российский учёный в области кибернетики, академик РАН (2003), специалист в области вычислительной техники и элементной базы вычислительных машин. Научный руководитель Федерального государственного унитарного предприятия «Научно-исследовательский институт „КВАНТ“». Лауреат Ленинской премии и Государственной премии СССР.
Момофуку Андо (яп. 安藤 百福 Андо: Момофуку, при рождении Го Пек-Хок, 5 марта 1910, Пуцзы, Японская империя — 5 января 2007, Икеда, Осака) — изобретатель и бизнесмен, основавший компанию Nissin Foods. Он известен как изобретатель лапши быстрого приготовления и создатель торговых марок Top Ramen и Cup Noodles. В опросе общественного мнения в Японии, проведённом в 2000 году, изобретение Момофуку Андо лапши быстрого приготовления назвали главным японским изобретением XX века.
🌐 Знаковые события
1979 — космический аппарат «Вояджер-1» достиг планеты Юпитер.
#Biography #Birth_Date #Events #05марта
Владимир Константинович Ле́вин (5 марта 1929, Москва — 4 февраля 2026) — советский и российский учёный в области кибернетики, академик РАН (2003), специалист в области вычислительной техники и элементной базы вычислительных машин. Научный руководитель Федерального государственного унитарного предприятия «Научно-исследовательский институт „КВАНТ“». Лауреат Ленинской премии и Государственной премии СССР.
Момофуку Андо (яп. 安藤 百福 Андо: Момофуку, при рождении Го Пек-Хок, 5 марта 1910, Пуцзы, Японская империя — 5 января 2007, Икеда, Осака) — изобретатель и бизнесмен, основавший компанию Nissin Foods. Он известен как изобретатель лапши быстрого приготовления и создатель торговых марок Top Ramen и Cup Noodles. В опросе общественного мнения в Японии, проведённом в 2000 году, изобретение Момофуку Андо лапши быстрого приготовления назвали главным японским изобретением XX века.
1979 — космический аппарат «Вояджер-1» достиг планеты Юпитер.
#Biography #Birth_Date #Events #05марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #012]
Тема: При итерации по Map для получения и ключа, и значения используйте entrySet(), а не keySet() с последующим get().
Проблема: Итерация по Map через keySet() с последующим вызовом get(key) для получения значения создает избыточную нагрузку.
Каждый вызов get() выполняет полноценный поиск по хэш-таблице: повторное вычисление хэш-кода, поиск в корзине (bucket) и сравнение ключей. При обходе всей мапы это означает двукратное выполнение работы — один раз для получения ключа из keySet(), и второй раз внутри get().
Для больших мап или частых итераций это приводит к неоправданному снижению производительности, особенно если хэш-функция сложная или коллизии часты.
Решение: Используйте entrySet(), который возвращает набор пар Map.Entry<K, V>. Каждый entry содержит и ключ, и значение, полученные за одну операцию доступа. Методы entry.getKey() и entry.getValue() работают с уже извлеченными данными и не выполняют повторного поиска по мапе. Это оптимальный способ итерации, когда нужны обе составляющие пары.
Объяснение: keySet() возвращает представление ключей, не содержащее значений.
При вызове get(key) происходит повторный проход по структуре данных. Для HashMap это повторное вычисление хэша и поиск в корзине; для TreeMap — логарифмический поиск; для LinkedHashMap — повторный обход цепочки. entrySet() предоставляет прямой доступ к паре, которая была извлечена при обходе внутренней структуры.
Начиная с Java 8, метод forEach напрямую принимает би-функцию (ключ, значение), что делает код еще более лаконичным и читаемым.
Разработчик всегда выбирает entrySet() или forEach при работе с обоими компонентами записи и использует keySet() только когда нужны исключительно ключи.
#Java #советы
Тема: При итерации по Map для получения и ключа, и значения используйте entrySet(), а не keySet() с последующим get().
Проблема: Итерация по Map через keySet() с последующим вызовом get(key) для получения значения создает избыточную нагрузку.
Каждый вызов get() выполняет полноценный поиск по хэш-таблице: повторное вычисление хэш-кода, поиск в корзине (bucket) и сравнение ключей. При обходе всей мапы это означает двукратное выполнение работы — один раз для получения ключа из keySet(), и второй раз внутри get().
Для больших мап или частых итераций это приводит к неоправданному снижению производительности, особенно если хэш-функция сложная или коллизии часты.
Решение: Используйте entrySet(), который возвращает набор пар Map.Entry<K, V>. Каждый entry содержит и ключ, и значение, полученные за одну операцию доступа. Методы entry.getKey() и entry.getValue() работают с уже извлеченными данными и не выполняют повторного поиска по мапе. Это оптимальный способ итерации, когда нужны обе составляющие пары.
import java.util.*;
public class Advice050326 {
private static final Map<String, Integer> SCORES = new HashMap<>();
static {
SCORES.put("Alice", 95);
SCORES.put("Bob", 87);
SCORES.put("Charlie", 92);
SCORES.put("Diana", 98);
}
//Антипаттерн: итерация через keySet() + get()
public static void badIteration() {
System.out.println("Bad iteration (keySet + get):");
for (String key : SCORES.keySet()) {
// get() выполняет ПОЛНЫЙ поиск по мапе
Integer value = SCORES.get(key);
System.out.println(key + " = " + value);
}
}
//Правильно: итерация через entrySet()
public static void goodIteration() {
System.out.println("Good iteration (entrySet):");
for (Map.Entry<String, Integer> entry : SCORES.entrySet()) {
// Данные уже извлечены, повторного поиска нет
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
}
//Java 8+: forEach с лямбдой
public static void lambdaIteration() {
System.out.println("Lambda iteration (forEach):");
SCORES.forEach((key, value) ->
System.out.println(key + " = " + value)
);
}
//Модификация значений через entrySet
public static void modifyValues() {
Map<String, Integer> mutableMap = new HashMap<>(SCORES);
// Увеличить баллы всем на 5
for (Map.Entry<String, Integer> entry : mutableMap.entrySet()) {
entry.setValue(entry.getValue() + 5); // Работает!
}
// Через keySet так не сделать
System.out.println("Modified: " + mutableMap);
}
public static void main(String[] args) {
badIteration();
goodIteration();
lambdaIteration();
modifyValues();
}
}
Объяснение: keySet() возвращает представление ключей, не содержащее значений.
При вызове get(key) происходит повторный проход по структуре данных. Для HashMap это повторное вычисление хэша и поиск в корзине; для TreeMap — логарифмический поиск; для LinkedHashMap — повторный обход цепочки. entrySet() предоставляет прямой доступ к паре, которая была извлечена при обходе внутренней структуры.
Начиная с Java 8, метод forEach напрямую принимает би-функцию (ключ, значение), что делает код еще более лаконичным и читаемым.
Разработчик всегда выбирает entrySet() или forEach при работе с обоими компонентами записи и использует keySet() только когда нужны исключительно ключи.
#Java #советы
👍6
Что выведет код?
#Tasks
import java.util.*;
public class Task050326 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Set<String> keys = map.keySet();
for (String key : keys) {
if (key.equals("b")) {
map.remove("c");
}
System.out.print(key + "=" + map.get(key) + " ");
}
}
}
#Tasks
👍2
Варианты ответа:
Anonymous Quiz
0%
a=1 b=2 c=3
29%
a=1 b=2
17%
a=1 b=2 null
54%
ConcurrentModificationException
👍2
Что такое Future и Callable? Чем Callable отличается от Runnable? 🤓
Ответ:
Оба интерфейса представляют задачи для выполнения в отдельном потоке.
Runnable существует с Java 1.0, его метод run() не возвращает результат и не может бросить проверяемое исключение.
Callable<V> появился в Java 5, его метод call() возвращает результат типа V и может бросать проверяемые исключения.
Callable удобно использовать с ExecutorService, который возвращает объект Future, позволяющий отслеживать состояние задачи, ожидать ее завершения и получать результат методом get().
#собеседование
Ответ:
Runnable существует с Java 1.0, его метод run() не возвращает результат и не может бросить проверяемое исключение.
Callable<V> появился в Java 5, его метод call() возвращает результат типа V и может бросать проверяемые исключения.
Callable удобно использовать с ExecutorService, который возвращает объект Future, позволяющий отслеживать состояние задачи, ожидать ее завершения и получать результат методом get().
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 06 марта
ℹ️ Кто родился в этот день
Адам Осборн (англ. Adam Osborne; 6 марта 1939, Бангкок, Таиланд — 18 марта 2003, Кодаиканал[англ.], Индия) — американский предприниматель британского происхождения, издатель книг и создатель программного обеспечения, компьютерный дизайнер, основавший несколько компаний в США и других странах. Создатель первого коммерчески успешного портативного компьютера Osborne 1.
🌐 Знаковые события
1869 — Дмитрий Менделеев представил периодическую систему химических элементов Русскому химическому обществу.
1983 — стартовали продажи Motorola DynaTAC 8000X, первого коммерчески успешного сотового телефона.
1992 — в день рождения великого художника и скульптора Микеланджело впервые активизировался компьютерный вирус Michelangelo, от которого только в Германии пострадали 1500 компьютеров.
#Biography #Birth_Date #Events #06марта
Адам Осборн (англ. Adam Osborne; 6 марта 1939, Бангкок, Таиланд — 18 марта 2003, Кодаиканал[англ.], Индия) — американский предприниматель британского происхождения, издатель книг и создатель программного обеспечения, компьютерный дизайнер, основавший несколько компаний в США и других странах. Создатель первого коммерчески успешного портативного компьютера Osborne 1.
1869 — Дмитрий Менделеев представил периодическую систему химических элементов Русскому химическому обществу.
1983 — стартовали продажи Motorola DynaTAC 8000X, первого коммерчески успешного сотового телефона.
1992 — в день рождения великого художника и скульптора Микеланджело впервые активизировался компьютерный вирус Michelangelo, от которого только в Германии пострадали 1500 компьютеров.
#Biography #Birth_Date #Events #06марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 8. Stream API и функциональный стиль в Java
Глава 4: Искусство агрегации. Коллекторы и группировки
(Практика): Анализ библиотеки в один проход
Подготовка: обновление модели Book
Для этой практики расширьте класс Book:
Задача: три агрегации над библиотекой
Требуемые результаты:
Map<String, Book> — автор → его самая старая книга (минимальный год)
int — общее количество страниц во всех книгах
OptionalDouble — средняя цена книг жанра «Фантастика»
Способ 1: Три отдельных Stream (три прохода)
Простой, читаемый, но неэффективный для больших данных.
Агрегация 1: автор → самая старая книга
Агрегация 2: общее количество страниц
Агрегация 3: средняя цена фантастики
Анализ трёх проходов
Плюсы:
Каждый метод простой и понятный
Легко тестировать изолированно
Легко добавить или убрать агрегацию
Минусы:
Три полных обхода списка — O(3n) время
Три раза загружаются данные в кэш процессора
Для миллиона книг — заметная разница с одним проходом
#Java #для_новичков #beginner #stream_api #практика
Глава 4: Искусство агрегации. Коллекторы и группировки
(Практика): Анализ библиотеки в один проход
Подготовка: обновление модели Book
Для этой практики расширьте класс Book:
public class Book {
private final String title;
private final String author;
private final int year; // год издания
private final int pages; // количество страниц
private final double price; // цена
private final List<String> genres;
// конструктор, геттеры...
public boolean hasGenre(String genre) {
return genres.contains(genre);
}
}Задача: три агрегации над библиотекой
Требуемые результаты:
Map<String, Book> — автор → его самая старая книга (минимальный год)
int — общее количество страниц во всех книгах
OptionalDouble — средняя цена книг жанра «Фантастика»
Способ 1: Три отдельных Stream (три прохода)
Простой, читаемый, но неэффективный для больших данных.
Агрегация 1: автор → самая старая книга
public Map<String, Book> getOldestBookByAuthor() {
return books.stream()
.collect(Collectors.toMap(
Book::getAuthor, // ключ — автор
Function.identity(), // значение — книга
(existing, replacement) -> // разрешение коллизии
existing.getYear() < replacement.getYear()
? existing : replacement
));
}
Что происходит: при встрече второй книги того же автора сравниваем годы, оставляем старую.Агрегация 2: общее количество страниц
public int getTotalPages() {
return books.stream()
.mapToInt(Book::getPages)
.sum();
}
Что происходит: mapToInt создаёт примитивный поток, sum — терминальная редукция.Агрегация 3: средняя цена фантастики
public OptionalDouble getAverageFantasyPrice() {
return books.stream()
.filter(Book::hasGenre, "Фантастика")
.mapToDouble(Book::getPrice)
.average();
}
Что происходит: фильтр оставляет только фантастику, average возвращает OptionalDouble для пустого результата.Анализ трёх проходов
Плюсы:
Каждый метод простой и понятный
Легко тестировать изолированно
Легко добавить или убрать агрегацию
Минусы:
Три полных обхода списка — O(3n) время
Три раза загружаются данные в кэш процессора
Для миллиона книг — заметная разница с одним проходом
#Java #для_новичков #beginner #stream_api #практика
👍4
Способ 2: Один Stream с комбинированным коллектором
Используем Collectors.teeing (Java 12+) или кастомный коллектор для одного прохода.
Подход через teeing (Java 12+)
teeing направляет элементы в два коллектора параллельно, затем объединяет результаты. Для трёх агрегаций применяем вложенно.
Что происходит:
Каждая книга обрабатывается один раз
teeing разветвляет поток: одна копия идёт в Map авторов, другая — в сумматор страниц и усреднитель фантастики
filtering (Java 9+) — коллектор-обёртка, пропускающий элементы по условию
#Java #для_новичков #beginner #stream_api #практика
Используем Collectors.teeing (Java 12+) или кастомный коллектор для одного прохода.
Подход через teeing (Java 12+)
teeing направляет элементы в два коллектора параллельно, затем объединяет результаты. Для трёх агрегаций применяем вложенно.
public LibraryStatistics analyzeInOnePass() {
return books.stream()
.collect(Collectors.teeing(
// Первый коллектор: автор → старая книга
Collectors.toMap(
Book::getAuthor,
Function.identity(),
(a, b) -> a.getYear() < b.getYear() ? a : b
),
// Второй коллектор: teeing для страниц и фантастики
Collectors.teeing(
Collectors.summingInt(Book::getPages),
Collectors.filtering(
b -> b.hasGenre("Фантастика"),
Collectors.averagingDouble(Book::getPrice)
),
(totalPages, avgFantasyPrice) ->
new IntermediateStats(totalPages, avgFantasyPrice)
),
// Объединение результатов
(oldestByAuthor, intermediate) -> new LibraryStatistics(
oldestByAuthor,
intermediate.totalPages,
intermediate.avgFantasyPrice
)
));
}
// Вспомогательные классы-контейнеры
private static class IntermediateStats {
final int totalPages;
final Double avgFantasyPrice; // null если нет фантастики
IntermediateStats(int totalPages, Double avgFantasyPrice) {
this.totalPages = totalPages;
this.avgFantasyPrice = avgFantasyPrice;
}
}
public static class LibraryStatistics {
public final Map<String, Book> oldestByAuthor;
public final int totalPages;
public final OptionalDouble avgFantasyPrice;
// конструктор, преобразование Double → OptionalDouble...
}Что происходит:
Каждая книга обрабатывается один раз
teeing разветвляет поток: одна копия идёт в Map авторов, другая — в сумматор страниц и усреднитель фантастики
filtering (Java 9+) — коллектор-обёртка, пропускающий элементы по условию
#Java #для_новичков #beginner #stream_api #практика
👍5
Подход через кастомный коллектор (Java 8+)
Если teeing недоступен, реализуем Collector вручную:
Практические задания
Задача 1: реализовать оба способа
В проекте «Библиотека» создайте класс LibraryAnalyzer с методами:
analyzeThreePasses() — три отдельных Stream
analyzeOnePassTeeing() — через teeing (если Java 12+)
analyzeOnePassCustom() — через кастомный Collector
Протестируйте на 100, 10 000 и 100 000 книг. Измерьте время System.nanoTime().
Задача 2: добавить четвёртую агрегацию
Добавьте: Map<String, Long> — жанр → количество книг.
Обновите оба подхода. В кастомном коллекторе используйте Map.merge() для подсчёта.
Задача 3: параллельная версия
Модифицируйте кастомный коллектор: добавьте CONCURRENT в характеристики, замените HashMap на ConcurrentHashMap или используйте toConcurrentMap.
Сравните stream() vs parallelStream() для 1 000 000 книг на многоядерной машине.
Задача 4: ленивая статистика (звёздочка)
Реализуйте LazyLibraryStatistics — класс, который не вычисляет агрегации при создании, а при первом обращении к любому полю выполняет один проход и кэширует все результаты.
Используйте Supplier + AtomicReference для thread-safe ленивой инициализации.
#Java #для_новичков #beginner #stream_api #практика
Если teeing недоступен, реализуем Collector вручную:
public LibraryStatistics analyzeWithCustomCollector() {
return books.stream()
.collect(new LibraryStatsCollector());
}
private static class LibraryStatsCollector
implements Collector<Book, StatsAccumulator, LibraryStatistics> {
@Override
public Supplier<StatsAccumulator> supplier() {
return StatsAccumulator::new;
}
@Override
public BiConsumer<StatsAccumulator, Book> accumulator() {
return (acc, book) -> {
// Обновление Map авторов
acc.oldestByAuthor.merge(
book.getAuthor(),
book,
(a, b) -> a.getYear() < b.getYear() ? a : b
);
// Накопление страниц
acc.totalPages += book.getPages();
// Накопление для фантастики
if (book.hasGenre("Фантастика")) {
acc.fantasyCount++;
acc.fantasyPriceSum += book.getPrice();
}
};
}
@Override
public BinaryOperator<StatsAccumulator> combiner() {
return (left, right) -> {
// Слияние для параллельных потоков
left.oldestByAuthor.putAll(right.oldestByAuthor); // с разрешением
left.totalPages += right.totalPages;
left.fantasyCount += right.fantasyCount;
left.fantasyPriceSum += right.fantasyPriceSum;
return left;
};
}
@Override
public Function<StatsAccumulator, LibraryStatistics> finisher() {
return acc -> new LibraryStatistics(
acc.oldestByAuthor,
acc.totalPages,
acc.fantasyCount == 0
? OptionalDouble.empty()
: OptionalDouble.of(acc.fantasyPriceSum / acc.fantasyCount)
);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet(); // не CONCURRENT из-за HashMap
}
}
private static class StatsAccumulator {
Map<String, Book> oldestByAuthor = new HashMap<>();
int totalPages = 0;
int fantasyCount = 0;
double fantasyPriceSum = 0.0;
}Практические задания
Задача 1: реализовать оба способа
В проекте «Библиотека» создайте класс LibraryAnalyzer с методами:
analyzeThreePasses() — три отдельных Stream
analyzeOnePassTeeing() — через teeing (если Java 12+)
analyzeOnePassCustom() — через кастомный Collector
Протестируйте на 100, 10 000 и 100 000 книг. Измерьте время System.nanoTime().
Задача 2: добавить четвёртую агрегацию
Добавьте: Map<String, Long> — жанр → количество книг.
Обновите оба подхода. В кастомном коллекторе используйте Map.merge() для подсчёта.
Задача 3: параллельная версия
Модифицируйте кастомный коллектор: добавьте CONCURRENT в характеристики, замените HashMap на ConcurrentHashMap или используйте toConcurrentMap.
Сравните stream() vs parallelStream() для 1 000 000 книг на многоядерной машине.
Задача 4: ленивая статистика (звёздочка)
Реализуйте LazyLibraryStatistics — класс, который не вычисляет агрегации при создании, а при первом обращении к любому полю выполняет один проход и кэширует все результаты.
Используйте Supplier + AtomicReference для thread-safe ленивой инициализации.
#Java #для_новичков #beginner #stream_api #практика
👍5
Что выведет код?
#Tasks
import java.util.concurrent.*;
public class Task060326 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.print("GO "));
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
latch.countDown();
latch.await();
barrier.await();
} catch (Exception e) {}
}).start();
}
latch.await();
System.out.print("FINISH ");
}
}
#Tasks
👍2
Варианты ответа:
Anonymous Quiz
40%
GO FINISH
10%
FINISH GO
20%
GO FINISH или FINISH GO (недетерминировано)
30%
Исключение Deadlock
👍2
Что такое CountDownLatch и CyclicBarrier? В чем разница? 🤓
Ответ:
Оба класса из java.util.concurrent используются для синхронизации потоков.
CountDownLatch — это счетчик, который уменьшается вызовом countDown(). Потоки, вызывающие await(), блокируются, пока счетчик не станет равен 0. Используется одноразово, например, когда один поток ждет завершения нескольких других.
CyclicBarrier — это барьер, который собирает потоки. Когда все потоки достигают барьера (вызывают await()), они одновременно освобождаются. Может быть повторно использован (cyclic). Используется, когда потоки ждут друг друга, чтобы начать новый этап работы.
#собеседование
Ответ:
CountDownLatch — это счетчик, который уменьшается вызовом countDown(). Потоки, вызывающие await(), блокируются, пока счетчик не станет равен 0. Используется одноразово, например, когда один поток ждет завершения нескольких других.
CyclicBarrier — это барьер, который собирает потоки. Когда все потоки достигают барьера (вызывают await()), они одновременно освобождаются. Может быть повторно использован (cyclic). Используется, когда потоки ждут друг друга, чтобы начать новый этап работы.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4