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
Альтернативные стратегии equals/hashCode

Стратегия 1: Только неизменяемый идентификатор (ISBN)

Если Book имеет уникальный ISBN:
private final String isbn;  // уникальный, неизменяемый

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
return isbn.equals(((Book) o).isbn);
}

@Override
public int hashCode() {
return isbn.hashCode();
}


Плюсы: Эффективно, надёжно. Минусы: Требует уникального поля.

Стратегия 2: Все поля

Плюсы: Корректно для value objects. Минусы: Медленнее, чувствительно к изменениям.

Стратегия 3: Не переопределять (только identity)

Если Book — entity с уникальным ID в базе данных, и вы никогда не сравниваете разные объекты с одинаковым ID:
// Нет equals/hashCode — используем Object

Плюсы: Просто. Минусы: distinct(), toMap, groupingBy работают по ссылке, не по содержимому.

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

Задача 1: воспроизвести все три проблемы

В проекте «Библиотека» создайте класс BrokenBook с equals только по title и без hashCode.

Продемонстрируйте:
distinct() «проглатывает» книги или нет (зависит от хэш-коллизий)
toMap создаёт больше записей, чем ожидается
groupingBy разбивает на неожиданное количество групп
Зафиксируйте наблюдения в комментариях.

Задача 2: исправить и сравнить

Создайте FixedBook с корректной парой equals/hashCode. Повторите те же операции.

Убедитесь, что:
Толстой, Достоевский, Тургенев — три разные книги с одинаковым названием
distinct() оставляет все три
toMap создаёт три записи
Поиск по ключу находит правильное значение

Задача 3: специфический equals для бизнес-логики

Добавьте в Library метод findByTitle(String title), который находит все книги с данным названием (независимо от автора).
Реализуйте через filter с кастомным предикатом, не через equals:
public List<Book> findByTitle(String title) {
return books.stream()
.filter(b -> b.getTitle().equals(title))
.collect(Collectors.toList());
}


Объясните, почему это безопаснее, чем менять equals для всего класса.

Задача 4: кеширование hashCode (звёздочка)


Если поля title и author неизменяемы, закешируйте hashCode:
private int hashCode;  // 0 = не вычислено

@Override
public int hashCode() {
int result = hashCode;
if (result == 0) {
result = Objects.hash(title, author);
hashCode = result;
}
return result;
}


Сравните производительность при частом использовании в HashMap для миллиона операций.

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

