Я хотел бы сказать, правило таково, что если лямбда-выражение является настолько сложным, что вы чувствуете необходимость издеваться битами этого, что это, вероятно, слишком сложно. Его следует разбить на более мелкие куски, которые составлены вместе, или, возможно, модель должна быть скорректирована, чтобы сделать ее более подходящей для композиции.
Я скажу, что Andrey Chaschev's answer, который предлагает параметрирование зависимости, является хорошим и, вероятно, применим в некоторых ситуациях. Итак, +1 для этого. Один мог продолжать этот процесс и разбить обработку на более мелкие куски, например, так:
public List<Item> processFile(
String fileName,
Function<String, BufferedReader> toReader,
Function<BufferedReader, List<String>> toStringList,
Function<List<String>, List<Item>> toItemList)
{
List<String> lines = null;
try (BufferedReader br = toReader.apply(fileName)) {
lines = toStringList.apply(br);
} catch (IOException ioe) { /* ... */ }
return toItemList.apply(lines);
}
пару замечаний по этому вопросу, хотя. Во-первых, это не работает так, как написано, так как различные lambdas бросают pesky IOExceptions
, которые проверяются, и тип Function
не объявлен, чтобы выбросить это исключение. Во-вторых, лямбды, которые вы должны передать этой функции, чудовищны. Несмотря на то, что это не сработало (из-за отмеченных исключений), я написал:
void processAnActualFile() {
List<Item> items = processFile(
"file.csv",
fname -> new BufferedReader(new FileReader(fname)),
// ERROR: uncaught IOException
br -> {
List<String> result = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
result.add(line);
}
return result;
}, // ERROR: uncaught IOException
stringList -> {
List<Item> result = new ArrayList<>();
for (String line : stringList) {
result.add(new Item(line));
}
return result;
});
}
Ugh! Я думаю, что обнаружил новый запах кода:
Если вам нужно написать цикл for или loop внутри лямбда, вы делаете что-то неправильно.
Несколько вещей здесь. Во-первых, библиотека ввода-вывода действительно состоит из разных частей реализации (InputStream
, Reader
, BufferedReader
), которые плотно связаны. На самом деле не полезно пытаться разбить их. В самом деле, библиотека развилась так, что есть некоторые удобные утилиты (например, NIO Files.readAllLines
), которые обрабатывают кучу работы для вас.
Более важным моментом является то, что проектирование функций, которые передают совокупности (списки) значений между собой и составляющих эти функции, на самом деле является неправильным способом. Это приводит к тому, что каждая функция должна писать петлю внутри нее. Мы действительно хотим написать функции записи, каждая из которых работает с одним значением, а затем пусть новая библиотека Streams в Java 8 позаботится об агрегировании для нас.
Ключевая функция для извлечения здесь из кода, описанного в комментарии «сделать еще несколько магии», которая преобразует List<String>
в List<Item>
. Мы хотим, чтобы извлечь вычисление, которое преобразует одинString
в Item
, как это:
class Item {
static Item fromString(String s) {
// do a little bit of magic
}
}
После того как вы это, то вы можете позволить Ручьи и NIO библиотеки делать кучу работы для вас:
public List<Item> processFile(String fileName) {
try (Stream<String> lines = Files.lines(Paths.get(fileName))) {
return lines.map(Item::fromString)
.collect(Collectors.toList());
} catch (IOException ioe) {
ioe.printStackTrace();
return Collections.emptyList();
}
}
(Обратите внимание, что более половины этого короткого метода предназначено для работы с IOException
.)
Теперь, если вы хотите выполнить некоторые модульные испытания, то, что вам действительно нужно проверить, - это немного магии. Таким образом, вы заверните его в другой поток трубопровода, как это:
void testItemCreation() {
List<Item> result =
Arrays.asList("first", "second", "third")
.stream()
.map(Item::fromString)
.collect(Collectors.toList());
// make assertions over result
}
(На самом деле, даже это не совсем верно Вы хотели бы писать тесты для преобразования одну строку в одну Item
Но.. может быть, у вас есть какие-то тестовые данные где-то, так что вы хотите преобразовать его в список элементов этого пути, а затем сделать глобальные утверждения по взаимосвязи результирующих элементов в списке.)
Я бродила довольно далеко от вашего первоначального вопроса о том, как разбить лямбду. Пожалуйста, прости меня за то, что я потворствую себе.
Лямбда в оригинальном примере довольно неудачная, так как библиотеки ввода-вывода Java довольно громоздки, и в библиотеке NIO есть новые API-интерфейсы, которые превращают этот пример в однострочный.
По-прежнему урок состоит в том, что вместо составления функций, которые обрабатывают агрегаты, составляются функции, которые обрабатывают отдельные значения, и позволяют потокам обрабатывать агрегацию. Таким образом, вместо тестирования путем издевательства над битами сложной лямбды, вы можете протестировать, подключив потоковые конвейеры по-разному.
lambda может загружать контекст, как и все остальное (если вы хотите его заглушить). но будьте осторожны, что лямбда без захвата переменных, скорее всего, будет синглом. – aepurniet