Java for Beginner
869 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
Жизненно
🔥9
История IT-технологий сегодня — 01 Февраля

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

Не нашел(


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

1982 компания Intel выпустила первый Intel 80286 — 16-разрядный процессор второго поколения архитектуры x86.


#Biography #Birth_Date #Events #01февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
А вы читаете этот канал? Давайте посчитаем сколько нас из 800, реально заходят и читают посты?
Anonymous Poll
81%
Я читаю!
9%
Я не читаю, но голос оставлю)
9%
Я есть Грут
🔥4
4. Spring Boot Actuator: телеметрия вашего микросервиса

В этом видео мы превращаем OrderHub из чёрного ящика в наблюдаемую production-систему.
Spring Boot Actuator — это не просто библиотека, а стандартный Runtime API, через который микросервис общается с платформой (Kubernetes, мониторинг, SRE).

Что вы узнаете:
🔹 Health как контракт с оркестратором
- Как Kubernetes использует /health для liveness и readiness probes
- Иерархия HealthIndicators: от диска до бизнес-логики
- Что происходит, когда база данных "падает"

🔹 Metrics — язык общения с платформой
- Как Micrometer и Actuator работают вместе
- Four Golden Signals для SRE: latency, traffic, errors, saturation
- Почему метрики должны быть с тегами

🔹 Info — паспорт сервиса в распределённой системе
- Immutable metadata для быстрой диагностики инцидентов

🔹 Advanced endpoints — инструменты последней линии обороны
- Для чего и когда испольщовать /env, /heapdump, /threaddump
- Динамическое управление логированием через /loggers
- Почему эти инструменты опасны в неверных руках

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

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

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

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

Джон Генри Холланд (англ. John Henry Holland; 2 февраля 1929, Форт-Уэйн — 9 августа 2015, Анн-Арбор)американский учёный, профессор психологии, профессор электротехники и информатики в Мичиганском университете, Анн-Арбор. Один из первых учёных, начавших изучать сложные системы и нелинейную науку; известен как отец генетических алгоритмов.

Ральф Чарльз Меркл (англ. Ralph Charles Merkle; родился 2 февраля 1952, Беркли, Калифорния, США)американский криптограф, один из создателей криптографии с открытым ключом (схема Меркла, деревья Меркла и др.); деревья Меркла являются базовым примитивом в блокчейнах, системах проверки целостности данных, файловых системах и ряде протоколов безопасности.


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

2000 – первая в Европе цифровая кинопроекция (Париж), реализованная Филиппом Бинаном с использованием технологии DLP CINEMA, разработанной компанией Texas Instruments .


#Biography #Birth_Date #Events #02февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Раздел 7. Алгоритмы

Глава 5: Рекурсия, деревья и введение в динамическое программирование

Практическое применение рекурсии в библиотечной системе

Цели:
Освоить принципы работы с древовидными структурами
Научиться применять рекурсию для обхода иерархических данных
Сравнить рекурсивные и итеративные подходы
Понять оптимизацию рекурсии через мемоизацию


Часть 1: Моделирование древовидной структуры жанров

Постановка задачи:
В нашей библиотеке жанры книг образуют иерархию.

Например:
Литература (корень)
Художественная литература
Фантастика
Научная фантастика
Фэнтези
Нехудожественная литература

Вам нужно: Создать структуру данных, которая позволит хранить такую иерархию.

Шаг 1.1: Создайте класс Genre

Что нужно сделать:

Определите, какие поля понадобятся для хранения:
Название жанра
Список поджанров (дочерние элементы)
Список книг, относящихся к этому жанру

Реализуйте:
Конструктор, принимающий название жанра
Методы для добавления поджанра и книги
Геттеры для доступа к данным

Вопрос для размышления: Почему важно, чтобы поджанры хранились как список объектов Genre?

Шаг 1.2: Свяжите книги с жанрами

Что нужно сделать:

Обновите класс Book:
Добавьте поле для хранения основного жанра книги
Измените конструктор для установки жанра

Подумайте: Может ли книга принадлежать к нескольким жанрам? Как это реализовать?

Шаг 1.3: Построение дерева жанров

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

