История технологий сегодня — 31 марта
ℹ️ Кто родился в этот день
Серге́й Влади́мирович Илью́шин (18 (30) марта 1894, Дилялево, Вологодский уезд, Вологодская губерния, Российская империя — 9 февраля 1977, Москва, СССР) — советский авиаконструктор, разработчик самого массового боевого самолёта в истории — штурмовика Ил-2. Трижды Герой Социалистического Труда (1941, 1957, 1974), единственный лауреат семи Сталинских премий (1941, 1941, 1943, 1946, 1947, 1950, 1951), лауреат Ленинской премии (1960), Государственной премии СССР (1971). Кавалер восьми орденов Ленина (1936, 1941, 1945, 1945, 1954, 1964, 1971, 1974). Генерал-полковник инженерно-технической службы (1967; с 1971 года — Генерал-полковник-инженер). Академик АН СССР (1968).
🌐 Знаковые события
1791 — Национальное собрание Франции ввело определение метра: одна сорокамиллионная часть длины парижского меридиана.
1950 — изобретённый фототранзистор представляется в Murray Hill, Нью-Джерси.
2017 — первый в истории успешный повторный запуск первой ступени ракеты Falcon 9 FT.
#Biography #Birth_Date #Events #31марта
Серге́й Влади́мирович Илью́шин (18 (30) марта 1894, Дилялево, Вологодский уезд, Вологодская губерния, Российская империя — 9 февраля 1977, Москва, СССР) — советский авиаконструктор, разработчик самого массового боевого самолёта в истории — штурмовика Ил-2. Трижды Герой Социалистического Труда (1941, 1957, 1974), единственный лауреат семи Сталинских премий (1941, 1941, 1943, 1946, 1947, 1950, 1951), лауреат Ленинской премии (1960), Государственной премии СССР (1971). Кавалер восьми орденов Ленина (1936, 1941, 1945, 1945, 1954, 1964, 1971, 1974). Генерал-полковник инженерно-технической службы (1967; с 1971 года — Генерал-полковник-инженер). Академик АН СССР (1968).
1791 — Национальное собрание Франции ввело определение метра: одна сорокамиллионная часть длины парижского меридиана.
1950 — изобретённый фототранзистор представляется в Murray Hill, Нью-Джерси.
2017 — первый в истории успешный повторный запуск первой ступени ракеты Falcon 9 FT.
#Biography #Birth_Date #Events #31марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
[Совет по Java #020]
Тема: HashSet с изменяемыми объектами ломает контракт.
Проблема: Контракт HashSet (и любой хэш-коллекции) основан на неизменности хэш-кода объекта во время его нахождения в коллекции.
Если объект, хранящийся в HashSet, изменяет свои поля, которые участвуют в вычислении hashCode() и equals(), то его хэш-код меняется. При этом объект остается в корзине (bucket), соответствующей старому хэшу, а поиск (contains, remove) выполняется по новому хэшу.
В результате объект становится недоступным для операций коллекции, хотя формально остается в ней. Это приводит к утечке памяти, дублированию (невозможности удалить), и нарушению ожидаемого поведения множества.
Решение: Никогда не используйте изменяемые объекты в качестве ключей HashMap или элементов HashSet.
Если это необходимо, либо не изменяйте их после добавления, либо удаляйте перед изменением и добавляйте заново.
Предпочтительно использовать неизменяемые (immutable) объекты или явно контролировать жизненный цикл.
Объяснение: При добавлении объекта в HashSet его хэш-код определяет корзину. Внутри корзины объект хранится по ссылке. При изменении полей, участвующих в hashCode(), объект физически остается в старой корзине, но его новый хэш-код указывает на другую корзину. Методы contains и remove вычисляют новый хэш и ищут в другой корзине, где объект не найден.
Это классический пример нарушения инварианта коллекции. Даже если вы переопределите hashCode и equals правильно, проблема останется.
Единственное надежное решение — использовать неизменяемые объекты (например, record в Java 16+, или класс с final полями). Если изменяемость необходима, обеспечьте явное управление: удаляйте объект из коллекции перед мутацией и добавляйте обратно.
#Java #советы
Тема: HashSet с изменяемыми объектами ломает контракт.
Проблема: Контракт HashSet (и любой хэш-коллекции) основан на неизменности хэш-кода объекта во время его нахождения в коллекции.
Если объект, хранящийся в HashSet, изменяет свои поля, которые участвуют в вычислении hashCode() и equals(), то его хэш-код меняется. При этом объект остается в корзине (bucket), соответствующей старому хэшу, а поиск (contains, remove) выполняется по новому хэшу.
В результате объект становится недоступным для операций коллекции, хотя формально остается в ней. Это приводит к утечке памяти, дублированию (невозможности удалить), и нарушению ожидаемого поведения множества.
Решение: Никогда не используйте изменяемые объекты в качестве ключей HashMap или элементов HashSet.
Если это необходимо, либо не изменяйте их после добавления, либо удаляйте перед изменением и добавляйте заново.
Предпочтительно использовать неизменяемые (immutable) объекты или явно контролировать жизненный цикл.
import java.util.*;
public class HashSetMutation {
static class MutablePoint {
private int x;
private int y;
public MutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
@Override
public boolean equals(Object o) {
if (!(o instanceof MutablePoint)) return false;
MutablePoint p = (MutablePoint) o;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}
public static void main(String[] args) {
Set<MutablePoint> points = new HashSet<>();
MutablePoint p = new MutablePoint(1, 2);
points.add(p);
System.out.println("Contains (1,2): " + points.contains(p)); // true
//Антипаттерн: изменение после добавления
p.setX(5);
System.out.println("Contains (5,2): " + points.contains(p)); // false — объект потерян!
System.out.println("Set size: " + points.size()); // 1, но внутри лежит объект (1,2) в другой корзине
// Объект невозможно удалить
points.remove(p); // false
System.out.println("Size after remove: " + points.size()); // всё ещё 1
//Правильно: удалить перед изменением, потом добавить заново
MutablePoint p2 = new MutablePoint(10, 20);
points.add(p2);
points.remove(p2);
p2.setX(30);
points.add(p2);
System.out.println("Contains (30,20): " + points.contains(p2)); // true
//Лучшее решение: неизменяемые объекты
record ImmutablePoint(int x, int y) {}
Set<ImmutablePoint> immutableSet = new HashSet<>();
ImmutablePoint ip = new ImmutablePoint(1, 2);
immutableSet.add(ip);
// ip.x нельзя изменить — компилятор запретит
immutableSet.contains(ip); // всегда корректно
}
}
Объяснение: При добавлении объекта в HashSet его хэш-код определяет корзину. Внутри корзины объект хранится по ссылке. При изменении полей, участвующих в hashCode(), объект физически остается в старой корзине, но его новый хэш-код указывает на другую корзину. Методы contains и remove вычисляют новый хэш и ищут в другой корзине, где объект не найден.
Это классический пример нарушения инварианта коллекции. Даже если вы переопределите hashCode и equals правильно, проблема останется.
Единственное надежное решение — использовать неизменяемые объекты (например, record в Java 16+, или класс с final полями). Если изменяемость необходима, обеспечьте явное управление: удаляйте объект из коллекции перед мутацией и добавляйте обратно.
#Java #советы
👍6
Что выведет код?
#Tasks
import java.util.HashSet;
public class Task300326 {
static class Item300326 {
int id;
Item300326(int id) { this.id = id; }
public boolean equals(Object o) {
return o instanceof Item300326 && ((Item300326) o).id == id;
}
public int hashCode() {
return id;
}
}
public static void main(String[] args) {
HashSet<Item300326> set = new HashSet<>();
Item300326 a = new Item300326(1);
Item300326 b = new Item300326(2);
set.add(a);
set.add(b);
a.id = 2; // изменяем объект, уже находящийся в множестве
System.out.print(set.contains(a) + " ");
System.out.print(set.contains(b) + " ");
System.out.print(set.remove(a) + " ");
System.out.print(set.size());
}
}
#Tasks
👍2
Варианты ответа:
Anonymous Quiz
38%
true true true 1
25%
false true false 2
13%
false true true 1
25%
true false false 2
👍2
Что такое this и super? 🤓
Ответ:
this — это ссылка на текущий объект.
Используется для разрешения конфликтов имен (параметр метода с тем же именем, что и поле класса: this.field = field), для вызова одного конструктора из другого (this()), или передачи текущего объекта в другой метод.
super — это ссылка на объект родительского класса.
Используется для доступа к полям и методам родителя, скрытым переопределением, и для вызова конструктора родителя (super()) — это должен быть первый оператор в конструкторе потомка.
#собеседование
Ответ:
this
Используется для разрешения конфликтов имен (параметр метода с тем же именем, что и поле класса: this.field = field), для вызова одного конструктора из другого (this()), или передачи текущего объекта в другой метод.
super — это ссылка на объект родительского класса.
Используется для доступа к полям и методам родителя, скрытым переопределением, и для вызова конструктора родителя (super()) — это должен быть первый оператор в конструкторе потомка.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 31 марта
ℹ️ Кто родился в этот день
Ка́рло Ру́ббиа (итал. Carlo Rubbia; род. 31 марта 1934, Гориция, Италия) — итальянский физик, лауреат Нобелевской премии по физике в 1984 году, совместно с Симоном ван дер Мером, «за решающий вклад в большой проект, осуществление которого привело к открытию квантов поля W и Z — переносчиков слабого взаимодействия».
Синъити́ро Томона́га (яп. 朝永 振一郎 Томонага Синъитиро:, 31 марта 1906, Токио — 8 июля 1979, там же) — японский физик-теоретик, известный разработкой базовых положений квантовой электродинамики, лауреат Нобелевской премии по физике 1965 года. Президент Научного совета Японии (1963—1969). Член Японской академии наук (1951), иностранный член Национальной академии наук США (1965)[4], Академии наук СССР (1971).
🌐 Знаковые события
1901 — выпуск первого автомобиля с названием Mercedes.
2005 — группой астрономов во главе с профессором Майклом Брауном была открыта карликовая планета Макемаке.
#Biography #Birth_Date #Events #31марта
Ка́рло Ру́ббиа (итал. Carlo Rubbia; род. 31 марта 1934, Гориция, Италия) — итальянский физик, лауреат Нобелевской премии по физике в 1984 году, совместно с Симоном ван дер Мером, «за решающий вклад в большой проект, осуществление которого привело к открытию квантов поля W и Z — переносчиков слабого взаимодействия».
Синъити́ро Томона́га (яп. 朝永 振一郎 Томонага Синъитиро:, 31 марта 1906, Токио — 8 июля 1979, там же) — японский физик-теоретик, известный разработкой базовых положений квантовой электродинамики, лауреат Нобелевской премии по физике 1965 года. Президент Научного совета Японии (1963—1969). Член Японской академии наук (1951), иностранный член Национальной академии наук США (1965)[4], Академии наук СССР (1971).
1901 — выпуск первого автомобиля с названием Mercedes.
2005 — группой астрономов во главе с профессором Майклом Брауном была открыта карликовая планета Макемаке.
#Biography #Birth_Date #Events #31марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 8. Stream API и функциональный стиль в Java
Глава 7: Stream API в экосистеме Java. Интеграционные паттерны
Рефакторинг legacy-кода в проекте «Библиотека»
Представьте, что в проекте «Библиотека» накопился такой метод — подсчёт статистики по авторам с множеством условий:
Характеристики кода:
40+ строк в одном методе
4 уровня вложенности в пике
Смешение фильтрации, трансформации и агрегации
Два прохода: основной цикл + пост-обработка
#Java #для_новичков #beginner #stream_api #практика
Глава 7: Stream API в экосистеме Java. Интеграционные паттерны
Рефакторинг legacy-кода в проекте «Библиотека»
Представьте, что в проекте «Библиотека» накопился такой метод — подсчёт статистики по авторам с множеством условий:
public Map<String, AuthorStats> calculateAuthorStatsLegacy(List<Book> books, int minYear) {
Map<String, AuthorStats> result = new HashMap<>();
for (Book book : books) {
// Пропускаем старые книги
if (book.getYear() < minYear) {
continue;
}
// Пропускаем без жанров
if (book.getGenres().isEmpty()) {
continue;
}
// Пропускаем, если нет цены
Double price = book.getPrice();
if (price == null || price <= 0) {
continue;
}
String author = book.getAuthor();
AuthorStats stats = result.get(author);
if (stats == null) {
stats = new AuthorStats();
stats.firstBookYear = book.getYear();
result.put(author, stats);
}
// Обновляем статистику
stats.bookCount++;
stats.totalPages += book.getPages();
stats.totalPrice += price;
// Обновляем самую старую книгу
if (book.getYear() < stats.firstBookYear) {
stats.firstBookYear = book.getYear();
}
// Собираем уникальные жанры
for (String genre : book.getGenres()) {
if (!stats.genres.contains(genre)) {
stats.genres.add(genre);
}
}
}
// Пост-обработка: считаем среднее
for (AuthorStats stats : result.values()) {
stats.averagePrice = stats.totalPrice / stats.bookCount;
}
return result;
}
static class AuthorStats {
int bookCount;
int totalPages;
double totalPrice;
double averagePrice;
int firstBookYear;
Set<String> genres = new HashSet<>();
}Характеристики кода:
40+ строк в одном методе
4 уровня вложенности в пике
Смешение фильтрации, трансформации и агрегации
Два прохода: основной цикл + пост-обработка
#Java #для_новичков #beginner #stream_api #практика
👍4
Мутация состояния на каждом шагу
Шаг 1: Выделить источник данных в stream
Не меняем логику — только оборачиваем в stream:
Что достигли: Начали привыкать к синтаксису. Никаких функциональных преимуществ ещё нет — это подготовка.
Шаг 2: Заменить простые фильтры
Выносим continue как предикаты filter. Каждый фильтр — отдельная проверка:
Улучшения:
Фильтры вынесены, читаются как декларативные условия
Заменили if (stats == null) на computeIfAbsent
Жанры теперь через forEach
Проблема осталась: Сложная логика агрегации всё ещё внутри forEach с мутацией.
#Java #для_новичков #beginner #stream_api #практика
Шаг 1: Выделить источник данных в stream
Не меняем логику — только оборачиваем в stream:
public Map<String, AuthorStats> calculateAuthorStatsStep1(List<Book> books, int minYear) {
Map<String, AuthorStats> result = new HashMap<>();
books.stream().forEach(book -> { // Заменили for-each на forEach
// ... весь прежний код без изменений ...
});
// пост-обработка
result.values().forEach(stats ->
stats.averagePrice = stats.totalPrice / stats.bookCount
);
return result;
}Что достигли: Начали привыкать к синтаксису. Никаких функциональных преимуществ ещё нет — это подготовка.
Шаг 2: Заменить простые фильтры
Выносим continue как предикаты filter. Каждый фильтр — отдельная проверка:
public Map<String, AuthorStats> calculateAuthorStatsStep2(List<Book> books, int minYear) {
Map<String, AuthorStats> result = new HashMap<>();
books.stream()
.filter(book -> book.getYear() >= minYear) // было: if (year < minYear) continue
.filter(book -> !book.getGenres().isEmpty()) // было: if (genres.isEmpty()) continue
.filter(book -> { // было: if (price == null || price <= 0) continue
Double price = book.getPrice();
return price != null && price > 0;
})
.forEach(book -> {
// ... урезанный цикл без фильтров ...
String author = book.getAuthor();
AuthorStats stats = result.computeIfAbsent(author, k -> {
AuthorStats s = new AuthorStats();
s.firstBookYear = book.getYear();
return s;
});
stats.bookCount++;
stats.totalPages += book.getPages();
stats.totalPrice += book.getPrice();
if (book.getYear() < stats.firstBookYear) {
stats.firstBookYear = book.getYear();
}
book.getGenres().forEach(genre -> stats.genres.add(genre));
});
result.values().forEach(stats ->
stats.averagePrice = stats.totalPrice / stats.bookCount
);
return result;
}Улучшения:
Фильтры вынесены, читаются как декларативные условия
Заменили if (stats == null) на computeIfAbsent
Жанры теперь через forEach
Проблема осталась: Сложная логика агрегации всё ещё внутри forEach с мутацией.
#Java #для_новичков #beginner #stream_api #практика
👍4
Шаг 3: Вынести преобразование в map
Выделяем «чистые» данные для агрегации — промежуточный объект:
Улучшения:
Разделили фильтрацию, группировку и агрегацию
Каждый этап — отдельный, тестируемый блок
Убрали мутацию внутри потока
Новая проблема: Два прохода по данным, избыточные промежуточные структуры.
Шаг 4: Сложная агрегация через collect
Объединяем всё в один проход с кастомным коллектором:
Достигнуто:
Один проход по данным
Нет промежуточных структур
Потенциально parallelizable
Цена: 30 строк сложного кода вместо 40 строк простого. Стало ли читаемее?
#Java #для_новичков #beginner #stream_api #практика
Выделяем «чистые» данные для агрегации — промежуточный объект:
// Вспомогательный record для промежуточных данных
record BookData(String author, int year, int pages, double price, List<String> genres) {}
public Map<String, AuthorStats> calculateAuthorStatsStep3(List<Book> books, int minYear) {
Map<String, List<BookData>> grouped = books.stream()
.filter(book -> book.getYear() >= minYear)
.filter(book -> !book.getGenres().isEmpty())
.filter(book -> book.getPrice() != null && book.getPrice() > 0)
.map(book -> new BookData(
book.getAuthor(),
book.getYear(),
book.getPages(),
book.getPrice(),
book.getGenres()
))
.collect(Collectors.groupingBy(BookData::author));
// Отдельный проход для агрегации
Map<String, AuthorStats> result = new HashMap<>();
grouped.forEach((author, dataList) -> {
AuthorStats stats = new AuthorStats();
stats.bookCount = dataList.size();
stats.totalPages = dataList.stream().mapToInt(BookData::pages).sum();
stats.totalPrice = dataList.stream().mapToDouble(BookData::price).sum();
stats.averagePrice = stats.totalPrice / stats.bookCount;
stats.firstBookYear = dataList.stream()
.mapToInt(BookData::year)
.min()
.orElse(0);
stats.genres = dataList.stream()
.flatMap(d -> d.genres().stream())
.collect(Collectors.toSet());
result.put(author, stats);
});
return result;
}
Улучшения:
Разделили фильтрацию, группировку и агрегацию
Каждый этап — отдельный, тестируемый блок
Убрали мутацию внутри потока
Новая проблема: Два прохода по данным, избыточные промежуточные структуры.
Шаг 4: Сложная агрегация через collect
Объединяем всё в один проход с кастомным коллектором:
public Map<String, AuthorStats> calculateAuthorStatsStep4(List<Book> books, int minYear) {
return books.stream()
.filter(book -> book.getYear() >= minYear)
.filter(book -> !book.getGenres().isEmpty())
.filter(book -> book.getPrice() != null && book.getPrice() > 0)
.collect(Collectors.groupingBy(
Book::getAuthor,
Collector.of(
AuthorStats::new, // supplier
(stats, book) -> { // accumulator
if (stats.bookCount == 0) {
stats.firstBookYear = book.getYear();
} else {
stats.firstBookYear = Math.min(stats.firstBookYear, book.getYear());
}
stats.bookCount++;
stats.totalPages += book.getPages();
stats.totalPrice += book.getPrice();
stats.genres.addAll(book.getGenres());
},
(left, right) -> { // combiner для parallelStream
if (left.bookCount == 0) return right;
if (right.bookCount == 0) return left;
AuthorStats merged = new AuthorStats();
merged.bookCount = left.bookCount + right.bookCount;
merged.totalPages = left.totalPages + right.totalPages;
merged.totalPrice = left.totalPrice + right.totalPrice;
merged.firstBookYear = Math.min(left.firstBookYear, right.firstBookYear);
merged.genres.addAll(left.genres);
merged.genres.addAll(right.genres);
return merged;
},
stats -> { // finisher
stats.averagePrice = stats.totalPrice / stats.bookCount;
return stats;
}
)
));
}Достигнуто:
Один проход по данным
Нет промежуточных структур
Потенциально parallelizable
Цена: 30 строк сложного кода вместо 40 строк простого. Стало ли читаемее?
#Java #для_новичков #beginner #stream_api #практика
👍4
Шаг 5: Точка остановки — прагматичный компромисс
Вернёмся к шагу 3 и доработаем его до читаемого состояния без избыточной сложности:
Почему это лучше шага 4:
Методы isValidForStats и aggregateStats можно тестировать отдельно
Нет 30-строчного лямбда-ада в коллекторе
Junior-разработчик поймёт код за 5 минут
Легко добавить логирование или метрики в промежуточные точки
Критерии остановки рефакторинга
Остановитесь, если:
Код стал медленнее без причины
Появились неочевидные побочные эффекты
Теряется отладочная информация
В исходном коде можно было поставить breakpoint на любой continue. В сложном коллекторе — только внутри лямбд.
Команда не владеет паттернами
Если 3 из 5 разработчиков не знают Collector.of — код неподдерживаем.
Практические задания
Задача 1: рефакторинг с остановкой
В проекте «Библиотека» найдите метод с циклом, содержащим:
2+ условия continue
мутацию аккумулятора
вложенные циклы
Проведите рефакторинг до шага 3 (группировка + явная агрегация). Остановитесь. Обоснуйте выбор.
Задача 2: сравнение читаемости
Покажите шаг 4 (полный коллектор) коллеге, не знакомому с Stream API. Засеките время, за которое он поймёт логику. Повторите с шагом 5. Зафиксируйте разницу.
Задача 3: добавление функциональности
К обоим вариантам (шаг 4 и шаг 5) добавьте требование: «пропускать авторов с менее чем 3 книгами».
В каком варианте изменение проще? В каком меньше риск регрессии?
Задача 4: документация компромисса (звёздочка)
Создайте в проекте docs/stream-guidelines.md с правилами:
когда использовать Stream
когда остановиться
примеры «хорошего», «плохого» и «достаточного» кода из вашей кодовой базы
#Java #для_новичков #beginner #stream_api #практика
Вернёмся к шагу 3 и доработаем его до читаемого состояния без избыточной сложности:
public Map<String, AuthorStats> calculateAuthorStatsPragmatic(List<Book> books, int minYear) {
// Предварительная фильтрация — ясная и тестируемая
List<Book> validBooks = books.stream()
.filter(this::isValidForStats)
.filter(book -> book.getYear() >= minYear)
.collect(Collectors.toList());
// Группировка — стандартная операция
Map<String, List<Book>> byAuthor = validBooks.stream()
.collect(Collectors.groupingBy(Book::getAuthor));
// Агрегация — явный цикл с понятной логикой
Map<String, AuthorStats> result = new HashMap<>();
for (Map.Entry<String, List<Book>> entry : byAuthor.entrySet()) {
result.put(entry.getKey(), aggregateStats(entry.getValue()));
}
return result;
}
private boolean isValidForStats(Book book) {
return !book.getGenres().isEmpty()
&& book.getPrice() != null
&& book.getPrice() > 0;
}
private AuthorStats aggregateStats(List<Book> authorBooks) {
AuthorStats stats = new AuthorStats();
stats.bookCount = authorBooks.size();
stats.totalPages = authorBooks.stream()
.mapToInt(Book::getPages)
.sum();
stats.totalPrice = authorBooks.stream()
.mapToDouble(Book::getPrice)
.sum();
stats.averagePrice = stats.totalPrice / stats.bookCount;
stats.firstBookYear = authorBooks.stream()
.mapToInt(Book::getYear)
.min()
.orElse(0);
stats.genres = authorBooks.stream()
.flatMap(b -> b.getGenres().stream())
.collect(Collectors.toSet());
return stats;
}Почему это лучше шага 4:
Методы isValidForStats и aggregateStats можно тестировать отдельно
Нет 30-строчного лямбда-ада в коллекторе
Junior-разработчик поймёт код за 5 минут
Легко добавить логирование или метрики в промежуточные точки
Критерии остановки рефакторинга
Остановитесь, если:
Код стал медленнее без причины
Появились неочевидные побочные эффекты
// Опасно: параллельный stream с непотокобезопасным accumulators
.collect(Collectors.groupingByConcurrent(
Book::getAuthor,
Collector.of(
() -> new AuthorStats(), // ОК — новый для каждого
(stats, book) -> stats.genres.addAll(...), // Опасно! HashSet не thread-safe
...
)
))
Теряется отладочная информация
В исходном коде можно было поставить breakpoint на любой continue. В сложном коллекторе — только внутри лямбд.
Команда не владеет паттернами
Если 3 из 5 разработчиков не знают Collector.of — код неподдерживаем.
Практические задания
Задача 1: рефакторинг с остановкой
В проекте «Библиотека» найдите метод с циклом, содержащим:
2+ условия continue
мутацию аккумулятора
вложенные циклы
Проведите рефакторинг до шага 3 (группировка + явная агрегация). Остановитесь. Обоснуйте выбор.
Задача 2: сравнение читаемости
Покажите шаг 4 (полный коллектор) коллеге, не знакомому с Stream API. Засеките время, за которое он поймёт логику. Повторите с шагом 5. Зафиксируйте разницу.
Задача 3: добавление функциональности
К обоим вариантам (шаг 4 и шаг 5) добавьте требование: «пропускать авторов с менее чем 3 книгами».
В каком варианте изменение проще? В каком меньше риск регрессии?
Задача 4: документация компромисса (звёздочка)
Создайте в проекте docs/stream-guidelines.md с правилами:
когда использовать Stream
когда остановиться
примеры «хорошего», «плохого» и «достаточного» кода из вашей кодовой базы
#Java #для_новичков #beginner #stream_api #практика
👍4
Java for Beginner
Ради интереса добавил чат, пока думаю что там насобирать))) ✌️ Пишите, заодно и потестим))) 🧑💻
А вы уже тестили чат?
Я добавил звук уведомления, при свернутом браузере)) Так что теперь можно и пообщаться)
Я добавил звук уведомления, при свернутом браузере)) Так что теперь можно и пообщаться)
🔥2👍1
Что выведет код?
#Tasks
public class Task310326 {
public static void main(String[] args) {
int[] arr = new int[2];
System.out.println(arr + arr);
System.out.println(arr == arr);
System.out.println(arr.equals(arr));
System.out.println(arr.hashCode() == arr.hashCode());
}
}#Tasks
👍2
Варианты ответа:
Anonymous Quiz
0%
true false true true
23%
true true true true
0%
true false false false
77%
Ошибка компиляции
👍1
Как работает switch со строками (String)? 🤓
Ответ:
Поддержка String в switch появилась в Java 7.
В байт-коде это реализуется не как прямое сравнение строк, а с помощью вычисления хеш-кода строки и использования switch по int.
Сначала вычисляется hashCode() строки в case-метках, и по нему выбирается возможный блок. Затем внутри выбранного блока выполняется проверка equals() для подтверждения точного совпадения (на случай коллизий хешей).
Это делает switch по строкам несколько медленнее, чем по числам или enum.
#собеседование
Ответ:
В байт-коде это реализуется не как прямое сравнение строк, а с помощью вычисления хеш-кода строки и использования switch по int.
Сначала вычисляется hashCode() строки в case-метках, и по нему выбирается возможный блок. Затем внутри выбранного блока выполняется проверка equals() для подтверждения точного совпадения (на случай коллизий хешей).
Это делает switch по строкам несколько медленнее, чем по числам или enum.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 1 апреля
ℹ️ Кто родился в этот день
Не нашел...(
🌐 Знаковые события
1940 — в Нью-Джерси впервые демонстрируется электронный микроскоп.
1960 — запущен спутник TIROS-1, впервые обеспечивший телевещание из космоса.
1972 — компания Intel представила микропроцессор 8008.
1976 — Стив Джобс, Стив Возняк и Рональд Уэйн основывают Apple Computer Company.
2004 — открылась почта Gmail от Google.
#Biography #Birth_Date #Events #01апреля
Не нашел...(
1940 — в Нью-Джерси впервые демонстрируется электронный микроскоп.
1960 — запущен спутник TIROS-1, впервые обеспечивший телевещание из космоса.
1972 — компания Intel представила микропроцессор 8008.
1976 — Стив Джобс, Стив Возняк и Рональд Уэйн основывают Apple Computer Company.
2004 — открылась почта Gmail от Google.
#Biography #Birth_Date #Events #01апреля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #021]
Тема: Использование intern() в (String.intern()) для уникальных строк может забить PermGen/Metaspace и вызвать утечку памяти.
Проблема: Метод String.intern() помещает строку в пул, управляемый JVM, и возвращает канонический экземпляр.
В ранних версиях Java пул находился в PermGen (Permanent Generation) — области памяти фиксированного размера (обычно 64-96 МБ). При интенсивном интернировании уникальных строк (например, полученных из внешних источников или сгенерированных динамически) пул быстро переполняется, вызывая OutOfMemoryError: PermGen space.
Начиная с Java 8, PermGen заменен на Metaspace (неограниченный по умолчанию, но ограниченный физической памятью). Однако проблема остается: интернированные строки никогда не собираются сборщиком мусора, если на них есть ссылки из пула. Даже после того, как исходная строка становится недоступной, интернированный экземпляр продолжает жить в пуле, что приводит к утечке памяти. Кроме того, операция intern() синхронизирована и в высококонкурентной среде становится узким местом.
Решение: Не используйте intern() для дедупликации строк, особенно если строки уникальны или их количество велико.
Для кэширования повторяющихся строк применяйте ConcurrentHashMap<String, String> или ConcurrentHashMap<String, WeakReference<String>> с возможностью ручного контроля размера.
Альтернатива — использовать String как есть, полагаясь на то, что JVM сама оптимизирует хранение литералов и констант. Для ограниченных наборов (например, коды ошибок, статусы) используйте enum. Если интернирование необходимо для экономии памяти при огромном количестве дубликатов, настройте размер StringTable через -XX:StringTableSize=N и используйте intern() только для строк с высокой степенью повторяемости.
Объяснение: Пул строк — это хэш-таблица (StringTable) фиксированного размера.
При интернировании строка добавляется в эту таблицу, и сборщик мусора не удаляет ее, даже если исходный объект больше не используется.
В Java 8+ Metaspace может расти до предела физической памяти, но при интенсивном интернировании уникальных строк рост становится бесконтрольным, и приложение падает с OutOfMemoryError: Metaspace.
#Java #советы
Тема: Использование intern() в (String.intern()) для уникальных строк может забить PermGen/Metaspace и вызвать утечку памяти.
Проблема: Метод String.intern() помещает строку в пул, управляемый JVM, и возвращает канонический экземпляр.
В ранних версиях Java пул находился в PermGen (Permanent Generation) — области памяти фиксированного размера (обычно 64-96 МБ). При интенсивном интернировании уникальных строк (например, полученных из внешних источников или сгенерированных динамически) пул быстро переполняется, вызывая OutOfMemoryError: PermGen space.
Начиная с Java 8, PermGen заменен на Metaspace (неограниченный по умолчанию, но ограниченный физической памятью). Однако проблема остается: интернированные строки никогда не собираются сборщиком мусора, если на них есть ссылки из пула. Даже после того, как исходная строка становится недоступной, интернированный экземпляр продолжает жить в пуле, что приводит к утечке памяти. Кроме того, операция intern() синхронизирована и в высококонкурентной среде становится узким местом.
Решение: Не используйте intern() для дедупликации строк, особенно если строки уникальны или их количество велико.
Для кэширования повторяющихся строк применяйте ConcurrentHashMap<String, String> или ConcurrentHashMap<String, WeakReference<String>> с возможностью ручного контроля размера.
Альтернатива — использовать String как есть, полагаясь на то, что JVM сама оптимизирует хранение литералов и констант. Для ограниченных наборов (например, коды ошибок, статусы) используйте enum. Если интернирование необходимо для экономии памяти при огромном количестве дубликатов, настройте размер StringTable через -XX:StringTableSize=N и используйте intern() только для строк с высокой степенью повторяемости.
import java.util.concurrent.ConcurrentHashMap;
public class StringInternRisk {
//Антипаттерн: интернирование всех строк
public static String dangerousIntern(String value) {
return value.intern(); // Попадает в Metaspace, никогда не освободится
}
//Решение: ручной пул с контролем
private static final ConcurrentHashMap<String, String> STRING_POOL = new ConcurrentHashMap<>();
public static String safeDeduplicate(String value) {
// putIfAbsent атомарно добавляет, если отсутствует
String existing = STRING_POOL.putIfAbsent(value, value);
return existing != null ? existing : value;
}
// Вариант с автоматической очисткой через WeakReference
private static final ConcurrentHashMap<String, WeakReference<String>> WEAK_POOL = new ConcurrentHashMap<>();
// Для ограниченных наборов — enum
public enum Status {
NEW, PROCESSING, COMPLETED;
// вместо "NEW".intern() используйте Status.NEW.name()
}
public static void main(String[] args) {
String s1 = new String("Hello");
String s2 = new String("Hello");
// Рискованно
String interned = s1.intern();
System.out.println(s1 == s2); // false
System.out.println(s1 == interned); // true (попал в пул)
// Безопасная альтернатива
String dedup = safeDeduplicate(s2);
System.out.println(s1 == dedup); // false, но equals true — корректно
// Демонстрация утечки: цикл с уникальными строками быстро заполнит Metaspace
for (int i = 0; i < 1_000_000; i++) {
String unique = Integer.toString(i);
// unique.intern(); // Раскомментировать — опасно, приведет к OOM
}
}
}
Объяснение: Пул строк — это хэш-таблица (StringTable) фиксированного размера.
При интернировании строка добавляется в эту таблицу, и сборщик мусора не удаляет ее, даже если исходный объект больше не используется.
В Java 8+ Metaspace может расти до предела физической памяти, но при интенсивном интернировании уникальных строк рост становится бесконтрольным, и приложение падает с OutOfMemoryError: Metaspace.
#Java #советы
👍4