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
С 07.02 по 13.02
Предыдущий пост(с 31.01 по 06.02)

Воскресный мотивационный пост:
Не было мотивации

Запись встреч/видео:
5. Бизнес-метрики в Spring Boot: Micrometer и реальная Observability

Обучающие статьи:
Раздел 8. Stream API и функциональный стиль

Глава 1: Философский фундамент. От шагов к преобразованиям
Практика: Рефакторинг мышления в «Библиотеке»

Глава 2: Анатомия Stream API. Ленивость и стоимость операций
Stream — это не данные, это план

Советы по Java:
[Совет по Java #003] Никогда не используйте float и double для точных расчетов с деньгами. Используйте BigDecimal или целые числа (копейки, центы).
[Совет по Java #004] При сравнении строк, которые могут быть null, вызывайте equals на константе или используйте Objects.equals().
[Совет по Java #005] Закрывайте ресурсы (InputStream, Connection, Scanner) в блоке try-with-resources. Это гарантирует их закрытие даже при исключении.

Полезные статьи и видео:
Паттерн Transactional Outbox — обеспечиваем консистентность между микросервисами на примере Java


Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍3
История IT-технологий сегодня — 15 Февраля

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

Никлаус Вирт (нем. Niklaus Emil Wirth; 15 февраля 1934, Винтертур, Цюрих — 1 января 2024, Цюрих) — швейцарский информатик, программист, один из известнейших теоретиков в области разработки языков программирования, профессор компьютерных наук Швейцарской высшей технической школы Цюриха (ETHZ), лауреат премии Тьюринга 1984 года. Создатель и ведущий проектировщик языков программирования Паскаль, Модула-2, Оберон.

Джордж Джонстон Стони (англ. George Johnstone Stoney, 1826—1911) — британский ирландский физик и математик, более всего известный тем, что ввёл в науку термин «электрон», определив его как «фундаментальную единицу измерения электроэнергии». Ввёл в обиход термин волновое число и его обозначение (k) используется до сих пор.

Галиле́о Галиле́й (итал. Galileo Galilei; 15 февраля 1564, Пиза — 8 января 1642, Арчетри) — итальянский физик, механик, астроном, философ, математик, оказавший значительное влияние на науку своего времени. Одним из первых использовал телескоп для наблюдения небесных тел и сделал ряд выдающихся астрономических открытий. Галилей — основатель экспериментальной физики. Своими экспериментами он убедительно опроверг умозрительную физику Аристотеля и заложил фундамент классической механики.


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

1995 — в США арестован хакер Кевин Митник, взломавший компьютерную сеть Пентагона.


#Biography #Birth_Date #Events #15февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
6. От сырых метрик к production-мониторингу: Prometheus и Grafana

Ваше приложение собирает метрики, но вы всё ещё не понимаете, что происходит с бизнесом? 🤓

В предыдущем видео мы добавили @BusinessMetric и научили OrderHub считать заказы. Но метрики без визуализации — это как камера без монитора: данные есть, а увидеть их нельзя.

В этом видео мы построим полный production-стек наблюдаемости:
• Prometheus — научимся собирать и хранить метрики
• Grafana — превратим цифры в живые дашборды
• Golden Signals — поймём язык SRE (QPS, Latency, Errors, Saturation)
• Бизнес-метрики — сделаем видимыми для всей команды
• Алерты — настроим Telegram, чтобы узнавать о проблемах первыми

Исходный код проекта на GitHub очень ждет Ваших звезд ☺️

Ссылка на Youtube
Ссылка на Рутьюб

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥4🆒21
История IT-технологий сегодня — 16 Февраля

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

Ю́рий Ива́нович Ма́нин (16 февраля 1937, Симферополь, СССР — 7 января 2023) — советский и немецкий математик, алгебраический геометр, педагог. Один из основоположников некоммутативной алгебраической геометрии, теории квантовых вычислений и квантовой информатики.

Робе́рта Линн Уи́льямс (англ. Roberta Lynn Williams, урождённая Хойер, англ. Heuer; 16 февраля 1953 года, Лос-Анджелес, Калифорния, США)американский дизайнер компьютерных игр, соучредитель компании Sierra On-Line. Вышедшая в 1980 году её первая игра, Mystery House, имела скромный коммерческий успех и считается первой графической приключенческой игрой. Уильямс также известна созданием и поддержкой серии King’s Quest, и разработкой видеоигры Phantasmagoria.


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

1978 — создана первая компьютерная электронная доска объявлений — BBS.


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

Глава 2: Анатомия Stream API. Ленивость и стоимость операций

Ленивость и Short-Circuit — ключ к эффективности

Ленивость в Stream API — не метафора, а конкретное техническое свойство, которое можно продемонстрировать экспериментально. Рассмотрим код, который выглядит как активная операция, но на самом деле является пассивным декларацией:
Stream<Book> stream = library.getBooks().stream()
.peek(book -> System.out.println("Просматриваем: " + book.title()))
.filter(book -> book.year() > 1950);

System.out.println("Конвейер построен");
// На консоли только: "Конвейер построен"
// Ни одной строки "Просматриваем..." не появится


Метод peek — промежуточная операция, предназначенная преимущественно для отладки. Он принимает Consumer — функцию, которая получает элемент и ничего не возвращает. Интуитивно кажется, что при вызове peek с System.out::println мы должны увидеть вывод. Но поток остаётся холодным: без терминальной операции конвейер — это мёртвая структура данных, не связанная с вычислениями.

Это принципиальное отличие от коллекций. Если бы peek был методом List, он бы немедленно прошёлся по всем элементам. В Stream API peek лишь добавляет узел в граф операций, сохраняя ссылку на переданный Consumer для будущего использования.

Ленивость достигается через отложенную композицию. Каждая промежуточная операция возвращает новый объект Stream, который знает о своей предшественнице и о логике, которую нужно применить. Но само применение откладывается до момента, когда терминальная операция запросит элементы. Тогда происходит обратная реакция: терминальная операция спрашивает у предыдущей стадии элемент, та — у своей предшественницы, и так до источника.


Порядок операций: семантика vs производительность

Ленивость открывает возможности для оптимизации, но эти возможности реализуются только при правильном порядке операций. Рассмотрим два варианта одной задачи: найти первую дорогую книгу после тяжёлой трансформации.

Антипаттерн: дорогая работа перед фильтрацией
Optional<ProcessedBook> result = books.stream()
.map(b -> heavyTransform(b)) // Тяжёлая операция для ВСЕХ книг
.filter(b -> b.price() > 100) // Фильтрация уже обработанных
.findFirst(); // Возврат первой подходящей


Здесь heavyTransform — метод, требующий значительных ресурсов: обращение к внешнему API, сложные вычисления, парсинг больших данных. Проблема в том, что map применяется ко всем элементам потока до того, как filter получает шанс отсеять неподходящие. Если в библиотеке 10 000 книг, из которых дорогими являются только 50, мы выполним 10 000 тяжёлых трансформаций вместо 50 (или даже меньше, если учитывать short-circuit).

Оптимальный порядок: фильтрация перед дорогой работой
Optional<ProcessedBook> result = books.stream()
.filter(b -> b.price() > 100) // Сначала дешёвая проверка
.map(b -> heavyTransform(b)) // Тяжёлая операция только для подходящих
.findFirst(); // Остановка после первой успешной


Теперь heavyTransform выполняется только для книг, прошедших фильтр цены. Но главное преимущество — short-circuit поведение findFirst. Как только первая дорогая книга найдена и трансформирована, обход источника прекращается. Если первая книга в списке стоит 150, мы выполняем ровно одну тяжёлую операцию вместо 10 000.

Этот принцип универсален: дешёвые предикаты должны предшествовать дорогим трансформациям. Проверка полей объекта (цена, год, флаги) практически бесплатна по сравнению с вычислениями, вводом-выводом или аллокациями новых объектов.


#Java #для_новичков #beginner #stream_api #stream #short_circuit
👍4
Short-Circuit операции: управляемое прерывание

Short-circuit операции — терминальные операции, которые не требуют полного обхода источника для вычисления результата. Они аналогичны оператору break в императивных циклах, но реализованы через кооперативную отмену выполнения.

Каталог short-circuit операций:

findFirst() — возвращает первый элемент в порядке потока
findAny() — возвращает произвольный элемент (оптимизация для параллельных потоков)
anyMatch(Predicate) — true, если хотя бы один элемент удовлетворяет предикату
allMatch(Predicate) — true, если все элементы удовлетворяют предикату (может остановиться при первом нарушении)
noneMatch(Predicate) — true, если ни один элемент не удовлетворяет предикату
limit(long) — ограничивает количество элементов (stateful, но поддерживает short-circuit)

Механизм реализации short-circuit основан на флаге отмены. Когда операция типа findFirst находит нужный элемент, она устанавливает флаг cancellationRequested в объекте Sink. Этот флаг распространяется вверх по конвейеру, и источник прекращает обход при первой возможности.

// Упрощённая логика findFirst (псевдокод)
public Optional<T> findFirst() {
final AtomicReference<T> result = new AtomicReference<>();

evaluate(new TerminalSink<T, Void>() {
boolean found = false;

@Override
public void accept(T value) {
if (!found) {
result.set(value);
found = true;
// Запрос отмены дальнейшего обхода
cancellationRequested = true;
}
}

@Override
public boolean cancellationRequested() {
return found; // Сигнализируем об остановке
}
});

return found ? Optional.of(result.get()) : Optional.empty();
}


Для anyMatch логика ещё эффективнее: как только предикат возвращает true, обход останавливается немедленно, без необходимости обрабатывать оставшиеся элементы.
// Эффективный поиск: остановка при первом совпадении
boolean hasExpensive = books.stream()
.anyMatch(b -> b.price() > 1000); // Проверяет до первого совпадения, не все

// Неэффективная альтернатива
boolean hasExpensive = books.stream()
.filter(b -> b.price() > 1000)
.findFirst()
.isPresent(); // Больше объектов создаётся, но семантически эквивалентно


Особенность limit. Операция limit(n) — промежуточная, но поддерживающая short-circuit. Она отслеживает количество пропущенных элементов и запрашивает отмену обхода после достижения лимита. Это критично для бесконечных потоков:
Stream.iterate(0, i -> i + 1)  // Бесконечная последовательность
.filter(i -> i % 2 == 0) // Чётные числа — тоже бесконечно
.limit(10) // Ограничение делает конечным
.forEach(System.out::println); // Выводит 0, 2, 4, 6, 8, 10, 12, 14, 16, 18


Без limit forEach никогда не завершился бы. С limit конвейер корректно останавливается после десятого элемента.


#Java #для_новичков #beginner #stream_api #stream #short_circuit
👍4
Вертикальная vs горизонтальная обработка

Критическое заблуждение о Stream API — представление, что элементы обрабатываются "по слоям": сначала все проходят filter, затем все проходят map, затем все попадают в collect. Реальность — вертикальная обработка: каждый элемент проходит через весь конвейер до конца, прежде чем берётся следующий.

Это архитектурное решение, необходимое для short-circuit. Если бы мы сначала фильтровали все элементы, мы не могли бы остановиться после нахождения первого подходящего — нам нужно было бы видеть все элементы, чтобы начать следующую стадию.

Рассмотрим конвейер:
books.stream()
.filter(b -> {
System.out.println("Filter: " + b.title());
return b.year() > 1950;
})
.map(b -> {
System.out.println("Map: " + b.title());
return b.title().toUpperCase();
})
.limit(2)
.forEach(System.out::println);


При вертикальной обработке вывод будет чередоваться:
Filter: Book A (1900)     // Отсеян, Map не вызывается
Filter: Book B (1960) // Прошёл
Map: Book B // Преобразован
BOOK B // forEach
Filter: Book C (1970) // Прошёл
Map: Book C // Преобразован
BOOK C // forEach
// limit(2) достигнут, остановка


Если бы обработка была горизонтальной, мы увидели бы сначала все "Filter:", затем все "Map:", затем результат. Но тогда limit(2) не смог бы прервать выполнение после второго элемента — все фильтрации уже произошли бы.
Вертикальность имеет следствия для побочных эффектов. Если peek или map содержат логирование или модификацию внешнего состояния, порядок вызовов будет чередующимся, а не пакетным. Это затрудняет отладку, но обеспечивает корректность short-circuit семантики.


Ленивость и исключения


Ленивое выполнение влияет на обработку исключений. Если исключение возникает в лямбде промежуточной операции, оно будет брошено только при терминальной операции, что может затруднить трассировку:
Stream<Book> stream = books.stream()
.map(b -> {
if (b.year() < 0) throw new IllegalStateException("Invalid year: " + b.year());
return b;
});
// Исключение не брошено

stream.forEach(System.out::println); // Исключение здесь, стектрейс указывает на forEach


Более того, если short-circuit операция останавливается до элемента с ошибкой, исключение никогда не возникнет:
books.stream()
.map(b -> {
if (b.year() < 0) throw new IllegalStateException("Invalid year");
return b;
})
.findFirst(); // Если первая книга валидна, исключение для второй (с ошибкой) не бросится

Это делает потоки непредсказуемыми с точки зрения полноты валидации. Если необходима проверка всех элементов, short-circuit операции неприменимы — нужно использовать collect или forEach, требующие полного обхода.


#Java #для_новичков #beginner #stream_api #stream #short_circuit
👍4🔥1
Что выведет код?

import java.util.function.Function;

public class Task160226 {
public static void main(String[] args) {
Function<Integer, Integer> f1 = i -> i * 2;
Function<Integer, Integer> f2 = i -> i + 1;
Function<Integer, Integer> f3 = f1.compose(f2);
Function<Integer, Integer> f4 = f1.andThen(f2);

System.out.println(f3.apply(5) + " " + f4.apply(5));
}
}


#Tasks
🔥4
Варианты ответа:
Anonymous Quiz
53%
12 11
16%
11 12
21%
10 12
11%
12 10
👍2
Что такое лямбда-выражения и функциональные интерфейсы? 🤓

Ответ:

Лямбда-выражения
— это краткая форма записи анонимных функций (методов без объявления). Имеют синтаксис (параметры) -> { тело }.

Они позволяют передавать поведение как аргумент. Функциональный интерфейс — это интерфейс, который содержит ровно один абстрактный метод (SAM - Single Abstract Method). Такие интерфейсы могут быть представлены лямбда-выражениями.

Примеры: Runnable, Comparator, а также новые в Java 8: Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>. Аннотация
@FunctionalInterface является необязательной, но указывает на намерение.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
История IT-технологий сегодня — 17 Февраля

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

Дженсен Хуанг (англ. Jensen Huang, кит. трад. 黃仁勳, пиньинь Huáng Rénxūn, палл. Хуан Жэньсюнь; род. 17 февраля 1963) — американский предприниматель тайваньского происхождения, сооснователь, президент и главный исполнительный директор компании NVIDIA. В интернете получил прозвище «Куртка» за то что на всех своих презентациях он одет в черную кожаную куртку.


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

1972 — образован Научно-исследовательский вычислительный центр АН СССР.

1983 — «день рождения»
языка программирования Ада.


#Biography #Birth_Date #Events #17февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #006]

Тема: Используйте Enum вместо строковых констант для ограниченного набора значений.

Проблема: Использование строковых констант (String constants) для представления фиксированного набора допустимых значений является антипаттерном, известным как "Stringly Typed".

Такой подход лишен типобезопасности: компилятор не может предотвратить передачу в метод произвольной строки, не входящей в допустимый набор. Это приводит к ошибкам, обнаруживаемым только на этапе выполнения.

Кроме того, строковые константы не обеспечивают автодополнения в IDE (если только не используются статические импорты, но и они не решают проблему проверки на этапе компиляции), а рефакторинг (например, переименование значения) требует глобального поиска и замены по всему проекту, что чревато ошибками.

Решение: Использование перечислений (enum), которые являются полноценными классами в Java. enum предоставляет типобезопасность — компилятор гарантирует, что в метод, принимающий enum, может быть передан только один из предопределенных элементов перечисления или null.

Это делает код самодокументируемым, улучшает читаемость и полностью исключает класс ошибок, связанных с опечатками в строковых литералах. Более того, enum может содержать поля, методы и реализовывать интерфейсы, что позволяет инкапсулировать поведение, специфичное для каждого элемента, прямо внутри перечисления.

package com.example.model;

public enum OrderStatus {
NEW("Новый") {
@Override
public void handle() {
System.out.println("Проверка наличия товара для нового заказа");
}
},
PROCESSING("В обработке") {
@Override
public void handle() {
System.out.println("Передача в службу доставки");
}
},
COMPLETED("Завершен") {
@Override
public void handle() {
System.out.println("Отправка опроса удовлетворенности");
}
};

private final String displayName;

OrderStatus(String displayName) {
this.displayName = displayName;
}

public String getDisplayName() {
return displayName;
}

public abstract void handle();
}



#Java #советы
👍5
// OrderService.java - Сервисный слой
package com.example.service;

import com.example.model.OrderStatus;

public class OrderService {

public void processOrder(OrderStatus status) {
if (status == null) {
throw new IllegalArgumentException("Статус не может быть null");
}

status.handle();
displayStatusInfo(status);
}

private void displayStatusInfo(OrderStatus status) {
switch (status) {
case NEW -> System.out.println("Отображение как: " + status.getDisplayName());
case PROCESSING -> System.out.println("Отображение как: " + status.getDisplayName());
case COMPLETED -> System.out.println("Отображение как: " + status.getDisplayName());
}
}
}


Запуск кода.
// Application.java - Точка входа
package com.example;

import com.example.model.OrderStatus;
import com.example.service.OrderService;

public class Application {
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.processOrder(OrderStatus.NEW);
}
}


Объяснение: Каждое перечисление в Java — это класс, который неявно наследуется от java.lang.Enum.

Элементы перечисления являются статическими финализированными экземплярами этого класса. Это позволяет добавлять в enum поля (например, для хранения описания), конструкторы (которые вызываются для создания каждого элемента), методы и даже реализовывать интерфейсы.

Особую мощь предоставляет возможность объявлять абстрактные методы и переопределять их для каждого элемента (как в примере с handle()). Это реализует паттерн "Стратегия" на уровне языка и позволяет избежать громоздких конструкций if-else или switch при обработке значений.

Разработчик должен рассматривать enum не просто как типизированный набор именованных констант, а как полноценный инструмент для моделирования предметной области с инкапсуляцией данных и поведения.


#Java #советы
👍5
Что выведет код?

public class Task170226 {
enum Color {
RED, GREEN, BLUE;

static {
System.out.println("Static block");
}

Color() {
System.out.println("Constructor: " + this);
}
}

public static void main(String[] args) {
System.out.println("Start");
Color c = Color.RED;
System.out.println("End");
}
}


#Tasks
👍3
Объясните паттерн Singleton. Как его реализовать в Java? 🤓

Ответ:

Singleton
гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.

Базовая небезопасная для потоков реализация через ленивую инициализацию.

Потокобезопасные реализации:

1) Eager Initialization: создание экземпляра в статическом поле при загрузке класса.
2) Double-Checked Locking: проверка в synchronized блоке с volatile переменной.
3) Static Nested Class (Holder): самый популярный и безопасный способ. Внутренний статический класс содержит экземпляр, который создается при первом обращении.
4) enum: наилучший способ по Джошуа Блоху, защищает от рефлексии и сериализации.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
История IT-технологий сегодня — 18 Февраля

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

