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
Механизм JVM: как исключения работают под капотом

Когда в Java-коде возникает исключительная ситуация (через оператор throw или аппаратное прерывание, например, деление на ноль), JVM выполняет сложную последовательность действий.

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

Затем JVM начинает раскрутку стека (stack unwinding): последовательно проверяет каждый фрейм стека вызовов, начиная с текущего метода, на наличие подходящего обработчика в блоках catch. Поиск ведется по типу исключения с учетом иерархии — подходящим считается блок, объявляющий тип, равный классу исключения или его суперклассу.

Если подходящий обработчик найден, управление передается в соответствующий блок catch, а стектрейс сохраняется в объекте исключения для последующего анализа. Если обработчик не найден до дна стека вызовов, поток завершается, и JVM вызывает глобальный обработчик неперехваченных исключений (UncaughtExceptionHandler), который обычно выводит стектрейс в stderr.

Важный нюанс: для unchecked-исключений компилятор не генерирует проверки на этапе компиляции, но JVM обрабатывает их идентично checked-исключениям во время выполнения. Разница только в статической проверке кода.


Практические паттерны работы с иерархией

Правильное наследование при создании кастомных исключений

При проектировании собственных исключений критически важно выбрать правильного родителя. Это решение определяет семантику ошибки и обязательства вызывающего кода:
// Проверяемое исключение для восстановимых бизнес-ошибок
public class PaymentProcessingException extends Exception {
private final String transactionId;
private final PaymentErrorCode errorCode;

public PaymentProcessingException(String message, String transactionId,
PaymentErrorCode errorCode, Throwable cause) {
super(message, cause);
this.transactionId = transactionId;
this.errorCode = errorCode;
}

// Геттеры для структурированной информации об ошибке
public String getTransactionId() { return transactionId; }
public PaymentErrorCode getErrorCode() { return errorCode; }
}

// Непроверяемое исключение для программных ошибок валидации
public class InvalidPaymentRequestException extends RuntimeException {
private final String fieldName;
private final Object rejectedValue;

public InvalidPaymentRequestException(String fieldName, Object rejectedValue, String message) {
super(message);
this.fieldName = fieldName;
this.rejectedValue = rejectedValue;
}
}


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

InvalidPaymentRequestException наследует RuntimeException, так как передача невалидных данных — это ошибка программиста, вызывающего API. Проверка аргументов должна происходить до вызова, и исключение служит последней линией защиты, не требуя загромождения сигнатур методов.


#Java #для_новичков #beginner #exception #Throwable
👍3
Антипаттерн: перехват Throwable

Перехват Throwable на верхнем уровне метода — серьезная ошибка, которая маскирует критические сбои:
// Антипаттерн — никогда так не делайте
public void processRequest(Request request) {
try {
executeBusinessLogic(request);
} catch (Throwable t) { // Опасно: перехватывает Error
logger.error("Request failed", t);
// Если это был OutOfMemoryError, приложение продолжит работу
// с нестабильной JVM, что приведет к непредсказуемым последствиям
}
}


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

Использование цепочки причин

При оборачивании низкоуровневых исключений в высокоуровневые бизнес-исключения всегда сохраняйте оригинальную причину:
public User loadUserById(String userId) throws UserRepositoryException {
try {
return database.executeQuery("SELECT * FROM users WHERE id = ?", userId);
} catch (SQLException e) {
// Сохраняем оригинальное исключение как cause
throw new UserRepositoryException(
"Failed to load user with ID: " + userId,
e // Цепочка причин сохраняется
);
}
}



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


Современный контекст: эволюция подходов

Современная экосистема Java претерпела значительные изменения в отношении исключений. Если в классической Java (до Java 8) checked-исключения считались нормой для любых внешних сбоев, то современные фреймворки (Spring, Hibernate, JPA) и языки, работающие на JVM (Kotlin, Scala), склоняются к использованию unchecked-исключений даже для восстановимых ситуаций.