В классе Library создайте метод initGenres(), который:
Создаёт корневой жанр "Литература"
Добавляет 3-4 уровня вложенности жанров
Распределяет существующие книги по соответствующим жанрам

Проверка: Убедитесь, что у вас получилось дерево глубиной не менее 4 уровней.



Часть 2: Рекурсивный подсчёт книг в жанре

Задача: Напишите метод, который считает все книги в жанре и его поджанрах

Рекурсия работает так:
Базовый случай: если у жанра нет поджанров, возвращаем количество его книг
Рекурсивный случай: суммируем книги текущего жанра + рекурсивно вызываем метод для каждого поджанра

Пример:
Книг в "Фантастике" = 
книги в "Фантастике" +
книги в "Научной фантастике" +
книги в "Фэнтези"


Шаг 2.1: Реализуйте метод countBooksRecursive()

Что нужно сделать:

Добавьте метод в класс Genre
Реализуйте рекурсивный подсчёт по описанному алгоритму
Протестируйте на разных уровнях дерева

Контрольные вопросы:
Что произойдёт, если в дереве будет циклическая ссылка?
Какова максимальная глубина рекурсии для вашего дерева?


Часть 3: Сравнение рекурсивного и итеративного обхода

Задача: Вывести структуру дерева жанров двумя способами

Шаг 3.1: Рекурсивный обход (Pre-order)

Алгоритм:
Обработать текущий узел (вывести информацию)
Рекурсивно обработать все дочерние узлы

Что нужно сделать:
Реализуйте метод printTreeRecursive(Genre genre, int depth), где depth используется для создания отступов при выводе.

Шаг 3.2: Итеративный обход с использованием стека

Можно эмулировать рекурсию с помощью стека:
Помещаем корневой элемент в стек

Пока стек не пуст:
Извлекаем элемент
Обрабатываем его
Добавляем в стек его детей

Что нужно сделать:
Реализуйте метод printTreeIterative(Genre root) с использованием Stack<Genre>.

Вопрос: Почему детей нужно добавлять в стек в обратном порядке?

Шаг 3.3: Сравнение подходов

Практическое задание:
Создайте дерево глубиной 10 уровней
Замерьте время выполнения обоих методов

Ответьте на вопросы:
Какой подход читаемее?
Какой безопаснее для глубоких деревьев?
Когда стоит использовать каждый из них?


#Java #для_новичков #beginner #algorithm #recursion #практика
👍3
Часть 4: Числа Фибоначчи - три подхода

Задача: Вычислить n-ное число Фибоначчи

Определение:
F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)

Шаг 4.1: Наивная рекурсия

Что нужно сделать:
Реализуйте метод fibRecursive(int n) по определению.

Анализ проблемы:
Нарисуйте дерево вызовов для n=5. Сколько раз вычисляется F(2)? F(3)?

Задание: Замерьте время выполнения для n=40. Почему это так долго?

Шаг 4.2: Мемоизация (Top-down динамическое программирование)

Идея: Сохранять уже вычисленные значения, чтобы не вычислять их повторно.

Что нужно сделать:
Создайте HashMap<Integer, Long> для хранения вычисленных значений

Измените рекурсивную функцию так, чтобы она:

Проверяла, нет ли значения в кэше
Сохраняла новое значение после вычисления

Вопрос: Как изменилось дерево вызовов?

Шаг 4.3: Итеративное решение (Bottom-up)

Алгоритм:
Если n <= 1: вернуть n
Иначе: последовательно вычислить все числа от 2 до n

Что нужно сделать:
Реализуйте метод fibIterative(int n) без рекурсии.

Задание: Сравните производительность всех трёх методов для n=50.


Итоговые задания

Задание 1: Рефакторинг
Перепишите один из рекурсивных методов в итеративный. Объясните, что изменилось в сложности кода.

Задание 2: Оптимизация
Добавьте проверку на циклические ссылки в дереве жанров. Что произойдёт без этой проверки?

Задание 3: Эксперимент
Создайте глубокое дерево (20+ уровней). Протестируйте оба метода обхода. При какой глубине возникает StackOverflowError?

Задание 4: Документация

Напишите комментарии к методам, объясняющие:
Сложность алгоритма (Big O)
Ограничения (максимальная глубина)
Рекомендации по использованию