Алесса́ндро Джузе́ппе Анто́нио Анаста́сио Во́льта (итал. Alessandro Giuseppe Antonio Anastasio Volta; 18 февраля 1745, Комо — 5 марта 1827, там же) — итальянский физик, химик и физиолог, один из основоположников учения об электричестве; граф (1810). Создатель вольтова столба, первого устойчивого химического источника тока. Его работа фактически запустила эпоху управляемого электричества; единица напряжения «вольт» названа в его честь.


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

2021 – Марсоход « Персеверанс» , предназначенный для исследования кратера Езеро на Марсе в рамках миссии НАСА «Марс 2020» , успешно совершил посадку.


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

Глава 2: Анатомия Stream API. Ленивость и стоимость операций

(Практика): Диссекция конвейера в «Библиотеке»

Убедитесь, что в проекте «Библиотека» настроены базовые классы. Для этого урока достаточно простого main метода в отдельном тестовом классе или в существующем Main.


Построение конвейера с наблюдением


Шаг 1. Создание источника с отслеживанием

Создайте метод demonstrateLazyEvaluation() в классе Main или отдельном тестовом классе.

Начните с простого источника данных и операции peek, которая позволяет наблюдать за элементами без изменения потока:
IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))


Что происходит: IntStream.range(0, 10) создаёт поток чисел от 0 до 9. peek — промежуточная операция, которая выполняет действие при прохождении элемента. На этом этапе ничего не выводится, потому что нет терминальной операции.