Spring Framework, например, транслирует большинство checked-исключений (таких как SQLException или IOException из репозиториев) в иерархию unchecked DataAccessException. Это упрощает сигнатуры методов и интеграцию с лямбда-выражениями, которые не поддерживают checked-исключения в своих функциональных интерфейсах.

Kotlin полностью отказался от checked-исключений на уровне языка, считая, что механизм не оправдывает своих затрат на verboseness. Это влияет на дизайн API, совместимых с Kotlin — они также предпочитают unchecked-исключения.

Тем не менее, понимание иерархии Throwable остается фундаментальным. Даже в функциональном стиле с использованием Optional, Result-типов из библиотек (Vavr, Arrow) или sealed-классов (Java 17+), исключения не исчезают — они трансформируются в другие формы представления ошибок, но базовая модель JVM с Throwable в корне остается неизменной.

#Java #для_новичков #beginner #exception #Throwable
👍4
Что выведет код?

public class Task080426 {
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch (Throwable t) {
System.out.print("A");
throw new RuntimeException();
} finally {
System.out.print("B");
}
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
7%
A
20%
B
33%
AB
40%
ABRuntimeException
👍2
Что такое ThreadLocal? 🤓

Ответ:

ThreadLocal<T>
— это класс, который позволяет хранить переменные, уникальные для каждого потока.

Каждый поток имеет свою собственную, независимую копию переменной. Это полезно для хранения контекстной информации (например, ID пользователя в веб-приложении, соединение с БД) без необходимости передавать ее через все методы.

Важно помнить об очистке (remove()) в средах с пулом потоков (веб-серверы), чтобы избежать утечек памяти, когда поток переиспользуется.



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

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

Ива́н Миха́йлович Са́вченко (9 апреля 1919, село Водяное Каменско-Днепровского района Запорожской области Украины — 7 апреля 1984, Ленинград) — инженер-кораблестроитель, организатор кораблестроительного производства, в 60-е — 80-е годы XX века — один из крупнейших в СССР специалистов в области атомного подводного кораблестроения и технологии судостроения.

Джон Адам Преспер Эккерт-младший (англ. John Adam Presper Eckert, Jr., 9 апреля 1919, Филадельфия, США — 3 июня 1995, Брин-Мар, Пенсильвания, США) — американский учёный в области компьютерной инженерии, инженер-электронщик. Вместе с Джоном Мокли является создателем первого электронного компьютера ENIAC. Является одним из авторов Лекций школы Мура — первого в истории курса лекций на тему компьютеров. Основатель одной из первых коммерческих компьютерных компаний — Eckert–Mauchly Computer Corporation (EMCC) и создатель первого коммерческого компьютера UNIVAC I. Изобретатель памяти на линиях задержки, использовавшейся до конца 1960-х. Один из авторов архитектуры фон Неймана.

Чарлз Протеус Штейнмец (англ. Charles Proteus Steinmetz, нем. Carl August Rudolph Steinmetz; 9 апреля 1865, Бреслау — 26 октября 1923, Скенектади) — американский инженер-электрик германского происхождения.

Одна из историй о Штейнмеце была опубликована в 1965 году в журнале Life за авторством Джека Б. Скотта. На заводе River Rouge компании Ford в Дирборне (штат Мичиган) возникла проблема с гигантским электрогенератором, и инженеры компании обратились за помощью к Штейнмецу. Тот, прибыв на завод, попросил дать ему только блокнот, карандаш и кровать-раскладушку: согласно Скотту, в течение двух следующих суток Штейнмец прислушивался к шуму генератора и выполнял сложные вычисления, чтобы определить источник неполадок. После двух суток работы он попросил лестницу-стремянку, взобрался по ней на генератор и сделал отметку мелом на участке, где, по его мнению, скрывался источник проблем. После этого он попросил инженеров снять пластину, на которой была оставлена отметка, и заменить 16 витков провода: инженеры выполнили все указания, и генератор заработал в прежнем режиме. После этого Генри Форд получил от от General Electric счёт на 10 тысяч долларов за оказанную услугу — колоссальную по тем временам сумму. Удивлённый Форд попросил конкретизировать счёт, и Штейнмец прислал более точный счёт, в котором, согласно Скотту, говорилось следующее: «1 доллар — за поставленную мелом метку, 9999 долларов — за знание того, где её нужно было поставить». Форд немедленно оплатил счёт.



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

1989 — американец Дуглас Энгельбарт удостоен почётного приза Массачусетского технологического института (500 000 долларов) за изобретение компьютерной мыши (1968).


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

Тема: ClassLoader не удаляет классы даже после сборки мусора.

Проблема: В Java классы загружаются через ClassLoader и остаются в памяти до тех пор, пока сам ClassLoader доступен. Даже если экземпляры классов собраны GC, объекты Class и их метаданные (методы, поля) не могут быть выгружены, пока на ClassLoader есть живые ссылки.

При перезагрузке приложения (например, в Tomcat, Jetty, OSGi, или при горячей перезагрузке модулей) создается новый ClassLoader, а старый должен быть уничтожен. Однако множество компонентов (ThreadLocal, JDBC-драйверы, статические кеши, логгеры) сохраняют ссылки на старый загрузчик, не давая GC его собрать.

В результате каждый перезапуск приводит к накоплению метаданных классов в Metaspace/PermGen, что вызывает OutOfMemoryError: Metaspace или длительные паузы GC. Эта проблема известна как "classloader leak".

Решение: Используйте инструменты для обнаружения утечек (например, плагин Eclipse Memory Analyzer, YourKit). В коде избегайте хранения ссылок на объекты, загруженные динамическими загрузчиками, в статических полях или долгоживущих коллекциях.

Для библиотек, создающих потоки с ThreadLocal, обязательно вызывайте remove() после использования. Для JDBC-драйверов вызывайте DriverManager.deregisterDriver(). В веб-контейнерах используйте слушатели контекста для очистки. В сложных случаях используйте WeakReference или PhantomReference.

public class ClassLoaderLeakExample {

//Антипаттерн: статическое поле хранит класс, загруженный динамическим ClassLoader'ом
private static Class<?> CACHED_CLASS;

public static void dangerousStore(Class<?> clazz) {
CACHED_CLASS = clazz; // Удерживает ClassLoader навсегда
}

//Антипаттерн: ThreadLocal без очистки
private static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal<>();

public static void dangerousThreadLocal() {
THREAD_LOCAL.set(new Object()); // Поток из пула может пережить перезагрузку
}

//Решение: очистка ThreadLocal в finally или в слушателе
public static void safeThreadLocal() {
try {
THREAD_LOCAL.set(new Object());
// работа
} finally {
THREAD_LOCAL.remove(); // Обязательно
}
}

//Решение для веб-приложений: слушатель контекста
public static class CleanupListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Дерегистрация JDBC-драйверов
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() ==
Thread.currentThread().getContextClassLoader()) {
try {
DriverManager.deregisterDriver(driver);
} catch (Exception e) { /* log */ }
}
}

// Очистка статических кешей логирования (Log4j, Logback)
// org.slf4j.LoggerFactory.getILoggerFactory().reset();

// Остановка фоновых потоков
// Thread.interruptAll() и т.п.
}
}
}


Объяснение: Каждый класс хранит ссылку на свой ClassLoader. ClassLoader хранит ссылки на все загруженные классы. Если на ClassLoader остается хотя бы одна живая ссылка, все его классы остаются в Metaspace.

Типичные источники утечек:
статические поля, ThreadLocal (особенно в пулах потоков),
JDBC-драйверы (они регистрируются в DriverManager статически),
логгеры (LogManager хранит ссылки на контексты),
библиотеки Java EE (например, JAXB, EL-парсеры).

При перезагрузке веб-приложения контейнер создает новый ClassLoader, но старый продолжает висеть, и память растет.

Диагностика утечек ClassLoader обычно требует heap dump и анализа путей к корням GC.

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

import java.lang.ref.WeakReference;

public class Task090426 {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> ref = new WeakReference<>(obj);
obj = null;
System.gc();
System.out.println(ref.get());
}
}


#Tasks
👍2
🔥5
Что такое BlockingQueue и где он применяется? 🤓

Ответ:

BlockingQueue
из java.util.concurrent — это интерфейс для потокобезопасных очередей с блокирующими операциями.

При попытке взять элемент из пустой очереди поток блокируется до появления элемента.

При попытке добавить элемент в переполненную очередь (если есть ограничение) поток блокируется до освобождения места.

Классическая реализация — паттерн Producer-Consumer. Реализации: ArrayBlockingQueue (ограниченная), LinkedBlockingQueue (опционально ограниченная), PriorityBlockingQueue и др.



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

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

Прийт Касесалу (родился 10 апреля 1972 г.) — эстонский программист и разработчик программного обеспечения, наиболее известный своим участием в разработке Kazaa, Skype и, совсем недавно, Joost. В настоящее время он работает в Ambient Sound Investments и живет в Таллинне, Эстония .


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

1957 — в Дубне введён в действие синхрофазотрон Объединённого института ядерных исследований.

2019 — миру представлена первая фотография чёрной дыры.


#Biography #Birth_Date #Events #10апреля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 10. Исключения, логирование, отладка

Глава 1. Иерархия исключений (Exceptions)

Checked vs Unchecked исключения: когда что использовать.


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

Классическое правило, сформулированное в документации Oracle, гласит: если клиент может разумно ожидать восстановления от исключения, сделайте его проверяемым. Если клиент не может ничего сделать для восстановления, сделайте его непроверяемым. Это различие отражает два разных мира: внешние, непредсказуемые условия (отсутствие файла, недоступность сети) против внутренних нарушений контрактов (передача null вместо валидного аргумента, выход за границы массива).

Однако за четверть века существования Java эта модель подверглась серьезной критике и эволюции. Современные фреймворки и языки на JVM демонстрируют явный сдвиг в сторону unchecked-исключений даже для традиционно checked-сценариев.


Механика checked-исключений: контракты и обязательства

Checked-исключения представляют собой часть сигнатуры метода, формируя контракт между вызывающим и вызываемым кодом. Когда метод объявляет throws IOException, он явно заявляет о возможности сбоя ввода-вывода, обязывая вызывающий код либо обработать эту ситуацию через try-catch, либо передать ответственность дальше по стеку вызовов. Этот механизм создает цепочку ответственности.

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

Практический пример демонстрирует эту проблему:
// Низкоуровневый метод работы с базой данных
public User findById(String id) throws SQLException {
Connection conn = dataSource.getConnection();
// ... выполнение запроса
}

// Сервисный слой вынужден пробрасывать checked-исключение
public User getUser(String id) throws SQLException {
return userRepository.findById(id);
}

// Контроллер также заражен checked-исключением
public UserDto getUserEndpoint(String id) throws SQLException {
User user = userService.getUser(id);
return userMapper.toDto(user);
}


В этой иерархии SQLException пронизывает все слои приложения, нарушая принцип разделения ответственности. Контроллер, отвечающий за HTTP-протокол, вынужден знать о деталях работы с базой данных, что создает нежелательную связанность (coupling) между модулями.


Unchecked-исключения: свобода и ответственность

Unchecked-исключения, наследующие RuntimeException, не требуют объявления в сигнатурах методов. Они могут возникнуть в любом месте и распространяться по стеку вызовов до тех пор, пока не будут перехвачены или не приведут к завершению потока. Эта модель освобождает разработчика от обязательной обработки каждого возможного сбоя, но требует дисциплины в проектировании границ транзакций и обработки ошибок.

Ключевое преимущество unchecked-исключений — сохранение чистоты сигнатур методов. Метод может сосредоточиться на своей основной ответственности, не загромождая сигнатуру перечнем возможных сбоев. Обработка ошибок откладывается на уровень, обладающий достаточным контекстом для принятия решений: retry, fallback, уведомление пользователя или аварийное завершение операции.


#Java #для_новичков #beginner #exception #checked #unchecked
👍3
Пример рефакторинга предыдущего кода в unchecked-стиле:
// Кастомное unchecked-исключение для домена
public class UserNotFoundException extends RuntimeException {
private final String userId;

public UserNotFoundException(String userId) {
super("User not found: " + userId);
this.userId = userId;
}
}

// Репозиторий скрывает детали SQL, транслирует в доменное исключение
public User findById(String id) {
try {
Connection conn = dataSource.getConnection();
// ... выполнение запроса
} catch (SQLException e) {
throw new DataAccessException("Database error while loading user", e);
}
}

// Сервисный слой чист, без throws
public User getUser(String id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}

// Контроллер также чист, фокусируется на HTTP-логике
public UserDto getUserEndpoint(String id) {
User user = userService.getUser(id);
return userMapper.toDto(user);
}


В этом варианте SQLException перехватывается на границе инфраструктурного слоя и транслируется в DataAccessException — unchecked-исключение из Spring Framework. Доменное исключение UserNotFoundException также unchecked, так как отсутствие пользователя — это валидный бизнес-сценарий, который контроллер обрабатывает через глобальный обработчик исключений, преобразуя в HTTP 404.


Эволюция Spring Framework: от checked к unchecked

Spring Framework стал одним из первых major-фреймворков, систематически отказавшихся от checked-исключений. Философия Spring заключается в том, что большинство исключений в enterprise-приложениях — это фатальные сбои текущей операции, от которых невозможно восстановиться на месте.

Центральным элементом этой стратегии является иерархия DataAccessException. Spring перехватывает низкоуровневые checked-исключения (SQLException, HibernateException, JPAException) и транслирует их в структурированную иерархию unchecked-исключений: DataRetrievalFailureException, DataIntegrityViolationException, QueryTimeoutException и другие. Это достигается двумя целями: абстрагирование от конкретной технологии доступа к данным и признание того, что большинство ошибок базы данных фатальны для текущей транзакции.

Пример интеграции с Spring Data:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Метод объявлен без throws, но может выбросить unchecked исключения
Optional<Order> findByOrderNumber(String orderNumber);
}

@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;

@Transactional
public Order processOrder(String orderNumber) {
// Никаких try-catch для checked-исключений
Order order = orderRepository.findByOrderNumber(orderNumber)
.orElseThrow(() -> new OrderNotFoundException(orderNumber));

// При ошибке базы данных будет выброшено DataAccessException
// Транзакция откатится автоматически
return executeBusinessLogic(order);
}
}


В этом коде отсутствуют объявления throws, несмотря на потенциальные сбои базы данных. Spring управляет транзакциями через @Transactional, и любое unchecked-исключение приводит к автоматическому откату.

Обработка ошибок централизована через @ControllerAdvice:
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDatabaseError(DataAccessException e) {
// Логирование, метрики, уведомление
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(new ErrorResponse("Database temporarily unavailable"));
}

@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(OrderNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(e.getMessage()));
}
}



#Java #для_новичков #beginner #exception #checked #unchecked
👍3
Этот подход разделяет бизнес-логику и обработку ошибок. Бизнес-код остается чистым и сфокусированным, в то время как обработка ошибок концентрируется в специализированных компонентах с полным контекстом HTTP-запроса.


Kotlin: полный отказ от checked-исключений

Kotlin пошел дальше, полностью устранив концепцию checked-исключений из языка. Все исключения в Kotlin по умолчанию unchecked, что соответствует философии минимизации шаблонного кода (boilerplate) и прагматичной разработки.

Это решение было обусловлено накопившимися проблемами checked-исключений в Java: многословностью, разрушением композиции в функциональном стиле, принуждением к плохим практикам (пустые catch-блоки, оборачивание в RuntimeException) и несовместимостью с лямбда-выражениями. Как отмечают разработчики Kotlin, за 25 лет checked-исключения в Java продемонстрировали столько же проблем, сколько решений, и ни один другой язык не принял эту модель .

В Kotlin код обработки ошибок выглядит естественно:
fun updateOrderQuantity(orderId: OrderId, quantity: Int) {
require(quantity > 0) { "Quantity must be positive" }
val order = loadOrder(orderId) // Может выбросить исключение, но не требует try-catch
order.quantity = quantity
storeOrder(order) // То же самое
}


Ключевая концепция Kotlin — централизованная обработка I/O-ошибок. Вместо разбросанных по коду try-catch блоков для каждой сетевой операции, Kotlin предлагает выносить обработку на границу между бизнес-логикой и пользовательским интерфейсом. Это соответствует реальности enterprise-приложений, где сетевые сбои обрабатываются единообразно: retry с экспоненциальным backoff, fallback к кэшу или уведомление пользователя о временной недоступности сервиса.

Для взаимодействия с Java-кодом, использующим checked-исключения, Kotlin предоставляет аннотацию @Throws, которая генерирует соответствующие сигнатуры в байткоде для Java-вызывающих сторон. Однако внутри Kotlin- codebase эта информация не используется для статической проверки.


Фатальный удар: checked-исключения и Java 8 Streams

Введение лямбда-выражений и Stream API в Java 8 выявило фундаментальную несовместимость checked-исключений с функциональным программированием. Функциональные интерфейсы (Function<T,R>, Consumer<T>, Supplier<T>) не объявляют checked-исключений в своих абстрактных методах, что делает невозможным прямое использование методов, бросающих checked-исключения, в лямбдах.

Рассмотрим попытку использовать метод, объявляющий throws IOException, внутри Stream.map():
// Метод с checked-исключением
public String fetchDataFromUrl(String url) throws IOException {
return httpClient.fetch(url);
}

// Невозможно напрямую использовать в Stream
List<String> urls = List.of("http://api1.com", "http://api2.com");
List<String> data = urls.stream()
.map(this::fetchDataFromUrl) // Ошибка компиляции: IOException не обработано
.collect(Collectors.toList());


Разработчики вынуждены прибегать к обходным маневрам: оборачиванию исключений в RuntimeException внутри лямбды, созданию специальных функциональных интерфейсов с throws, или использованию библиотек вроде Vavr. Все эти решения уродливы и разрушают элегантность функционального стиля.

Пример оборачивания, который демонстрирует проблему:
List<String> data = urls.stream()
.map(url -> {
try {
return fetchDataFromUrl(url);
} catch (IOException e) {
throw new RuntimeException(e); // Уничтожение семантики checked-исключения
}
})
.collect(Collectors.toList());


Эта ситуация привела к тому, что комитет по развитию Java фактически признал checked-исключения тупиковой ветвью эволюции языка. Отсутствие поддержки checked-исключений в Stream API — это не oversight, а осознанное решение, демонстрирующее, что checked-исключения и современный Java несовместимы.


#Java #для_новичков #beginner #exception #checked #unchecked
👍4
Современные best practices: доминирование unchecked

Современные рекомендации по проектированию исключений в Java существенно эволюционировали. Если ранее считалось нормой делать все восстановимые сбои checked, то теперь преобладает подход "unchecked by default" с несколькими четкими критериями для исключений.

Используйте checked-исключения только когда:
- Восстановление возможно и вероятно на месте вызова. Пример: повторная попытка при временном сбое сети с backoff, запрос альтернативного ввода от пользователя.
- Исключение является частью доменной модели и требует явного внимания клиента. Пример: бизнес-исключение InsufficientFundsException в финансовой системе, где вызывающий код должен явно обработать сценарий недостаточности средств.
- API предназначен для широкого использования внешними клиентами, и пропуск исключения приведет к катастрофическим последствиям.

Во всех остальных случаях используйте unchecked-исключения:
- Программные ошибки (null-аргументы, выход за границы, нарушение инвариантов).
- Фатальные сбои внешних систем (база данных недоступна, сетевой таймаут), где восстановление требует транзакционной логики на более высоком уровне.
- Сценарии, где исключение транслируется через несколько слоев абстракции перед обработкой.
- Код, интегрирующийся с функциональными API (Streams, CompletableFuture, Optional).

Пример проектирования современного API:
// Checked-исключение для редкого, но критичного сценария восстановления
public class PaymentReversalRequiredException extends Exception {
private final Payment originalPayment;
private final String reasonCode;

public PaymentReversalRequiredException(Payment payment, String reasonCode, String message) {
super(message);
this.originalPayment = payment;
this.reasonCode = reasonCode;
}

// Методы доступа для логики восстановления
public Payment getOriginalPayment() { return originalPayment; }
public String getReasonCode() { return reasonCode; }
}

// Unchecked-исключения для всех остальных сценариев
public class PaymentProcessingException extends RuntimeException {
private final String transactionId;

public PaymentProcessingException(String transactionId, String message, Throwable cause) {
super(message, cause);
this.transactionId = transactionId;
}
}

public class InvalidPaymentRequestException extends IllegalArgumentException {
private final String fieldName;

public InvalidPaymentRequestException(String fieldName, Object value) {
super(String.format("Invalid value for field %s: %s", fieldName, value));
this.fieldName = fieldName;
}
}


PaymentReversalRequiredException — checked, потому что требует немедленного действия: отката платежа, уведомления бухгалтерии, возможно, ручного вмешательства. Это редкий, но критичный бизнес-сценарий.
PaymentProcessingException — unchecked, так как ошибки процессинга (таймаут, недоступность шлюза) фатальны для текущей операции и обрабатываются централизованно через retry-механизм или circuit breaker.
InvalidPaymentRequestException — unchecked, так как передача невалидных данных — это ошибка вызывающего кода, которая должна быть исправлена до деплоя.


#Java #для_новичков #beginner #exception #checked #unchecked
👍3
Паттерн "Railway Oriented Programming" и альтернативы исключениям

В функциональном стиле, особенно при работе с Kotlin или современными Java-библиотеками, исключения часто заменяются на типы-результаты (Result types). Это позволяет явно моделировать ошибки в типовой системе без checked-исключений.

Пример с использованием библиотеки Vavr:
import io.vavr.control.Either;
import io.vavr.control.Try;

public Either<PaymentError, PaymentReceipt> processPayment(PaymentRequest request) {
return validateRequest(request)
.flatMap(this::authorizePayment)
.flatMap(this::executeTransfer)
.map(this::generateReceipt);
}

private Either<PaymentError, ValidatedRequest> validateRequest(PaymentRequest request) {
if (request.getAmount() <= 0) {
return Either.left(new PaymentError("INVALID_AMOUNT", "Amount must be positive"));
}
if (request.getCurrency() == null) {
return Either.left(new PaymentError("MISSING_CURRENCY", "Currency is required"));
}
return Either.right(new ValidatedRequest(request));
}


В этом подходе ошибки — это значения (values), а не исключительные ситуации. Клиентский код вынужден обрабатывать оба случая (success и failure) через pattern matching или методы fold, getOrElse, orElseThrow. Это устраняет проблему "забытого catch-блока", присущую unchecked-исключениям, без возврата к verbosity checked-исключений.


#Java #для_новичков #beginner #exception #checked #unchecked
👍4
Что выведет код?

import java.io.IOException;

public class Task100426 {
public static void main(String[] args) {
try {
method();
} catch (Exception e) {
System.out.println("Catch: " + e.getClass().getSimpleName());
}
}

static void method() {
try {
throw new IOException("IO");
} catch (IOException e) {
throw new RuntimeException("Runtime");
} finally {
return;
}
}
}


#Tasks
👍1
Что такое ReentrantLock и чем он отличается от synchronized? 🤓

Ответ:

ReentrantLock
— это гибкая альтернатива блоку synchronized из пакета java.util.concurrent.locks.

Преимущества: возможность прервать ожидание (lockInterruptibly()), попытаться захватить lock без блокировки (tryLock()), создание честных блокировок (fairness).

Недостаток: нужно вручную освобождать в блоке finally. synchronized проще в использовании, менее подвержен ошибкам, но менее гибок.

Оба являются reentrant — поток может повторно захватить уже удерживаемый им же монитор/lock.



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