Как использовать try-catch (IOException) для BufferedWriter.write, вложенного в if-else?

У меня есть блок для записи данных (строки) с использованием BufferedWriter, но компилятор продолжает сообщать об ошибке: несообщенное исключение IOException; должен быть перехвачен или объявлен для выдачи this.dataContainer.stream().forEach( line -> write.write(line) );

BufferWriter.write() уже находится внутри блока try-catch. Вызванная этим ошибка вложена внутри if-else? Как это написать?

void saveData() throws IOException {
        
String filename;
Path filePath; 
BufferedWriter writer;
    
filename = "missionImpossible9.csv";
filePath = this.dirPath.resolve(filename); //make path of this file
writer = Files.newBufferedWriter(filePath); 
        
try {
    if (condition1) {
       this.dataContainer1.stream().forEach( line -> writer.write(line) );
     } else {
       this.dataContainer2.stream().forEach( line -> writer.write(line) );
     }
     writer.close();
} catch (IOException err){
    err.getStackTrace();}
}

Измените это на try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(filePath))) { ... И printStackTrace кстати

g00se 02.07.2024 00:21

Кстати, существует метод Files.write, который записывает последовательность строк в файл без необходимости создавать Writer. Например, Files.write(filePath, this.dataContainer1);.

VGR 02.07.2024 16:34

@VGR Files.write() также был поднят Хольгером. Похоже, он принимает Path и Iterable (ArrayList, Queue и т. д.), и он автоматически проходит через него и записывает элемент за элементом?

limestreetlab 02.07.2024 17:22

Да. Он не идентичен вашему коду, поскольку ваш код не записывает новую строку после каждой строки. Я не уверен, было ли это намеренно.

VGR 02.07.2024 17:36

@VGR, спасибо, это работает лучше. Каждый элемент данных в моем примере dataContainer представляет собой CSV с включенной новой строкой.

limestreetlab 02.07.2024 22:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
102
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема в том, что метод write() из BufferedWriter объявлен как выбрасывающий IOException, но вы вызываете его внутри Consumer<?> (.forEach()), который не должен выдавать никаких проверенных исключений:

this.dataContainer1.stream().forEach( line -> writer.write(line) );

Выражение line -> writer.write(line) — это лямбда-выражение, реализующее функциональный интерфейс Consumer, и вы не можете создавать проверенные исключения (например, IOException) внутри лямбды.

Способ решения проблемы состоит в том, чтобы поймать IOException внутри самой лямбды и перебросить его, завернутый в RuntimeException (который не отмечен галочкой):

this.dataContainer1.stream().forEach(line -> {
        try {
            writer.write(line); //try code that throws a checked exception
        } catch (IOException e) { //catch that checked exception
            throw new RuntimeException(e); //rethrow it as the cause of a runtime (unchecked) exception
        }
    });

На этом этапе вам больше не нужно ловить IOException (в любом случае, если IOException произойдет от write(), вы получите его как RuntimeException имеющую причину того, что IOException.

Примечание. Есть более удобный способ сохранить тип IOException без жалоб компилятора, который называется Sneaky Throw. Возможно, вам захочется прочитать об этом статью: Статья о хитрых бросках. Вообще говоря, не рекомендуется отключать проверенное исключение таким образом, но иногда другого выбора нет (например, когда вы хотите запустить код внутри лямбда-выражения и вам нужно использовать функциональный интерфейс, который не выдает выдачи, например Consumer) .

Спасибо. Никогда не думал, что проверенное исключение не может возникнуть внутри forEach(). В этом случае многие операции ввода-вывода не подходят для выполнения внутри потока?

limestreetlab 02.07.2024 00:57

Вот тут есть UncheckedIOException, вам следует использовать его вместо RuntimeException. В более общем плане очевидным решением здесь является не использовать forEach. Лямбды непрозрачны по трем причинам (локальные, поток управления, проверенные исключения) — если это раздражает (а это обычно так), не используйте их. Просто используйте цикл while.

rzwitserloot 02.07.2024 01:13

@rzwitserloot Это мало что меняет, настоящим исключением является IOException, повторное создание того, которое вы сказали, а не любой другой среды выполнения, будет вести себя одинаково с точки зрения потока (вы не объявляете это в сигнатуре), и если вы входите в систему, вы все равно будете см. исходное исключение IOException в журналах. Что касается лямбда-выражений, пока вы создаете/используете хитрую конструкцию throw, вы можете продолжать использовать лямбды без особых накладных расходов. Я согласен, что иногда вы можете просто использовать стандартные циклы, но когда эти циклы становятся сложными, потоковый API делает код более читабельным, на мой вкус.

Matteo NNZ 02.07.2024 07:40

Имя переменной line предполагает, что на самом деле предназначена запись строк. В этом случае используйте подходящий для работы инструмент: Files.write(filePath, dataContainer1); Files.write(filePath, dataContainer2, StandardOpenOption.APPEND); Тогда перенос исключений не требуется. Но если вы оборачиваете IOException в исключение во время выполнения, использование UncheckedIOException предпочтительнее не из-за того, что вы видите в журналах, когда исключение не перехвачено, а потому, что это позволяет перехватить его и повторно выбросить IOException, поскольку метод был объявлен как throws IOException

Holger 02.07.2024 11:58

@Holger мои данные хранятся в LinkedList (dataContainer), поэтому нужно просмотреть их и записать каждый элемент (строку) один за другим. Предложенный вами Files.write() принимает Iterable в качестве аргумента и автоматически перебирает коллекцию? Никогда не использовал его раньше.

limestreetlab 02.07.2024 15:58

Да, именно это и происходит.

Holger 02.07.2024 16:19

У @Holger правильная идея. Иначе, Limestreetlab, откуда у тебя разделители строк?

g00se 02.07.2024 18:51

@Хольгер, я не говорю, что ты неправ, явно есть лучшие способы сделать то, что хочет ОП. Но давайте не будем забывать, что вопрос был о том, «почему компилятор все еще говорит мне перехватывать IOException», и ответ - это то, что я написал. Но я с вами согласен, есть много других лучших способов записать строки в файл

Matteo NNZ 02.07.2024 19:05

@MatteoNNZ, конечно, это многое меняет. Вы удалили из меню пункт «Разберись с этим». Вы не сможете уловить RuntimeException осмысленно, если не приложите все усилия, чтобы «взломать» этот «взлом» (найти причину и instanceof или сопоставить его с образцом, что просто копает глубже. Что while когда-либо сделал с вами, что вы испортили кодовую базу с stream() просто назло?) - или вы все ловите (бегуны по конечным точкам могут это сделать). UncheckedIOException, по крайней мере, более значимо перехватывается.

rzwitserloot 02.07.2024 21:22

@MatteoNNZ У тебя отключена чувствительность. Это довольно распространенное явление: «О, посмотрите, потоки. Этот код ЧИСТЫЙ. А любой код, который этого не делает, — УЖАСНЫЙ». Это вводит в заблуждение и затрудняет написание кода для тестирования и поддержки. Например, очень небрежно отбросить любую способность писать осмысленные уловки. Или просто быть слепым к тому, что происходит. См., например, комментарий Хольгера. Этот ответ не «просто» отвечает на вопрос. Также существуют мнения о том, как с этим бороться. И это мнение ошибочно. Или, по крайней мере, весьма подозрительно.

rzwitserloot 02.07.2024 21:24

@rzwitserloot, если вы хотите открыть поток споров или цикл, я останавливаю вас на этом: я никогда не пишу потоки, если мне не нужно делать базовые вещи, потому что они медленнее, их невозможно отладить, они навязывают вам конечные переменные и не могут выдавать проверенные исключения, это единственный способ явно сообщить вызывающему абоненту, что что-то может пойти не так. Но опять же: ОП спросил, почему перехват IOException не сработал, и я объяснил, что это из-за лямбды в forEach. Я никогда не говорил, что это лучший подход к тому, что он делает, я просто отвечал на вопрос.

Matteo NNZ 02.07.2024 21:44

@rzwitserloot относительно непроверенного IOException: это по-прежнему исключение во время выполнения. Конечно, вы можете это уловить, но компилятор вам об этом не скажет. Он ничего вам не скажет, даже если вы объявите его в списке throws метода. Таким образом, либо вы знаете, что вам нужно его перехватить (и в этом случае вы можете использовать хитрый бросок, как я предложил, и избежать любого бесполезного переноса), или вы пропустите его так же, как вы пропустили бы любое другое исключение во время выполнения, которое по определению не думал, что его поймают, но он будет течь вверх до тех пор, пока не будет достигнута глобальная точка обработчика catch Throwable

Matteo NNZ 02.07.2024 21:51

@g00se спасибо, каждый line в моих данных уже имеет разделитель новой строки

limestreetlab 02.07.2024 22:58

Спасибо @rzwitserloot и MatteroNNZ. Допустим, у вас обоих есть хорошие идеи, и мы остановимся на этом?

limestreetlab 02.07.2024 23:02

@limestreetlab, вам приятно волноваться, но мы не спорим, это всего лишь дискуссии среди разработчиков :) rzwitserloot прав во многих вещах, которые он сказал, а именно в использовании потоков (которыми часто злоупотребляют, думая, что они чище), а также в переносе исключений. в RuntimeException. Опять же, я ответил на ваш вопрос как есть. Если вы спросите меня, какое решение я бы выбрал, это решение, предложенное g00se, а не исправление, которое я предложил выше (что, опять же, является просто ответом на ваш вопрос, а не ответом на вашу проблему).

Matteo NNZ 02.07.2024 23:16

@MatteoNNZ да, ваш ответ касался буквального вопроса ОП. Вот почему ваш пост — это ответ, а мой — просто комментарий. В таких ситуациях я обычно предпочитаю включать в ответ оба аспекта, чтобы помочь понять проблему и найти (лучшее) решение. Мне нравится эта старая статья о дилемме.

Holger 03.07.2024 09:37

Просто чтобы прояснить, насколько чисто решение Хольгера:

    void saveData() throws IOException {
        String filename;
        Path filePath;
        boolean condition1 = false;
        Path dirPath = Path.of("/tmp");
        filename = "missionImpossible9.csv";
        filePath = dirPath.resolve(filename);

        if (condition1) {
            Files.write(filePath, dataContainer1);
        } else {
            Files.write(filePath, dataContainer2);
        }
    }

Или даже Files.write(filePath, condition1? dataContainer1: dataContainer2);

Holger 03.07.2024 09:17

Другие вопросы по теме