Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
История технологий сегодня — 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) объекты или явно контролировать жизненный цикл.

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
Что выведет код?

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
Что такое this и 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марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 8. Stream API и функциональный стиль в Java

Глава 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:
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

Выделяем «чистые» данные для агрегации — промежуточный объект:
// Вспомогательный 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 и доработаем его до читаемого состояния без избыточной сложности:
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
Что выведет код?

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
Как работает switch со строками (String)? 🤓

Ответ:

Поддержка String в switch появилась в Java 7.

В байт-коде это реализуется не как прямое сравнение строк, а с помощью вычисления хеш-кода строки и использования 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апреля
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() только для строк с высокой степенью повторяемости.

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
Что выведет код?

import java.util.ArrayList;
import java.util.List;

public class Task010426 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();

for (int i = 0; i < 10_000; i++) {
String s = new String("data_" + i);
String interned = s.intern();
list.add(interned);
}

String a = new String("data_5000");
String b = a.intern();

System.out.println(a == b);
System.out.println(a == list.get(5000));
System.out.println(b == list.get(5000));
}
}


#Tasks
👍1
👍2
Что такое анонимные классы (Anonymous classes)? 🤓

Ответ:

Анонимные классы
— это локальные внутренние классы без имени, которые объявляются и инстанцируются одновременно в одном выражении.

Они используются, когда нужен одноразовый объект с небольшими изменениями, чаще всего для реализации интерфейсов или расширения классов (например, Runnable, компараторы, обработчики событий).

Синтаксис: new Interface() { ... } или new Class() { ... }.

Анонимные классы имеют доступ к final или effectively final переменным окружающего контекста.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 2 апреля

ℹ️ Кто родился в этот день

Уо́лтер Пе́рси Кра́йслер (англ. Walter Percy Chrysler; 2 апреля 1875, Уомего, Канзас — 18 августа 1940, Кингс-Пойнт, Нью-Йорк) — американский автомобилестроитель, промышленник, основатель корпорации Chrysler.


🌐 Знаковые события

1966 — советский спутник «Луна-10» стал первым в мире космическим аппаратом, облетевшим Луну.


#Biography #Birth_Date #Events #02апреля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Раздел 8. Stream API и функциональный стиль в Java

Глава 8: За пределами коллекций. Бесконечность и I/O

Бесконечные стримы и ленивая генерация

Ранее Stream API рассматривался как инструмент обработки конечных коллекций данных. Но фундаментальная мощь потоков — в их способности моделировать бесконечные последовательности, вычисляемые по требованию. Это сдвигает фокус с "данных в памяти" к "процессу генерации", открывая паттерны для числовых последовательностей, генераторов уникальных идентификаторов, обработки потоковых источников вроде сетевых соединений.


Stream.iterate: рекурсия с состоянием

Метод Stream.iterate создаёт поток, где каждый следующий элемент вычисляется из предыдущего.

Перегрузка Java 8 принимает начальное значение и унарную функцию:
// Натуральные числа: 0, 1, 2, 3...
Stream<Integer> natural = Stream.iterate(0, n -> n + 1);

// Степени двойки: 1, 2, 4, 8...
Stream<Integer> powersOfTwo = Stream.iterate(1, n -> n * 2);

// Фибоначчи: пара (a, b) -> (b, a+b)
Stream<long[]> fibonacci = Stream.iterate(
new long[]{0, 1},
f -> new long[]{f[1], f[0] + f[1]}
);


Ключевая особенность: iterate сохраняет состояние между элементами. Это делает его непараллелизуемым — каждый элемент зависит от предыдущего, разделение невозможно. Попытка вызвать .parallel() на таком потоке не даст выигрыша, а может замедлить из-за накладных расходов.


Java 9+: iterate с предикатом остановки

Перегрузка с тремя параметрами добавляет условие продолжения:
// Числа от 0 до 99
Stream<Integer> limited = Stream.iterate(
0, // seed
n -> n < 100, // hasNext (предикат продолжения)
n -> n + 1 // next (функция следующего)
);


Это заменяет паттерн iterate(...).limit(n) более семантически ясным конструктом. Предикат проверяется перед генерацией каждого элемента, включая seed. Если hasNext(seed) ложно, поток пуст.


Stream.generate: чистая генерация без состояния

Stream.generate принимает Supplier — функцию без аргументов, возвращающую значение. Каждый вызов независим, что теоретически позволяет параллелизм (хотя реализация в OpenJDK не делит такие потоки эффективно).
// Постоянное значение
Stream<String> constants = Stream.generate(() -> "repeat");

// Случайные числа
Stream<Double> randoms = Stream.generate(Math::random);

// Уникальные ID через атомарный счётчик
AtomicLong idGenerator = new AtomicLong(0);
Stream<String> uniqueIds = Stream.generate(() -> "ID-" + idGenerator.incrementAndGet());


generate идеален для стохастических или внешне определяемых последовательностей, где нет рекуррентной зависимости. Но бесконечность требует ограничения: limit, takeWhile или короткое замыкание терминальной операцией.


takeWhile и dropWhile: предикатные границы

Java 9 добавила методы takeWhile и dropWhile, критически отличные от filter по семантике:
filter проверяет каждый элемент независимо, пропуская или отбрасывая по условию
takeWhile пропускает элементы, пока предикат истинен, и останавливает поток при первом ложном
dropWhile отбрасывает элементы, пока предикат истинен, и продолжает с первого ложного

// Сортированный поток температур
IntStream temperatures = IntStream.of(15, 18, 22, 25, 19, 12, 8);

// takeWhile: температура ниже 25
temperatures.takeWhile(t -> t < 25)
.forEach(System.out::println); // 15, 18, 22 — остановка на 25

// dropWhile: пропускаем прохладную погоду
temperatures.dropWhile(t -> t < 20)
.forEach(System.out::println); // 22, 25, 19, 12, 8


Ключевое отличие takeWhile от filter — short-circuit поведение. filter(t -> t < 25) обработал бы все элементы, включая 19, 12, 8. takeWhile останавливается, предполагая, что последовательность упорядочена и дальнейшие элементы не интересны.

Это критично для бесконечных потоков:
// Бесконечная последовательность, ограниченная условием
Stream.iterate(1, n -> n * 2)
.takeWhile(n -> n < 1000) // 1, 2, 4, 8, ..., 512 — остановка
.forEach(System.out::println);


Без takeWhile пришлось бы использовать limit, требующий знания количества элементов заранее, или filter, не останавливающийся.


#Java #для_новичков #beginner #stream_api
👍6