public class Task160326 {
public static void main(String[] args) {
String s1 = " hello ".trim();
String s2 = " hello ".strip();
String s3 = "\u2000hello\u2000".trim();
String s4 = "\u2000hello\u2000".strip();

System.out.println(s1.equals(s2));
System.out.println(s3.length());
System.out.println(s4.length());
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
27%
true 5 5
13%
false 7 5
60%
true 7 5
0%
false 5 5
👍2
Что такое Profiling и для чего он нужен? 🤓

Ответ:

Profiling
— это процесс динамического анализа работающего приложения для измерения его производительности, использования памяти и поведения потоков.

Профилировщики (например, JProfiler, VisualVM, YourKit) собирают данные о:
CPU Profiling — какие методы потребляют больше всего процессорного времени;
Memory Profiling — какие объекты занимают память, поиск утечек памяти;
Thread Profiling — состояние потоков, поиск deadlock'ов.

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


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

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

Влади́мир Па́влович Барми́н (4 (17) марта 1909 года, Москва — 17 июля 1993 года, там же) советский учёный, конструктор реактивных пусковых установок, ракетно-космических и боевых стартовых комплексов. Один из основоположников советской космонавтики. С 1947 года под руководством Бармина были разработаны стартовые комплексы для многих ракет конструкции Королёва: Р-1, Р-2, Р-11, Р-5, Р-5М — первой стратегической ракеты с ядерным боезарядом Р-5М. В 1957 году завершены работы над стартовым комплексом первой в мире межконтинентальной баллистической ракеты Р-7, которая вывела на орбиту Земли первый искусственный спутник Земли и первого космонавта Юрия Гагарина.

Готтлиб Вильгельм Даймлер (нем. Gottlieb Wilhelm Daimler); собственно Доймлер (Däumler; 17 марта 1834, Шорндорф, Королевство Вюртемберг, Германский союз — 6 марта 1900, Бад-Канштатт, Королевство Вюртемберг, Германская империя) — немецкий инженер, конструктор и промышленник. Совместно с Вильгельмом Майбахом Даймлер разработал один из первых автомобилей и несколько типов бензиновых двигателей внутреннего сгорания.


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

1966 — первый пуск с космодрома Плесецк: ракетой-носителем «Восток-2» на орбиту Земли выведен искусственный спутник «Космос-112».


#Biography #Birth_Date #Events #17марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #016]

Тема: Ленивая загрузка в Hibernate (FetchType.LAZY) не работает после закрытия сессии.

Проблема: Hibernate использует шаблон ленивой загрузки для оптимизации производительности — связанные сущности подгружаются только при первом обращении к ним.

Однако для этого необходима открытая сессия (persistence context). Когда сессия закрывается (например, после завершения транзакции), доступ к неинициализированным прокси-объектам вызывает исключение LazyInitializationException.

Это классическая проблема при передаче сущностей на уровень представления (view) или при работе с ними вне транзакционного контекста.

Решение: Существует три основных подхода.

Первый — продлить транзакцию на весь сервисный метод, чтобы сессия оставалась открытой до полной обработки данных.
Второй — инициализировать все необходимые связи явно через JOIN FETCH в HQL/JPQL или через EntityGraph.
Третий — использовать паттерн Open Session in View (OSIV), который держит сессию открытой до завершения HTTP-запроса, но это антипаттерн для production из-за проблем с производительностью и риска утечек соединений.

@Entity
public class Order {
@Id private Long id;

@ManyToOne(fetch = FetchType.LAZY)
private User user; // ленивая загрузка

@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<Item> items;
// геттеры/сеттеры
}

@Service
public class OrderService {

@PersistenceContext
private EntityManager em;

//Антипаттерн: сессия закрыта при доступе к ленивым полям
public Order badFind(Long id) {
Order order = em.find(Order.class, id);
// Транзакция закончилась, сессия закрыта
order.getUser().getName(); // LazyInitializationException!
return order;
}

//Решение 1: @Transactional на уровне сервиса
@Transactional
public Order goodFindTransactional(Long id) {
Order order = em.find(Order.class, id);
order.getUser().getName(); // Ок, сессия открыта
return order;
}

//Решение 2: JOIN FETCH в запросе
@Transactional(readOnly = true)
public Order findWithJoinFetch(Long id) {
return em.createQuery(
"SELECT o FROM Order o JOIN FETCH o.user WHERE o.id = :id", Order.class)
.setParameter("id", id)
.getSingleResult(); // user уже загружен
}

//Решение 3: EntityGraph
@Transactional(readOnly = true)
public Order findWithEntityGraph(Long id) {
EntityGraph<?> graph = em.getEntityGraph("Order.withUserAndItems");
return em.find(Order.class, id,
Collections.singletonMap("javax.persistence.fetchgraph", graph));
}
}

// DTO-подход — лучшая практика
@Data
class OrderDTO {
private Long id;
private String userName; // только нужные поля, без прокси
}

@Service
class OrderDTOService {
@PersistenceContext
private EntityManager em;

public OrderDTO findDTO(Long id) {
return em.createQuery(
"SELECT NEW com.example.OrderDTO(o.id, u.name) " +
"FROM Order o JOIN o.user u WHERE o.id = :id", OrderDTO.class)
.setParameter("id", id)
.getSingleResult(); // Никаких прокси, только данные
}
}


Объяснение: Корень проблемы — отделение уровня персистентности от уровня представления.

Самое надежное решение — не передавать сущности наружу, а преобразовывать их в DTO (Data Transfer Objects) внутри транзакции.

Если все же нужны сущности, то либо продлевайте транзакцию (@Transactional на весь метод контроллера — плохо), либо инициализируйте все необходимое до закрытия сессии. OSIV (spring.jpa.open-in-view=true) включен по умолчанию в Spring Boot — это удобно для разработки, но в production может держать соединения с БД долго и маскировать проблемы с производительностью.


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

import java.util.*;

public class Task170326 {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add(null);
list1.add("text");
list1.remove(1);

List<String> list2 = new ArrayList<>();
list2.add("text");
list2.remove(0);

System.out.println(list1.isEmpty());
System.out.println(list2.isEmpty());
System.out.println(list1.size() == 0);
System.out.println(list2.size() == 0);
}
}


#Tasks
👍2
Что такое Maven и Gradle? 🤓

Ответ:

Maven
и Gradle — это инструменты для автоматизации сборки и управления зависимостями.

Maven использует XML (файл pom.xml) для конфигурации и строго следует концепции жизненного цикла. Он декларативен — вы описываете, что хотите, а не как.

Gradle использует Groovy или Kotlin DSL, что делает скрипты более компактными и гибкими. Gradle основан на графе задач (task-based) и поддерживает инкрементальную сборку, что часто делает его быстрее Maven.

Gradle предлагает более плавный и мощный способ настройки сборки.


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

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

Павел Игнатьевич Гроховский (18 марта 1899, Вязьма, Смоленская губерния — 29 мая 1943, Коммунарка)один из идеологов и родоначальников воздушно-десантных сил СССР, советский конструктор, изобретатель и организатор производства парашютной, авиационной и воздушно-десантной техники. Создал первые в мире хлопчатобумажные парашюты, парашютные системы и автоматические устройства к ним, грузовые контейнеры для воздушно-десантных войск.


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

1965 — советский космонавт Алексей Леонов совершил первый в истории человечества выход в открытый космос.

1992 — компания Майкрософт представляет операционную систему Windows 3.1.


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

Глава 6: Parallel Stream

Механика ForkJoinPool.commonPool()

Вызов .parallelStream() или .parallel() на потоке создаёт иллюзию простоты: система сама разделит работу между ядрами процессора, ускорит выполнение, вернёт результат. Реальность сложнее. Параллельные потоки в Java построены на ForkJoinPool — специализированном механизме для задач с шаблоном "разделяй и властвуй", и их эффективность зависит от понимания внутренней механики.


ForkJoinPool: архитектура общего пула

ForkJoinPool.commonPool() — статический пул, создаваемый при загрузке класса ForkJoinTask. Его размер по умолчанию равен количеству доступных процессоров минус один (оставляя один поток для основной работы), но не менее одного. Максимальный размер ограничен 32767 потоками, но на практике редко превышает десятки.

Этот пул — разделяемый ресурс. Он используется не только для parallelStream, но и для CompletableFuture.async, Arrays.parallelSort, RecursiveTask и других компонентов стандартной библиотеки. Исчерпание пула блокирующими задачами парализует всё приложение.


Разделение данных: Spliterator.trySplit()

Ключ к параллелизму — способность разделить источник данных на независимые части. Эту функцию выполняет Spliterator.trySplit():
public interface Spliterator<T> {
Spliterator<T> trySplit(); // Возвращает новый Spliterator для части данных или null
void forEachRemaining(Consumer<? super T> action);
boolean tryAdvance(Consumer<? super T> action);
long estimateSize();
int characteristics();
}


Метод trySplit() пытается разделить оставшиеся элементы пополам. Если успешно — возвращает новый Spliterator для первой половины, текущий продолжает обрабатывать вторую. Если данных мало или разделение невозможно — возвращает null, сигнализируя, что эту часть нужно обрабатывать последовательно.

Качество разделения определяет эффективность параллелизма:
Идеальные источники (ArrayList, массивы, IntStream.range): знают свой размер, поддерживают произвольный доступ. ArrayListSpliterator вычисляет середину как (lo + hi) >>> 1 и создаёт новый сплитератор для поддиапазона за O(1).
Приемлемые источники (HashSet, TreeSet): HashSet разделяется по бакетам хеш-таблицы. Разделение неравномерное (некоторые бакеты пусты), но работает. TreeSet использует структуру дерева для разделения.
Проблемные источники (LinkedList, Stream.iterate, Stream.generate, BufferedReader.lines()): не поддерживают произвольный доступ. LinkedList должен проходить узлы от начала до середины для разделения — O(n) на каждый split.

Stream.iterate и generate вообще не делятся, возвращая null из trySplit(). Параллельная обработка таких источников сводится к последовательной с накладными расходами на координацию.


Работа воркеров и кража задач

ForkJoinPool использует модель "work-stealing" (кража работы). Каждый поток-пул имеет локальную двустороннюю очередь (deque) задач. Новые задачи добавляются в голову очереди владельцем, выполняются с головы (LIFO — последняя добавленная первой). Когда поток опустошает свою очередь, он "ворует" задачи с хвоста очереди другого потока (FIFO — старые задачи), уменьшая contention.

Алгоритм для parallelStream:
Инициация: терминальная операция оборачивает конвейер в ForkJoinTask и отправляет в пул.
Разделение: корневой Spliterator делится рекурсивно, пока части достаточно малы или достигнут лимит параллелизма.
Выполнение: воркеры забирают подзадачи, обрабатывают свои сегменты данных через Spliterator.forEachRemaining.
Слияние: результаты подзадач комбинируются через Collector.combiner или аналогичный механизм.
Завершение: финальный результат возвращается вызывающему потоку.

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


#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍4
Опасность блокирующих задач

Самый разрушительный антипаттерн — блокирующие операции внутри parallelStream.

Рассмотрим сценарий:

List<Result> results = urls.parallelStream()
.map(url -> {
try {
return httpClient.fetch(url); // Блокирующий HTTP-запрос, 500мс
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.collect(toList());


При 100 URL и пуле размером 8 все воркеры быстро блокируются в ожидании сети. Оставшиеся 92 URL стоят в очереди. Но хуже: другие компоненты приложения, использующие commonPool (CompletableFuture, другие parallelStream), не получают потоков. Система "замораживается" — не от зависания, а от исчерпания ресурса.

Ещё хуже с Thread.sleep:
// Имитация тяжёлой работы
IntStream.range(0, 1000).parallel()
.map(i -> {
try { Thread.sleep(100); } catch (InterruptedException e) { }
return i * 2;
})
.collect(toList());


Все воркеры спят. Никакая другая задача в пуле не выполняется. Это эквивалентно deadlock для всего, что зависит от commonPool.

Диагностика в production:
Мониторинг ForkJoinPool.commonPool() через JMX: getActiveThreadCount(), getQueuedTaskCount(), getStealCount().
Thread dumps: поиск потоков с именем вида ForkJoinPool.commonPool-worker-N, ожидающих в Object.wait(), Thread.sleep(), или блокирующих I/O.
Профилирование: высокое время ожидания в ForkJoinTask.join() при отсутствии CPU-bound работы указывает на блокировки.


Альтернативы для блокирующих операций

Для I/O-bound задач parallelStream неприменим.

Используйте:
CompletableFuture с кастомным пулом:
ExecutorService ioPool = Executors.newFixedThreadPool(50);  // Много потоков, не боится блокировок

List<CompletableFuture<Result>> futures = urls.stream()
.map(url -> CompletableFuture.supplyAsync(() -> fetch(url), ioPool))
.collect(toList());

List<Result> results = futures.stream()
.map(CompletableFuture::join)
.collect(toList());

ioPool.shutdown();
Virtual Threads (Java 21+):
java
Copy
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
List<StructuredTaskScope.Subtask<Result>> subtasks = urls.stream()
.map(url -> scope.fork(() -> fetch(url)))
.collect(toList());

scope.join().throwIfFailed();

return subtasks.stream()
.map(StructuredTaskScope.Subtask::get)
.collect(toList());
}


Виртуальные потоки не блокируют носитель потоков ОС при блокировке Java-потока, делая блокирующие операции дешёвыми.


Контроль над commonPool

Размер пула можно настроить через системное свойство:

// При запуске JVM
-Djava.util.concurrent.ForkJoinPool.common.parallelism=16

// Или программно, но только до первого использования пула
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "16");


Но увеличение размера не решает проблему блокирующих задач — оно лишь откладывает исчерпание. Правильное решение — изоляция: блокирующие задачи в отдельном пуле, CPU-bound задачи в commonPool.


#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍5
Что выведет код?

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class Task180326 {
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
int result = pool.invoke(new Task(10));
System.out.println(result);
}

static class Task extends RecursiveTask<Integer> {
int n;
Task(int n) { this.n = n; }

@Override
protected Integer compute() {
if (n <= 1) return n;

Task t1 = new Task(n - 1);
Task t2 = new Task(n - 2);

t1.fork();
return t2.compute() + t1.join();
}
}
}


#Tasks
👍3
Варианты ответа:
Anonymous Quiz
63%
55
13%
34
13%
89
13%
0
👍2
Что такое REST и какие принципы лежат в его основе? 🤓

Ответ:

REST (Representational State Transfer)
— это архитектурный стиль построения распределенных систем, чаще всего веб-сервисов.

Основные принципы:
Клиент-сервер (разделение ответственности).
Отсутствие состояния (Stateless) — сервер не хранит состояние клиента между запросами.
Кэширование ответов.
Единообразие интерфейса (Uniform Interface) — использование стандартных HTTP методов (GET, POST, PUT, DELETE) для работы с ресурсами, идентифицируемыми по URL.
Слои (Layered System).

RESTful сервисы обычно возвращают данные в формате JSON или XML.


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

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

Фредери́к Жолио́-Кюри́ (фр. Jean Frédéric Joliot-Curie, до брака — Фредерик Жолио; 19 марта 1900, Париж — 14 августа 1958, там же) — французский физик и общественный деятель, лауреат Нобелевской премии по химии (совместно с Ирен Жолио-Кюри, 1935) и, одновременно, инициатор Стокгольмского воззвания, посвящённого безусловному запрету атомного оружия.


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

1964 — руководство фирмы IBM приняло решение о разработке и запуске в производство семейства ЭВМ System/360.

2008 — Запуск в МГУ им. Ломоносова самого мощного в России суперкомпьютера «СКИФ МГУ».


#Biography #Birth_Date #Events #19марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #017]

Тема: ConcurrentModificationException при итерации и модификации коллекции. Нельзя удалять элементы из ArrayList в цикле for-each. Используйте Iterator.remove() или removeIf().

Проблема: При итерации по коллекции с использованием for-each (синтаксического сахара над Iterator) нельзя напрямую добавлять или удалять элементы.

Коллекция ведет подсчет модификаций (modCount), а итератор проверяет его при каждом next(). Прямой вызов list.remove() изменяет modCount, но не уведомляет итератор — при следующем вызове next() выбрасывается ConcurrentModificationException.

Это защитный механизм, предотвращающий непредсказуемое состояние во время итерации.

Решение: Для безопасного удаления во время итерации используйте собственный Iterator и его метод remove(), который синхронизирует состояние итератора с коллекцией.

Начиная с Java 8, предпочтительнее использовать Collection.removeIf() с лямбда-выражением — это декларативный и более читаемый подход.
public class ConcurrentModificationExample {

public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

//Антипаттерн: удаление в for-each
try {
for (String item : list) {
if (item.equals("b")) {
list.remove(item); //ConcurrentModificationException!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Ошибка: " + e);
}

//Явный Iterator.remove()
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("b")) {
iterator.remove(); // Безопасно
}
}
System.out.println("После iterator.remove(): " + list);

//removeIf()
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
list.removeIf(item -> item.equals("b")); // Самый лаконичный способ
System.out.println("После removeIf(): " + list);

//Сбор элементов для удаления
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (item.equals("b")) {
toRemove.add(item); //Отмечаем на удаление
}
}
list.removeAll(toRemove); //Удаляем после итерации
System.out.println("После removeAll(): " + list);
}
}


