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 опасность подстерегает в изменяемых ключах. Рассмотрим сценарий с составным ключом-автором:
public class Author {
private String name; // Не final! Сеттер есть!
private String country;

// equals и hashCode по всем полям
@Override public int hashCode() {
return Objects.hash(name, country);
}
}

// Группировка книг по автору
Map<Author, List<Book>> byAuthor = library.stream()
.collect(groupingBy(Book::author));

// Позже в коде...
Author key = byAuthor.keySet().iterator().next();
key.setName("Modified Name"); // Мутация ключа!

// Попытка доступа
List<Book> books = byAuthor.get(key); // null?!


HashMap хранит ключ в бакете, соответствующем хеш-коду на момент вставки. При изменении полей name или country hashCode объекта меняется, но позиция в таблице — нет. get(key) вычисляет новый хеш, ищет в другом бакете, не находит совпадения, возвращает null. Данные не потеряны физически (объект Author и список Book существуют в памяти), но недоступны через Map.

Это "утечка" памяти логического характера: структура данных разрастается, но поиск перестаёт работать. Баг проявляется далеко от места мутации, диагностика требует глубокого понимания работы хеш-таблиц.


Правильная реализация контрактов

Для Book корректные equals и hashCode должны учитывать все значимые поля:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return year == book.year &&
Objects.equals(title, book.title) &&
Objects.equals(author, book.author);
}

@Override
public int hashCode() {
return Objects.hash(title, author, year);
}


Ключевые требования к контракту:
Рефлексивность: x.equals(x) всегда true
Симметричность: x.equals(y) тогда и только тогда, когда y.equals(x)
Транзитивность: если x.equals(y) и y.equals(z), то x.equals(z)
Консистентность: многократные вызовы дают один результат (требует иммутабельности)
Согласованность с hashCode: равные объекты имеют равные хеш-коды
Последнее требование критично для HashMap/HashSet. Нарушение (equals true, но hashCode разный) делает коллекции неработоспособными.


Рекорды как решение

Java Records (14+, стабильно в 16+) автоматически генерируют корректные equals, hashCode, toString по всем компонентам, гарантируя иммутабельность:
public record Book(String title, String author, int year) {}

// Автоматически: final поля, конструктор, геттеры, equals, hashCode, toString


Использование records для ключей groupingBy, элементов distinct, элементов toSet устраняет класс ошибок, связанных с контрактами. Единственное ограничение: компоненты records также должны быть иммутабельными и с корректными контрактами. Если Author в Book — изменяемый класс, проблема сохраняется.


Диагностика в production


При подозрении на нарушение контрактов:
Логирование хеш-кодов: вывести System.identityHashCode(obj) и obj.hashCode() для "одинаковых" объектов. Различие указывает на проблему.
Проверка equals: убедиться, что obj1.equals(obj2) возвращает true для логически эквивалентных объектов.
Анализ мутаций: установить точки останова на сеттеры ключей, используемых в Map.
Использование LinkedHashMap/LinkedHashSet: сохраняют порядок вставки, облегчая отладку.

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

import java.util.*;
import java.util.stream.Collectors;

public class Task100326 {
public static void main(String[] args) {
List<Item100326> items = Arrays.asList(
new Item100326(1, "A"),
new Item100326(1, "B"),
new Item100326(2, "C"),
new Item100326(2, "D")
);

Set<Item100326> set = new LinkedHashSet<>(items);

Map<Integer, List<Item100326>> grouped = items.stream()
.collect(Collectors.groupingBy(Item100326::getId));

System.out.print(set.size() + " ");
System.out.print(grouped.get(1).size() + " ");
System.out.print(grouped.get(2).get(0).getName());
}

static class Item100326 {
private int id;
private String name;

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

public int getId() { return id; }
public String getName() { return name; }

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

@Override
public int hashCode() {
return Objects.hash(id);
}
}
}


#Tasks
👍3
Варианты ответа:
Anonymous Quiz
11%
2 2 A
33%
4 2 C
44%
2 2 C
11%
4 2 A
👍3
Что такое аннотации (Annotations) и как создать свою? 🤓

Ответ:

Аннотации
— это форма метаданных, добавляемая в код (классы, методы, поля) для передачи информации компилятору, инструментам сборки или фреймворкам во время выполнения.

