У меня есть блок для записи данных (строки) с использованием 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();}
}
Кстати, существует метод Files.write, который записывает последовательность строк в файл без необходимости создавать Writer. Например, Files.write(filePath, this.dataContainer1);
.
@VGR Files.write() также был поднят Хольгером. Похоже, он принимает Path и Iterable (ArrayList, Queue и т. д.), и он автоматически проходит через него и записывает элемент за элементом?
Да. Он не идентичен вашему коду, поскольку ваш код не записывает новую строку после каждой строки. Я не уверен, было ли это намеренно.
@VGR, спасибо, это работает лучше. Каждый элемент данных в моем примере dataContainer представляет собой CSV с включенной новой строкой.
Проблема в том, что метод 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()
. В этом случае многие операции ввода-вывода не подходят для выполнения внутри потока?
Вот тут есть UncheckedIOException
, вам следует использовать его вместо RuntimeException
. В более общем плане очевидным решением здесь является не использовать forEach
. Лямбды непрозрачны по трем причинам (локальные, поток управления, проверенные исключения) — если это раздражает (а это обычно так), не используйте их. Просто используйте цикл while.
@rzwitserloot Это мало что меняет, настоящим исключением является IOException, повторное создание того, которое вы сказали, а не любой другой среды выполнения, будет вести себя одинаково с точки зрения потока (вы не объявляете это в сигнатуре), и если вы входите в систему, вы все равно будете см. исходное исключение IOException в журналах. Что касается лямбда-выражений, пока вы создаете/используете хитрую конструкцию throw, вы можете продолжать использовать лямбды без особых накладных расходов. Я согласен, что иногда вы можете просто использовать стандартные циклы, но когда эти циклы становятся сложными, потоковый API делает код более читабельным, на мой вкус.
Имя переменной line
предполагает, что на самом деле предназначена запись строк. В этом случае используйте подходящий для работы инструмент: Files.write(filePath, dataContainer1); Files.write(filePath, dataContainer2, StandardOpenOption.APPEND);
Тогда перенос исключений не требуется. Но если вы оборачиваете IOException
в исключение во время выполнения, использование UncheckedIOException
предпочтительнее не из-за того, что вы видите в журналах, когда исключение не перехвачено, а потому, что это позволяет перехватить его и повторно выбросить IOException
, поскольку метод был объявлен как throws IOException
…
@Holger мои данные хранятся в LinkedList (dataContainer), поэтому нужно просмотреть их и записать каждый элемент (строку) один за другим. Предложенный вами Files.write() принимает Iterable в качестве аргумента и автоматически перебирает коллекцию? Никогда не использовал его раньше.
Да, именно это и происходит.
У @Holger правильная идея. Иначе, Limestreetlab, откуда у тебя разделители строк?
@Хольгер, я не говорю, что ты неправ, явно есть лучшие способы сделать то, что хочет ОП. Но давайте не будем забывать, что вопрос был о том, «почему компилятор все еще говорит мне перехватывать IOException», и ответ - это то, что я написал. Но я с вами согласен, есть много других лучших способов записать строки в файл
@MatteoNNZ, конечно, это многое меняет. Вы удалили из меню пункт «Разберись с этим». Вы не сможете уловить RuntimeException
осмысленно, если не приложите все усилия, чтобы «взломать» этот «взлом» (найти причину и instanceof
или сопоставить его с образцом, что просто копает глубже. Что while
когда-либо сделал с вами, что вы испортили кодовую базу с stream()
просто назло?) - или вы все ловите (бегуны по конечным точкам могут это сделать). UncheckedIOException, по крайней мере, более значимо перехватывается.
@MatteoNNZ У тебя отключена чувствительность. Это довольно распространенное явление: «О, посмотрите, потоки. Этот код ЧИСТЫЙ. А любой код, который этого не делает, — УЖАСНЫЙ». Это вводит в заблуждение и затрудняет написание кода для тестирования и поддержки. Например, очень небрежно отбросить любую способность писать осмысленные уловки. Или просто быть слепым к тому, что происходит. См., например, комментарий Хольгера. Этот ответ не «просто» отвечает на вопрос. Также существуют мнения о том, как с этим бороться. И это мнение ошибочно. Или, по крайней мере, весьма подозрительно.
@rzwitserloot, если вы хотите открыть поток споров или цикл, я останавливаю вас на этом: я никогда не пишу потоки, если мне не нужно делать базовые вещи, потому что они медленнее, их невозможно отладить, они навязывают вам конечные переменные и не могут выдавать проверенные исключения, это единственный способ явно сообщить вызывающему абоненту, что что-то может пойти не так. Но опять же: ОП спросил, почему перехват IOException не сработал, и я объяснил, что это из-за лямбды в forEach. Я никогда не говорил, что это лучший подход к тому, что он делает, я просто отвечал на вопрос.
@rzwitserloot относительно непроверенного IOException: это по-прежнему исключение во время выполнения. Конечно, вы можете это уловить, но компилятор вам об этом не скажет. Он ничего вам не скажет, даже если вы объявите его в списке throws метода. Таким образом, либо вы знаете, что вам нужно его перехватить (и в этом случае вы можете использовать хитрый бросок, как я предложил, и избежать любого бесполезного переноса), или вы пропустите его так же, как вы пропустили бы любое другое исключение во время выполнения, которое по определению не думал, что его поймают, но он будет течь вверх до тех пор, пока не будет достигнута глобальная точка обработчика catch Throwable
@g00se спасибо, каждый line
в моих данных уже имеет разделитель новой строки
Спасибо @rzwitserloot и MatteroNNZ. Допустим, у вас обоих есть хорошие идеи, и мы остановимся на этом?
@limestreetlab, вам приятно волноваться, но мы не спорим, это всего лишь дискуссии среди разработчиков :) rzwitserloot прав во многих вещах, которые он сказал, а именно в использовании потоков (которыми часто злоупотребляют, думая, что они чище), а также в переносе исключений. в RuntimeException. Опять же, я ответил на ваш вопрос как есть. Если вы спросите меня, какое решение я бы выбрал, это решение, предложенное g00se, а не исправление, которое я предложил выше (что, опять же, является просто ответом на ваш вопрос, а не ответом на вашу проблему).
@MatteoNNZ да, ваш ответ касался буквального вопроса ОП. Вот почему ваш пост — это ответ, а мой — просто комментарий. В таких ситуациях я обычно предпочитаю включать в ответ оба аспекта, чтобы помочь понять проблему и найти (лучшее) решение. Мне нравится эта старая статья о дилемме.
Просто чтобы прояснить, насколько чисто решение Хольгера:
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);
Измените это на
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(filePath))) { ...
ИprintStackTrace
кстати