Шаг 2. Добавление фильтрации


Добавьте фильтр чётных чисел с ещё одним peek:
IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))
.filter(i -> i % 2 == 0)
.peek(i -> System.out.println("filtered: " + i))


Что происходит: filter — промежуточная операция, которая пропускает только элементы, удовлетворяющие условию. Обе операции peek остаются промежуточными. Поток всё ещё не выполняется.

Шаг 3. Добавление преобразования

Добавьте возведение в квадрат с третьим peek:
IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))
.filter(i -> i % 2 == 0)
.peek(i -> System.out.println("filtered: " + i))
.map(i -> i * i)
.peek(i -> System.out.println("mapped: " + i))


Что происходит: map — промежуточная операция, преобразующая каждый элемент. Конвейер построен, но не запущен.


Часть 2. Запуск конвейера разными способами

Вариант А. Короткозамкнутая операция findFirst()

Добавьте терминальную операцию findFirst():
OptionalInt result = IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))
.filter(i -> i % 2 == 0)
.peek(i -> System.out.println("filtered: " + i))
.map(i -> i * i)
.peek(i -> System.out.println("mapped: " + i))
.findFirst();


Наблюдайте за выводом в консоли. Запишите, в каком порядке появляются сообщения.

Ожидаемый вывод:
source: 0
filtered: 0
mapped: 0
Почему только одна цепочка? Потому что findFirst() — короткозамкнутая операция. Как только найден первый подходящий элемент (0), выполнение останавливается. Остальные числа от 1 до 9 даже не достигают источника.