#Java #для_новичков #beginner #algorithm #recursion #практика
👍4
Что выведет код?

import java.util.Arrays;
import java.util.Comparator;

public class Task020226 {
public static void main(String[] args) {
Integer[] arr = {5, 2, 8, 1, 3};

Comparator<Integer> comp = (a, b) -> {
System.out.print(a + ":" + b + " ");
return a - b;
};

Arrays.sort(arr, comp);
System.out.println("\n" + Arrays.toString(arr));
}
}


#Tasks
👍1
Что такое String Pool? 🤓

Ответ:

String Pool (пул строк)
— это специальная область в Heap (куче) памяти JVM, предназначенная для хранения уникальных литералов строк.

Когда создается строковый литерал (например, String s = "hello";), JVM ищет такую же строку в пуле. Если находит, то возвращает ссылку на существующий объект, если нет — создает новый объект в пуле. Это позволяет экономить память.

Оператор new String("hello") всегда создает новый объект в куче вне пула, даже если такая строка уже есть в пуле.


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

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

Не нашел(


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

1966 — АМС «Луна-9», запущенная 31 января, впервые в мире осуществила посадку на поверхность Луны в районе Океана Бурь и передала первую лунную фотопанораму.


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

Тема: Всегда переопределяйте equals() и hashCode() вместе, особенно для сущностей, которые будут храниться в HashSet или использоваться как ключи в HashMap.

Проблема: Нарушение ключевого контракта (contract) между методами equals() и hashCode().

Согласно спецификации Java, если два объекта равны согласно методу equals(Object), то вызов метода hashCode() для каждого из них должен возвращать одно и то же целочисленное значение.

Обратное утверждение не является обязательным: объекты с одинаковым хэш-кодом могут быть не равны (коллизия). Если разработчик переопределяет только equals(), основываясь, например, на внутренних полях объекта, но оставляет исходный hashCode() (который обычно возвращает уникальный код на основе адреса в памяти), то такие объекты, будучи логически равными, будут иметь разные хэш-коды. Это приведет к катастрофическому нарушению работы хэш-коллекций (HashSet, HashMap, ConcurrentHashMap).
Объекты не будут найдены в коллекции, дубликаты будут добавляться в HashSet, а операции с HashMap дадут непредсказуемые и ошибочные результаты. Такие ошибки трудно отлаживать, так как поведение становится недетерминированным.

Решение: Всегда совместное переопределение обоих методов, гарантирующее соблюдение их общего контракта. Реализация должна основываться на одном и том же наборе значимых (significant) полей объекта. Для вычисления хэш-кода рекомендуется использовать алгоритм, дающий хорошее распределение.

import java.util.Objects;

public final class Entity {
private final Long id;
private final String name;

public Entity(Long id, String name) {
this.id = id;
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true; // Проверка на идентичность ссылок
if (o == null || getClass() != o.getClass()) return false; // Проверка класса
Entity entity = (Entity) o;
// Сравнение по значимым полям. Используем Objects.equals() для null-safe сравнения.
return Objects.equals(id, entity.id) &&
Objects.equals(name, entity.name);
}

@Override
public int hashCode() {
// Используем хэш-функцию на основе тех же полей, что и в equals()
return Objects.hash(id, name);
}
}


Объяснение: Метод Objects.hash(Object...) предоставляет удобную и эффективную реализацию хэш-функции, основанную на содержимом переданных полей.

Важно, что в вычислении участвуют те же поля (id и name), которые используются в equals(). Класс объявлен как final, чтобы избежать наследования и потенциального нарушения симметричности контракта equals() (проблема сравнения объекта родительского класса с объектом дочернего). Использование final полей делает класс неизменяемым (immutable), что дополнительно гарантирует стабильность хэш-кода во времени — ключевое требование для корректной работы в качестве ключа в хэш-таблицах. Нарушение этого требования (изменение поля, участвующего в hashCode(), после помещения объекта в коллекцию) приведет к потере объекта в структуре данных.


#Java #советы
👍3🔥2🆒1
Что выведет код?

import java.util.HashSet;

public class Task030226 {
public static void main(String[] args) {
HashSet<Item030226> set = new HashSet<>();

Item030226 item1 = new Item030226(1, "apple");
Item030226 item2 = new Item030226(1, "apple");

set.add(item1);
item1.id = 2;
set.add(item2);

System.out.println(set.size());
System.out.println(set.contains(item1));
System.out.println(set.contains(item2));
}

static class Item030226 {
int id;
String name;

Item030226(int id, String name) {
this.id = id;
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item030226 item = (Item030226) o;
return id == item.id && name.equals(item.name);
}

@Override
public int hashCode() {
return id + name.hashCode();
}
}
}


#Tasks
👍1
👍1
Что такое конструктор? Можно ли наследовать конструктор? 🤓

Ответ:

Конструктор
— это специальный метод класса, который вызывается при создании нового объекта.

Его имя должно совпадать с именем класса, и у него нет возвращаемого типа. Конструкторы не наследуются.

При создании объекта класса-потомка всегда вызывается конструктор его родительского класса (явно через super(...) или неявно, вызывается конструктор по умолчанию super()).

Если в родительском классе нет конструктора по умолчанию, в классе-потомке обязательно нужно явно вызвать один из существующих.


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

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

Кеннет Лейн (Кен) То́мпсон (англ. Kenneth Lane Thompson; род. 4 февраля 1943) — американский программист; один из создателей UNIX и языка B, предшественника C; работал над файловыми системами, процессами, утилитами и общим дизайном UNIX, а позже участвовал в создании языка Go. Его работа задала стандарты для современных ОС (процессы, файлы, пайпы, текстовые утилиты), что прямой фундамент почти всей современной серверной IT и интернета.


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

2004 — запуск “Thefacebook” Марком Цукербергом в общежитии Гарварда: первоначально как соцсеть для студентов, позже выросшую в Facebook; это сильно повлияло на социальные сети, рекламу, big data, рекомендательные системы и вообще на то, как интернет устроен социально и коммерчески.


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

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

Императивный код — машина Тьюринга в миниатюре

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

Рассмотрим типичную задачу: необходимо из списка заказов отфильтровать выполненные, вычислить общую сумму и вернуть топ-3 заказа по стоимости.

В императивной реализации мы начинаем с пустого состояния и последовательно его трансформируем:
public List<Order> getTopThreeCompletedOrders(List<Order> orders) {
// Инициализация временного состояния
List<Order> completedOrders = new ArrayList<>();

// Линейный проход с условным накоплением
for (Order order : orders) {
if (order.getStatus() == Status.COMPLETED) {
completedOrders.add(order);
}
}

// Сортировка через изменение состояния (in-place или новая коллекция)
completedOrders.sort((a, b) -> b.getAmount().compareTo(a.getAmount()));

// Извлечение подмножества с проверкой границ
List<Order> result = new ArrayList<>();
int limit = Math.min(3, completedOrders.size());
for (int i = 0; i < limit; i++) {
result.add(completedOrders.get(i));
}

return result;
}


Здесь каждая строчка — это шаг.

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

Мы не описываем что хотим получить — мы диктуем как это получить, командуя машиной на каждом этапе.

Переменные-состояния в этом коде играют роль оперативной памяти машины Тьюринга. completedOrders — это наша лента, куда мы записываем промежуточные результаты. Переменная i в цикле — это позиция головки. Каждое присваивание — запись в ячейку. Каждое чтение — позиционирование головки.


Паттерны императивной обработки

Линейный проход — самая фундаментальная структура. Мы последовательно обрабатываем элементы от первого до последнего, поддерживая текущий индекс или итератор. Этот паттерн универсален: он работает для любой коллекции, не требует дополнительной памяти для структуры данных (только для результатов), позволяет легко внедрить логику раннего выхода.
// Линейный поиск с ранним выходом
User foundUser = null;
for (User user : users) {
if (user.getId().equals(targetId)) {
foundUser = user; // Нашли — запомнили состояние
break; // Прервали ленту — головка остановлена
}
}


Ранний выход (break, return) — мощный инструмент оптимизации императивного кода. Он позволяет избежать лишних вычислений, как только достигнуто желаемое состояние. В функциональном программировании аналогичное поведение достигается иначе — через ленивые вычисления и короткое замыкание операций, но в императивном коде это явная инструкция процессору прекратить исполнение текущего блока.

Условное накопление комбинирует проход с фильтрацией. На каждой итерации мы проверяем предикат — логическое условие, определяющее, должна ли текущая ячейка ленты участвовать в формировании результата.

Это порождает разветвлённую логику внутри цикла:
List<Transaction> validTransactions = new ArrayList<>();
BigDecimal totalRisk = BigDecimal.ZERO;

for (Transaction tx : transactions) {
// Вложенные условия — сложное состояние машины
if (tx.getAmount().compareTo(THRESHOLD) > 0) {
if (tx.getTimestamp().isAfter(cutoffDate)) {
if (!tx.isFlagged()) {
validTransactions.add(tx);
totalRisk = totalRisk.add(tx.calculateRisk());
}
}
}
}


#Java #для_новичков #beginner #stream_api #imperative
👍4
Здесь мы наблюдаем эскалацию сложности состояния: несколько переменных (validTransactions, totalRisk) изменяются синхронно, и их консистентность поддерживается программистом вручную. Если забыть обновить totalRisk при добавлении транзакции — получим рассогласование состояний, баг, который сложно отследить, потому что он проявляется не сразу, а в конечном результате вычислений.

Вложенные циклы — квинтэссенция императивной вычислительной мощи и одновременно её проклятие. Когда данные имеют иерархическую структуру (категории, содержащие товары, содержащие отзывы), мы запускаем машину Тьюринга внутри машины Тьюринга. Внешняя лента — категории.

Для каждой позиции внешней ленты мы разворачиваем внутреннюю ленту — товары этой категории.
// O(n × m × k) — полиномиальная сложность
List<Review> allRecentReviews = new ArrayList<>();

for (Category category : categories) {
for (Product product : category.getProducts()) {
for (Review review : product.getReviews()) {
if (review.getDate().isAfter(lastMonth)) {
allRecentReviews.add(review);
}
}
}
}


Каждый уровень вложенности добавляет измерение в пространство состояний. Теперь у нас три индекса (позиции трёх головок на трёх лентах), три контекста выполнения, и логика раннего выхода становится запутанной: break прерывает только внутренний цикл, для выхода из всех требуется флаг или исключение, что ещё больше усложняет состояние.


Сила императивного подхода

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

Оптимизация аллокаций — яркий пример. В примере с заказами выше мы создали два промежуточных списка: completedOrders и result.

Опытный разработчик может соптимизировать это до одного прохода с ограниченной очередью (Bounded Priority Queue), избегая хранения всех выполненных заказов в памяти:
public List<Order> getTopThreeOptimized(List<Order> orders) {
// Очередь с обратным порядком (минимальная сумма на вершине)
PriorityQueue<Order> minHeap = new PriorityQueue<>(
Comparator.comparing(Order::getAmount)
);

for (Order order : orders) {
if (order.getStatus() == Status.COMPLETED) {
if (minHeap.size() < 3) {
minHeap.offer(order);
} else if (order.getAmount().compareTo(minHeap.peek().getAmount()) > 0) {
minHeap.poll(); // Удаляем минимальный из топ-3
minHeap.offer(order);
}
}
}

// Преобразование в список и сортировка по убыванию
List<Order> result = new ArrayList<>(minHeap);
result.sort(Comparator.comparing(Order::getAmount).reversed());
return result;
}


Этот код сложнее для понимания, но он использует O(1) дополнительной памяти (константный размер кучи) вместо O(n) для хранения всех выполненных заказов. В императивном стиле такая оптимизация достижима, потому что мы управляем состоянием явно.

Прерывание выполнения в любой точке — ещё одно преимущество.

Предположим, мы ищем первый элемент, удовлетворяющий сложному условию, и должны выполнить постобработку при нахождении:
for (Item item : items) {
if (complexValidation(item)) {
processFoundItem(item); // Постобработка
return result; // Мгновенный выход из метода
}
}


Никаких дополнительных проверок, никакого Optional, никакого пропагирования состояния "найдено/не найдено" наружу — просто остановка машины в нужный момент.


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