Они начинаются с @.

Стандартные аннотации:
@Override, @Deprecated, @SuppressWarnings.

Чтобы создать свою аннотацию, используют ключевое слово
@interface. Можно задать мета-аннотации: @Retention (когда доступна: SOURCE, CLASS, RUNTIME), @Target (к чему применять), @Inherited и др.

Аннотации могут содержать элементы (методы без параметров и тела).


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

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

Джозеф Карл Робнетт Ликлайдер (англ. Joseph Carl Robnett Licklider; 11 марта 1915, Сент-Луис, штат Миссури, США — 26 июня 1990, Арлингтон, штат Массачусетс, США) известный в научной и ИТ-среде как J.C.R. или «Лик» («Lick») — американский учёный. Ранние работы были посвящены психоакустике, последующие работы — сфере информационных технологий. Один из создателей сети ARPANET, прототипа Интернета.
В 1962—1964 годы работал в ARPA, заложил основы ARPANET. Высказал идею необходимости создания объединения компьютеров в сеть со свободным доступом любого человека из любого места мира к её ресурсам. Ликлайдера называют духовным отцом всемирной сети, человеком, посеявшим семена Интернета.
Вклад Ликлайдера в возникновение Интернета огромен, он состоит из идей и принципов, а не из изобретений и технологий. Ликлайдер предвидел необходимость объединения в сеть компьютеров, имеющих простые пользовательские интерфейсы. Его идеи предвосхитили компьютерную графику, интерфейсы, работающие по принципу указания и выбора (point-and-click), цифровые библиотеки, электронную коммерцию (e-commerce), дистанционное банковское обслуживание (online banking), а также программное обеспечение, размещаемое в сети. В США его считают «Джонни Эпплсидом программирования».


Вэни́вар[a] Буш (англ. Vannevar Bush /væˈniːvɑr/, 11 марта 1890, Эверетт, Массачусетс — 28 июня 1974, Белмонт, Массачусетс)американский ученый, инженер, разработчик аналоговых компьютеров, методолог и организатор научных исследований и научного сообщества. Советник по науке при президенте Рузвельте. Автор статьи «Как мы можем мыслить», в которой предложил прообраз гипертекстового устройства Memex (интересное дальновидение - мое прим.).

Урбе́н Жан Жозе́ф Леверье́ (фр. Urbain Jean Joseph Le Verrier; 11 марта 1811, Сен-Ло — 23 сентября 1877, Париж)французский математик, занимавшийся небесной механикой, бо́льшую часть своей жизни проработавший в Парижской обсерватории. Его наиболее известным достижением является предсказание существования планеты Нептун, сделанное с помощью математического анализа астрономических наблюдений. По предложению Франсуа Араго он выполнил вычисления для объяснения несоответствий между наблюдаемой орбитой Урана и той, которая должна быть согласно законам Кеплера и Ньютона.


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

1878 — Французской Академии продемонстрирован фонограф, но изобретение было объявлено шарлатанством.


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

Тема: Инъекция через конструктор предпочтительнее инъекции в поле. Она делает зависимости явными, поля immutable и упрощает тестирование. В новых версиях Spring @Autowired можно не ставить, если конструктор один.

Проблема: Инъекция зависимостей через поле (@Autowired над полем) — самый быстрый способ написать код, но он создает скрытые проблемы.

Поля не могут быть final, что нарушает иммутабельность и позволяет создавать объекты в частично инициализированном состоянии.
При тестировании невозможно подставить mock через конструктор — приходится использовать рефлексию (PowerMock, ReflectionTestUtils) или поднимать контекст Spring.
Также это скрывает обязательные зависимости: глядя на класс, непонятно, без каких компонентов он не может работать.

Решение: Используйте конструктор для инъекции зависимостей.

Все поля помечайте как private final — они инициализируются один раз при создании бина и гарантированно не равны null после этого.

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import java.util.Objects;

//Антипаттерн: field injection
@Service
public class BadService {

@Autowired
private UserRepository userRepository; // не final, можно забыть проинициализировать

@Autowired
private EmailService emailService; // скрытая зависимость

public void registerUser(User user) {
// может быть NullPointerException, если забыли протестировать
userRepository.save(user);
emailService.sendWelcome(user);
}

// невозможно создать объект в тесте без Spring
}