Объяснение: Механизм fail-fast итераторов в Java основан на проверке поля modCount.

При создании итератора он запоминает текущее значение modCount. Каждый раз при вызове next() или remove() итератор проверяет, не изменилось ли modCount коллекции другими способами. Если изменилось — выбрасывается ConcurrentModificationException.

Метод iterator.remove() изменяет коллекцию и одновременно корректирует ожидаемый счетчик итератора. removeIf() использует тот же механизм внутри своей реализации.

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

import java.util.*;

public class Task190326 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));

for (String s : list) {
if (s.equals("C")) {
list.remove(s);
}
System.out.print(s + " ");
}
}
}


#Tasks
👍3
👍2
Какие бывают области видимости бинов (scopes) в Spring? 🤓

Ответ:

В Spring контейнер управляет бинами, и у каждого бина есть свой scope (область видимости).

Основные:
singleton (по умолчанию) — один экземпляр бина на весь IoC-контейнер.
prototype — новый экземпляр создается при каждом запросе бина.

Для веб-приложений:
request — один экземпляр на один HTTP-запрос.
session — один экземпляр на одну HTTP-сессию пользователя.
application — один экземпляр на весь ServletContext.
websocket — один экземпляр на всю WebSocket-сессию.

Выбор scope зависит от потребностей приложения и потокобезопасности бина.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍5