惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
I
InfoQ
小众软件
小众软件
Recent Announcements
Recent Announcements
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
大猫的无限游戏
大猫的无限游戏
美团技术团队
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
V
V2EX
J
Java Code Geeks
有赞技术团队
有赞技术团队
博客园 - 聂微东
B
Blog RSS Feed
博客园 - 司徒正美

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Обработка исключений, возникших при обработке исключений
flaz14 · 2026-05-16 · via Все публикации подряд на Хабре

Обработка исключений, возникших при обработке исключений

Простой

4 мин

13K

Исключения рождаются не только в основном коде, но и в обработчиках этих самых исключений. Зачастую вопросу не уделяется должного внимания. Действительно, что может пойти не так в блоке catch? Там ведь код тривиальный! Но это только на первый взгляд.

Например, безобидный LOG.warn("...") выливается в десяток вызовов нижележащих методов. И чем больше «наслоений» в библиотеке логгирования, тем выше вероятность сбоя. Всё бы ничего, если бы не одна особенность языка Java…

Суть проблемы

Исключение, возникшее в блоке catch, «проглатывает» исключение, возникшее в блоке try.

Листинг 1

public class Listing1 {
    public static void main(String[] args) {
        try {
            System.out.println("> TRY block");
            throw new RuntimeException("exception from TRY block");
        } catch (Exception e) {
            System.out.println("> CATCH block");
            System.out.println("> " + e);
            throw new RuntimeException("exception from CATCH block");
        }
    }
}

Запустим код из листинга 1. Как видим, исключение из try не попало в стектрейс, как будто его не было вовсе:

> TRY block
> CATCH block
> java.lang.RuntimeException: exception from TRY block
Exception in thread "main" java.lang.RuntimeException: exception from CATCH block
	at Listing1.main(Listing1.java:9)

Подавленный, но не раздавленный

Немного отвлечёмся и рассмотрим автоматическое закрытие ресурсов, а именно try-with-resources. С ним ситуация несколько иная. Если исключение возникло и в блоке try, и в блоке TWR, то второе не пропадает бесследно, а добавляется к первому и становится подавленным (suppressed) по отношению к нему.

Листинг 2

public class Listing2 {
    public static void main(String[] args) throws Exception {
        try (AutoCloseable r = () -> {
            System.out.println("> TWR block");
            throw new RuntimeException("exception from TWR block");
        }) {
            System.out.println("> TRY block");
            throw new RuntimeException("exception from TRY block");
        }
    }
}

Прогоним листинг 2 и увидим, что оба исключения присутствуют в стектрейсе:

> TRY block
> TWR block
Exception in thread "main" java.lang.RuntimeException: exception from TRY block
	at Listing2.main(Listing2.java:8)
	Suppressed: java.lang.RuntimeException: exception from TWR block
		at Listing2.lambda$main$0(Listing2.java:5)
		at Listing2.main(Listing2.java:3)

Попробуем добиться такого же эффекта применительно к блоку catch.

Решение

К сожалению, TWR здесь не поможет, ибо он выполняется до catch. Остаётся лишь вложенный try и явное добавление вторичного исключения к первичному.

Листинг 3

public class Listing3 {
    public static void main(String[] args) {
        try {
            System.out.println("> TRY block");
            throw new RuntimeException("exception from TRY block");
        } catch (RuntimeException primaryException) {
            try {
                System.out.println("> CATCH block");
                System.out.println("> " + primaryException);
                throw new RuntimeException("exception from CATCH block");
            } catch (RuntimeException secondaryException) {
                primaryException.addSuppressed(secondaryException);
                throw primaryException;
            }
        }
    }
}

Запустим листинг 3 и убедимся, что исключение из catch не пропало:

> TRY block
> CATCH block
> java.lang.RuntimeException: exception from TRY block
Exception in thread "main" java.lang.RuntimeException: exception from TRY block
	at Listing3.main(Listing3.java:5)
	Suppressed: java.lang.RuntimeException: exception from CATCH block
		at Listing3.main(Listing3.java:10)

Стоит отметить, что метод Throwable.addSuppressed() добавит вторичное исключение только в том случае, если первичное было создано с параметром enableSuppression, установленным в true (что делается по умолчанию).

А вот выяснить, было ли разрешено добавление исключений, затруднительно. Так, метод Throwable.getSuppressed() возвращает пустой массив, если подавленных исключений ещё нет или их добавление запрещено. Различить этих два случая нельзя. Можно, конечно, измерить длину массива перед добавлением и после… А можно не мучаться, сразу создать ещё одно исключение и прицепить к нему уже имеющиеся.

Листинг 4

public class Listing4 {
    public static void main(String[] args) {
        try {
            System.out.println("> TRY block");
            throw new RuntimeException("exception from TRY block");
        } catch (RuntimeException primaryException) {
            try {
                System.out.println("> CATCH block");
                System.out.println("> " + primaryException);
                throw new RuntimeException("exception from CATCH block");
            } catch (RuntimeException secondaryException) {
                var joinedException = new RuntimeException("Joined exception");
                joinedException.addSuppressed(primaryException);
                joinedException.addSuppressed(secondaryException);
                throw joinedException;
            }
        }
    }
}

Результат работы листинга 4 выглядит неплохо. Но что-то здесь не так: стектрейс начинается с исключения, которое возникло в потоке выполнения последним. Такая перестановка может сбить с толку:

> TRY block
> CATCH block
> java.lang.RuntimeException: exception from TRY block
Exception in thread "main" java.lang.RuntimeException: Joined exception
	at Listing4.main(Listing4.java:12)
	Suppressed: java.lang.RuntimeException: exception from TRY block
		at Listing4.main(Listing4.java:5)
	Suppressed: java.lang.RuntimeException: exception from CATCH block
		at Listing4.main(Listing4.java:10)

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

Немного о finally

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

Листинг 5

public class Listing5 {
    public static void main(String[] args) {
        try (FinallyBlock f = new FinallyBlock()) {
            System.out.println("> Processing...");
            // делаем ещё работу 
        } finally {
            // намеренно оставляем этот блок пустым
        }
    }
}

class FinallyBlock implements AutoCloseable {
    @Override
    public void close() {
        String message = "> Completed";
        System.out.println(message);
        // делаем c message что-то ещё...
    }
}

Блок finally проигрывает TWR во всём, кроме:

  1. Выполняется после catch.

  2. В нём можно присваивать значения переменным, объявленным выше try.

Заключение

Работа над ошибками, порождёнными предыдущей работой над ошибками – не такая уж безумная затея. Но всего нужно в меру. Как говорил один умный человек, не превращайте паранойю в маразм.

Что до языка Java, вышеописанные особенности не являются его изъянами. Обстоятельства могут сложиться так, что одно исключение просто необходимо подменить другим. И лучше иметь такую возможность, чем не иметь.