//Правильно: constructor injection
@Service
public class GoodService {

private final UserRepository userRepository;
private final EmailService emailService;
private final AuditService auditService;

// С одним конструктором @Autowired не нужен (Spring 4.3+)
public GoodService(UserRepository userRepository,
EmailService emailService,
@Lazy AuditService auditService) {
this.userRepository = Objects.requireNonNull(userRepository,
"UserRepository must not be null");
this.emailService = Objects.requireNonNull(emailService);
this.auditService = auditService; // @Lazy — прокси будет создан при первом вызове
}

public void registerUser(User user) {
userRepository.save(user);
emailService.sendWelcome(user);
auditService.log("User registered: " + user.getId());
}
}

// Для циклических зависимостей — @Lazy
@Service
class A {
private final B b;

public A(@Lazy B b) { // B создастся лениво
this.b = b;
}
}

@Service
class B {
private final A a;

public B(A a) {
this.a = a;
}
}



Объяснение: Constructor injection гарантирует, что все необходимые зависимости будут предоставлены в момент создания объекта.

Пометка полей как final защищает от случайного изменения и делает класс потокобезопасным (если сами зависимости потокобезопасны).
Проверка Objects.requireNonNull() в конструкторе обеспечивает раннее обнаружение ошибок конфигурации.

При использовании одного конструктора Spring автоматически использует его для инъекции — аннотация @Autowired становится опциональной, что соответствует принципу "convention over configuration".

Для циклических зависимостей используется @Lazy.

Разработчик всегда предпочитает constructor injection, оставляя field injection только для очень специфичных случаев (например, некоторые тестовые фреймворки).


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

import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class Task110326 {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Task110326.class)) {
UserService110326 service = context.getBean(UserService110326.class);
System.out.println(service.getUser());
}
}
}

@Component
class UserService110326 {
private UserRepository110326 userRepository;

@Autowired
public UserService110326(UserRepository110326 userRepository) {
this.userRepository = userRepository;
}

public String getUser() {
return userRepository.findUser();
}
}

@Component
class UserRepository110326 {
private DatabaseService110326 databaseService;

@Autowired
public UserRepository110326(DatabaseService110326 databaseService) {
this.databaseService = databaseService;
}

public String findUser() {
return "User from " + databaseService.getDataSource();
}
}

@Component
class DatabaseService110326 {
public String getDataSource() {
return "MainDB";
}
}


#Tasks
👍2
Что такое SOLID принципы? 🤓

Ответ:

SOLID
— это пять основных принципов объектно-ориентированного проектирования.

S — Single Responsibility Principle (Принцип единственной ответственности): у класса должна быть только одна причина для изменения.

O — Open/Closed Principle (Открытости/закрытости): классы открыты для расширения, но закрыты для изменения.

L — Liskov Substitution Principle (Подстановки Лисков): наследники не должны нарушать поведение базового класса.

I — Interface Segregation Principle (Разделения интерфейса): много специализированных интерфейсов лучше, чем один общий.

D — Dependency Inversion Principle (Инверсии зависимостей): зависимости должны строиться на абстракциях, а не на конкретных классах.


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

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

Пётр Алекса́ндрович Фре́зе (29 февраля [12 марта] 1844, Санкт-Петербург, Российская империя — 24 апреля 1918, Граново, Вышневолоцкий уезд, Тверская губерния, РСФСР)российский изобретатель, один из конструкторов первого российского автомобиля.


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

1889 — Элмон Строуджер из США запатентовал автоматическую телефонную станцию.

1974 — станция «Марс-6» села на Марсе, впервые передав на Землю данные об атмосфере и почве этой планеты.


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

Глава 5: Контракты. Почему equals, hashCode и иммутабельность — закон

Иммутабельность данных в потоке — не прихоть, а необходимость

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

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


Контракт неизменности источника

Исходная коллекция, породившая поток, не должна модифицироваться во время обхода. Это правило кажется очевидным, но нарушается из-за недопонимания ленивости:
List<Book> library = new ArrayList<>(fetchBooks());
Stream<Book> stream = library.stream(); // Поток создан, но не активирован

// Позже, до терминальной операции:
library.add(new Book("New", "Author", 2024)); // Модификация источника!

// Теперь терминальная операция:
List<Book> result = stream.collect(toList()); // ConcurrentModificationException?


Поведение неопределённо. Если поток использует Iterator (коллекция не SIZED), модификация после создания потока, но до начала обхода выбросит ConcurrentModificationException при проверке modCount. Если поток использует Spliterator с характеристикой SIZED (например, ArrayList), и терминальная операция предвыделяет размер, добавление элемента приведёт к ArrayIndexOutOfBoundsException или пропуску элементов.

Ещё опаснее модификация внутри потоковой операции:
// Катастрофа: модификация источника изнутри потока
library.stream()
.filter(b -> b.year() < 1950)
.forEach(library::remove); // ConcurrentModificationException гарантирован


Удаление элементов из коллекции во время её обхода — классическая ошибка, приводящая к исключению или пропуску элементов из-за сдвига индексов.

Правильный подход — создание новой коллекции вместо модификации существующей:
// Правильно: фильтрация через collect, не removeIf в потоке
List<Book> modernBooks = library.stream()
.filter(b -> b.year() >= 1950)
.collect(toList());

// Или без потока, если модификация исходной нужна:
library.removeIf(b -> b.year() < 1950); // Метод коллекции, не Stream API



Иммутабельность промежуточных результатов

Не только источник, но и объекты в потоке должны быть неизменяемыми для корректности.

Рассмотрим изменяемый объект в map:
public class MutableBook {
private String title;
private int readCount; // Мутабельное поле!

public void incrementRead() { readCount++; }
}

// Антипаттерн: мутация внутри потока
List<MutableBook> books = fetchBooks();
books.stream()
.filter(b -> b.getTitle().startsWith("A"))
.forEach(MutableBook::incrementRead); //Модифицируем исходные объекты!

// Теперь исходный список books изменён — побочный эффект


forEach здесь используется не для агрегации, а для побочного эффекта — инкремента счётчика. Исходные объекты модифицированы, что делает код сложным для отладки и тестирования. Если позже тот же список обрабатывается другим потоком, результаты зависят от порядка выполнения.

Рефакторинг к иммутабельности:
public record Book(String title, int readCount) {
public Book withIncrementedRead() {
return new Book(title, readCount + 1); //Новый экземпляр
}
}

// Правильно: создание новых объектов
List<Book> updatedBooks = books.stream()
.filter(b -> b.title().startsWith("A"))
.map(Book::withIncrementedRead)
.collect(toList());

// Исходный список books неизменён


Здесь "обновление" — порождение новой версии объекта, а не модификация существующего. Старые данные сохраняются, новые создаются, история не разрушается.


#Java #для_новичков #beginner #stream_api
👍6
Контролируемые побочные эффекты

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

Критерии контролируемого побочного эффекта:

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

Пример: атомарный счётчик для отладки
AtomicInteger processedCount = new AtomicInteger(0);

List<Result> results = items.parallelStream()
.map(this::heavyProcessing)
.peek(r -> processedCount.incrementAndGet()) // Контролируемый эффект
.collect(toList());

System.out.println("Обработано: " + processedCount.get());


AtomicInteger обеспечивает потокобезопасность инкремента. Эффект изолирован — счётчик не влияет на обработку элементов. Он идемпотентен с точки зрения бизнес-логики (конечный результат results не зависит от счётчика). Но это всё ещё антипаттерн: peek предназначен для отладки, а не для бизнес-логики, и его поведение может измениться в будущих версиях JDK.

Более чистый подход — отделение агрегации от обработки:
// Лучше: явная агрегация результата
class ProcessingResult {
final List<Result> results;
final int count;
ProcessingResult(List<Result> results, int count) {
this.results = results;
this.count = count;
}
}

ProcessingResult finalResult = items.parallelStream()
.collect(() -> new ProcessingResult(new ArrayList<>(), 0),
(acc, item) -> {
acc.results.add(process(item));
acc.count++;
},
(left, right) -> {
left.results.addAll(right.results);
left.count += right.count;
});


Или использование teeing для подсчёта параллельно с сбором результатов.

Пример: логирование
orders.stream()
.filter(o -> o.amount().compareTo(THRESHOLD) > 0)
.peek(o -> auditLog.record("Крупный заказ", o.id())) // Побочный эффект
.map(this::processLargeOrder)
.collect(toList());


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

Надёжнее:
List<ProcessedOrder> processed = orders.stream()
.filter(o -> o.amount().compareTo(THRESHOLD) > 0)
.map(o -> {
auditLog.record("Крупный заказ", o.id()); // Явный эффект в map
return processLargeOrder(o);
})
.collect(toList());


Теперь логирование гарантировано для каждого прошедшего фильтр элемента, хотя map с побочным эффектом всё ещё нарушает чистоту функции.


Граница допустимого


Правило практики: побочные эффекты допустимы только в терминальных операциях, работающих с уже потокобезопасными внешними системами. forEach для отправки сообщений в Kafka, collect в ConcurrentHashMap, reduce с атомарными операциями — приемлемы. Побочные эффекты в промежуточных операциях (map, filter, flatMap) — почти всегда ошибка.
Исключение — кэширование вычислений внутри операции, но и здесь предпочтительны чистые функции с мемоизацией вне потока.


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

import java.util.*;
import java.util.stream.*;

public class Task120326 {
public static void main(String[] args) {
List<Person120326> people = new ArrayList<>(Arrays.asList(
new Person120326("Alice", 25),
new Person120326("Bob", 30),
new Person120326("Charlie", 20)
));

List<String> names = people.stream()
.map(p -> p.name)
.sorted((a, b) -> {
people.get(0).age = 999;
return a.compareTo(b);
})
.collect(Collectors.toList());

System.out.println(people);
System.out.println(names);
}

static class Person120326 {
String name;
int age;

Person120326(String name, int age) {
this.name = name;
this.age = age;
}

public String toString() {
return name + ":" + age;
}
}
}


#Tasks
👍2
Что такое Dependency Injection (DI) и Inversion of Control (IoC)? 🤓

Ответ:

Inversion of Control (IoC)
— это общий принцип, при котором фреймворк управляет потоком программы и объектами, а не наоборот.

Dependency Injection (DI) — это конкретная реализация IoC, при которой объекты получают свои зависимости извне (через конструктор, сеттер или интерфейс), а не создают их сами.

Это уменьшает связанность кода, повышает тестируемость (легко подставить моки) и гибкость.

Главный контейнер DI в Java-мире — это Spring IoC-контейнер.


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

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

Джон Хазбрук Ван Флек (англ. John Hasbrouck van Vleck; 13 марта 1899, Мидлтаун — 27 октября 1980, Кембридж, Массачусетс)американский физик, лауреат Нобелевской премии по физике 1977 года. Работы по квантовой теории атомной структуры, магнетизму, теории валентности, изучению атомных и молекулярных спектров, ферро- и ферримагнитному резонансу. Один из создателей современных представителей о магнетизме вещества. Разработал квантовомеханическую теорию диа- и парамагнетизма (1926—1928), получил парамагнитную добавку к диамагнитной восприимчивости несимметричных атомов и молекул, названную ванфлековским парамагнетизмом (1927). Развил теорию внутрикристаллического (лигандного) поля. Дал детальную трактовку антиферромагнетизма (1941), термодинамическую теорию молекулярного поля для антиферромагнетиков, развил гейзенберговскую модель локализованных спинов и предложил эффективный спиновый гамильтониан.


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

1781Английский астроном Вильям Гершель с помощью собственноручно изготовленного телескопа открыл седьмую планету Солнечной системы — Уран. Правда, первоначально он принял её за комету. Когда выяснилось, что это неизвестная ранее планета, он получил медаль Королевского общества и должность придворного астронома. Сам Гершель назвал планету в честь своего высокого покровителя короля Георга III «Звездой Георга», короткое время она носила имя первооткрывателя, пока немецкий астроном Иоганн Боде не придумал для неё название Уран.

1930 — американский астроном Клайд Томбо объявил в докладе об открытии им 18 февраля девятой планеты Солнечной системы. Доклад был приурочен к 75-летию основателя и первого директора крупнейшей частной обсерватории в США, Персиваля Лоуэлла, который многие годы потратил на поиски предсказанной им девятой планеты. На следующий день 14 марта Венеция Бёрни, одиннадцатилетняя школьница из Оксфорда, предложит имя для новой планеты, и в мае будет опубликовано официальное имя — Плутон.