Вариант Б. Полная обработка forEach()

Замените findFirst() на forEach():
IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))
.filter(i -> i % 2 == 0)
.peek(i -> System.out.println("filtered: " + i))
.map(i -> i * i)
.peek(i -> System.out.println("mapped: " + i))
.forEach(i -> System.out.println("result: " + i));


Наблюдайте за выводом. Запишите порядок сообщений.

Ожидаемый вывод:
source: 0
filtered: 0
mapped: 0
result: 0
source: 1
source: 2
filtered: 2
mapped: 4
result: 4
source: 3
source: 4
filtered: 4
mapped: 16
result: 16
...



Часть 3. Анализ «вертикальной» vs «горизонтальной» обработки

Вертикальная обработка (фактическая)

Stream API обрабатывает элементы по одному через весь конвейер:
Элемент 0: source → filter → map → result
Элемент 1: source → (отфильтрован) → ...
Элемент 2: source → filter → map → result


Это ленивая оценка: каждый элемент проходит через все промежуточные операции до терминальной, прежде чем берётся следующий.

Горизонтальная обработка (гипотетическая)

Можно было бы ожидать, что сначала все элементы проходят source, потом все проходят filter, потом все проходят map:
Все source: 0,1,2,3,4,5,6,7,8,9
Все filtered: 0,2,4,6,8
Все mapped: 0,4,16,36,64


Но Stream API так не работает. Почему?


#Java #для_новичков #beginner #Практика #stream_api #lazy
👍4
Часть 4. Почему вертикальная обработка?

Преимущества вертикального подхода

Эффективность с короткозамкнутыми операциями

При findFirst() или anyMatch() обработка останавливается при первом успехе. Если бы обработка была горизонтальной, пришлось бы прогнать все элементы через все операции, даже когда ответ найден сразу.
Экономия памяти
Не нужно хранить промежуточные коллекции между этапами. Каждый элемент обрабатывается «на лету».
Возможность бесконечных потоков
IntStream.iterate(0, i -> i + 1) — бесконечный поток. Горизонтальная обработка невозможна (нельзя собрать все элементы), вертикальная — работает с короткозамкнутыми операциями.


Часть 5. Применение к проекту «Библиотека»

Задача: найти первую книгу автора «X» с минимальными затратами

Императивный подход (неэффективный):

// Создаём полный список, сортируем, берём первый
List<Book> filtered = new ArrayList<>();
for (Book b : books) {
if (b.getAuthor().equals("X")) {
filtered.add(b);
}
}
filtered.sort(Comparator.comparingInt(Book::getYear));
Book first = filtered.get(0); // Всё равно обработали все книги


Stream с вертикальной обработкой (эффективный):
Optional<Book> first = books.stream()
.filter(b -> b.getAuthor().equals("X"))
.sorted(Comparator.comparingInt(Book::getYear))
.findFirst(); // Остановится при первой найденной книге


Вопрос для размышления: Почему sorted в этом примере всё-таки требует полного прохода? Когда Stream API может избежать полной сортировки?


Часть 6. Эксперименты

Эксперимент 1: Порядок операций

Поменяйте местами filter и map в конвейере:
IntStream.range(0, 10)
.peek(i -> System.out.println("source: " + i))
.map(i -> i * i) // теперь раньше
.peek(i -> System.out.println("mapped: " + i))
.filter(i -> i % 2 == 0) // теперь позже
.peek(i -> System.out.println("filtered: " + i))
.forEach(i -> System.out.println("result: " + i));


Наблюдение: Нечётные числа тоже проходят map (становятся нечётными квадратами), потом отфильтровываются. Это менее эффективно — лишние вычисления.
Вывод: Порядок промежуточных операций влияет на производительность. Фильтруйте как можно раньше.

Эксперимент 2: limit как короткозамкнутая операция

Добавьте limit(3) перед forEach:
IntStream.range(0, 1000)  // большой диапазон
.peek(i -> System.out.println("source: " + i))
.filter(i -> i % 2 == 0)
.peek(i -> System.out.println("filtered: " + i))
.limit(3)
.forEach(i -> System.out.println("result: " + i));


Наблюдение: Обработка останавливается после трёх отфильтрованных элементов. Источник даже не доходит до 1000.


Часть 7. Ключевые термины

Промежуточная операция (intermediate)

Возвращает новый Stream. Не выполняется до терминальной. Примеры: filter, map, sorted, peek, limit.
Терминальная операция (terminal)
Запускает выполнение конвейера, возвращает результат или побочный эффект. Примеры: forEach, findFirst, collect, reduce, anyMatch.
Короткозамкнутая операция (short-circuiting)
Может завершиться до обработки всех элементов. Примеры: findFirst, findAny, anyMatch, allMatch, noneMatch, limit.
Ленивая оценка (lazy evaluation)
Вычисление откладывается до момента, когда результат действительно нужен.


Практическое задание

Задача 1: Оптимизация порядка
В проекте «Библиотека» создайте метод findRecentBooksByAuthor(String author, int year, int limit), который находит limit книг автора, изданных после year, отсортированных по убыванию года.

Постройте конвейер и обоснуйте порядок операций:
Почему filter по автору стоит поставить первым?
Почему filter по году лучше объединить с фильтром автора или оставить отдельным?
Где разместить sorted относительно limit?

Задача 2: Сравнение подходов
Реализуйте ту же задачу императивно (циклами).

Сравните:
Объём кода
Читаемость намерения
Поведение при пустой библиотеке (Optional vs null)

Задача 3: Отладка конвейера
Добавьте peek в разные точки конвейера из Задачи 1. Убедитесь, что при limit(3) обработка действительно останавливается рано.


#Java #для_новичков #beginner #Практика #stream_api #lazy
👍3
Что выведет код?

import java.util.Arrays;

public class Task180226 {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = arr1;

arr3[0] = 10;

System.out.println(arr1 == arr2);
System.out.println(arr1.equals(arr2));
System.out.println(Arrays.equals(arr1, arr2));
System.out.println(arr1[0] + " " + arr2[0] + " " + arr3[0]);
}
}


#Tasks
👍3🗿1