我想说的规则是,如果一个lambda表达式是如此复杂,你觉得有必要模拟出它的位,这可能是太复杂了。它应该被分解成更小的部分组合在一起,或者模型需要进行调整以使其更易于合成。
我会说,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);
}
一对夫妇的意见这一点,虽然。首先,由于各种lambda投掷讨厌IOExceptions
,并且Function
类型没有被声明为抛出该异常,所以这不起作用。第二个是你必须传递给这个函数的lambdas是可怕的。尽管这是行不通的(因为检查的例外),但我把它写出来了:
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;
});
}
呃!我想我已经发现了新的代码味道:
如果你必须在lambda中编写for-loop或while循环,那么你做错了什么。
这里有一些事情正在进行。首先,I/O库实际上由紧密耦合的不同实现部分(InputStream
,Reader
,BufferedReader
)组成。试图将它们分开是没有用的。事实上,图书馆已经发展,因此有一些便利工具(例如NIO Files.readAllLines
)能够为您处理一大堆腿部工作。
更重要的一点是设计函数在它们之间传递值的集合(列表)并构造这些函数实际上是错误的方法。它导致每个函数都必须在它内部编写一个循环。我们真正想要做的是编写函数,每个函数都在一个值上运行,然后让Java 8中的新Streams库负责我们的聚合。
从评论“做更多的魔法”所描述的代码中提取的关键功能是将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
不过。也许你有一些测试数据的地方,所以你将它转换为这样的项目列表,然后在该列表中的结果条目的关系,使全球的断言。)
我徘徊这与您如何拆分lambda的原始问题相去甚远。请原谅我放纵自己。
原始示例中的lambda非常不幸,因为Java I/O库非常麻烦,并且NIO库中有新的API将示例转换为单行内容。
但是,这里的教训是,不是编写处理聚合的函数,而是编写处理各个值的函数,并让流处理聚合。通过这种方式,您可以通过以不同方式将流管线插入到一起来测试,而不是通过模拟复杂lambda的位来进行测试。
一个lambda可以像其他任何东西一样加载上下文(如果你正在寻找它的内部存根)。但要注意,没有捕获变量的lambda最有可能是单身。 – aepurniet