1989 — была изобретена Всемирная паутина (World Wide Web, WWW), более известная как Интернет. Изобретателем Интернета считается английский учёный Тим Бернерс-Ли и его коллеги, работавшие в Европейском совете по ядерным исследованиям (CERN), передали начальнику своего отдела документ, озаглавленный «Информационный менеджмент: некоторые предложения», в котором были заложены основные принципы WWW.


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

Тема: @Async по умолчанию не работает внутри того же класса. Вызов асинхронного метода из другого метода этого же класса не пройдет через Spring Proxy.

Проблема: Аннотация @Async работает благодаря Spring AOP, который создает прокси вокруг целевого бина.

При вызове метода через прокси (например, из другого бина) запускается асинхронное выполнение. Однако при прямом вызове из того же класса через this прокси не участвует — вызывается оригинальный метод, и асинхронность теряется.

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

Решение: Никогда не вызывайте @Async методы напрямую внутри того же класса.

Используйте self-injection (внедрение ссылки на самого себя через @Autowired/@jakarta.inject.Inject) или вынесите асинхронную логику в отдельный @Service-компонент.

Self-injection заставляет обращаться к прокси, а не к реальному объекту.

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

@Autowired
private AsyncService selfProxy; // self-injection

@Async
public CompletableFuture<String> asyncMethod() {
System.out.println("Асинхронный вызов в потоке: " + Thread.currentThread().getName());
return CompletableFuture.completedFuture("Результат");
}

//Проблема: прямой вызов
public void badCall() {
System.out.println("Прямой вызов");
asyncMethod(); // this.asyncMethod() — асинхронности НЕТ!
}

//Решение: вызов через self-proxy
public void goodCall() {
System.out.println("Вызов через self-proxy");
selfProxy.asyncMethod(); // через прокси — асинхронно
}
}

// Альтернатива: отдельный компонент
@Service
class AsyncWorker {
@Async
public CompletableFuture<String> doWork() {
return CompletableFuture.completedFuture("Работа выполнена");
}
}

@Service
class CallerService {
@Autowired
private AsyncWorker worker;

public void call() {
worker.doWork(); // асинхронно через прокси
}
}


Объяснение: Spring создает прокси-объект, который перехватывает вызовы @Async методов и передает их в пул потоков.

Когда бин внедряет сам себя (selfProxy), он получает именно прокси, а не исходный объект. Вызов через selfProxy.asyncMethod() корректно обрабатывается перехватчиком. Self-injection работает благодаря тому, что Spring после создания бина внедряет его же прокси (если включен режим @EnableAsync и прокси-таргет класс). Для этого требуется либо использовать интерфейсы (JDK proxy), либо CGLIB (proxy-target-class=true).

Альтернативное и более чистое решение — вынести асинхронный метод в отдельный бин, что полностью исключает проблему внутренних вызовов.

Разработчик всегда проверяет, через какой объект вызывается @Async метод, и избегает вызовов через this.


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

@SpringBootApplication
@EnableAsync
public class Task130326 implements CommandLineRunner {

@Autowired
private AsyncService130326 service;

public static void main(String[] args) {
SpringApplication.run(Task130326.class, args);
}

@Override
public void run(String... args) {
service.directCall();
service.proxyCall();
}
}

@Component
class AsyncService130326 {
@Lazy
@Autowired
private AsyncService130326 self;

@Async
public void asyncMethod() {
System.out.println("asyncMethod: " + Thread.currentThread().getName());
}

public void directCall() {
System.out.println("directCall: " + Thread.currentThread().getName());
asyncMethod();
}

public void proxyCall() {
System.out.println("proxyCall: " + Thread.currentThread().getName());
self.asyncMethod();
}
}


#Tasks
👍2
Что такое Java Memory Model (JMM)? 🤓

Ответ:

Java Memory Model (JMM)
— это формальная модель, описывающая, как потоки взаимодействуют через память и какое поведение можно ожидать в многопоточной среде.

JMM определяет правила, когда изменения, сделанные одним потоком, становятся видимыми для других потоков. Она описывает отношение "happens-before", которое гарантирует видимость и упорядоченность действий. Без JMM компилятор и процессор могли бы переупорядочивать инструкции так, что многопоточный код работал бы непредсказуемо.

Ключевые слова synchronized, volatile, final обеспечивают определенные гарантии видимости в рамках JMM.


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