Java 8流和地图值得吗?

感觉就像java 8流和映射函数那么冗长,它们并不是真正的改进。 例如,我编写了一些使用集合生成另一个修改过的集合的代码:

private List getDartFields(Class model) { List fields = new ArrayList(); for (Field field : model.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { fields.add(DartField.getDartField(field)); } } return fields; } 

这似乎是java 8流及其function的理想用例,所以我重写了它:

 private List getDartFields(Class model) { return Arrays.asList(model.getDeclaredFields()) .stream() .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> DartField.getDartField(field)) .collect(Collectors.toList()); } 

但我不确定我更喜欢那个。 这是236个字符,而普通风格的java则是239个字符。 它似乎没有或多或少可读。 很高兴您不必声明ArrayList ,但需要调用.collect(Collectors.toList())Arrays.asList (取决于数据类型)并不是更好。

使用像我这样的.stream()是否有一些实际的改进,或者这只是一种有趣的方式来抛弃任何不知道函数式编程的循环的同事?

我想如果我动态地传递filter或映射lambdas它会很有用,但如果你不需要这样做……

问题是您没有始终如一地使用Stream API。 您将用例限制为可以最好地描述为“实际上不使用Stream API”的内容,因为您坚持要返回Collection 。 这是非常荒谬的,因为它是一种private方法,所以你完全能够适应呼叫者。

考虑将方法更改为

 private Stream getDartFields(Class model) { return Stream.of(model.getDeclaredFields()) .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> DartField.getDartField(field)); } 

并查看调用者实际想要做什么。 通常他们本身不需要Collection作为目的,但是想要执行一个动作甚至更多可以链接的操作,例如打印它们:

 getDartFields(Foo.class).forEach(System.out::println); 

最有趣的特性是流的惰性,这意味着在getDartFields返回时,尚未执行任何操作,如果使用findFirst操作,则无需处理所有元素。 如果返回包含所有元素的Collection ,则会丢失此function。

这也适用于多步处理,其中处理普通列表意味着对于每个步骤,必须创建新列表并用结果填充。

你可以用不同的方式写出来(不一定更好)

 private List getDartFields(Class model) { return Stream.of(model.getDeclaredFields()) .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(DartField::getDartField) .collect(Collectors.toList()); } 

使用静态导入看起来像

 private static List getDartFields(Class model) { return of(model.getDeclaredFields()) .filter(field -> !isStatic(field.getModifiers())) .map(DartField::getDartField) .collect(toList()); } 

它似乎没有或多或少可读。

这通常是恕我直言。 但是,我会说,在> 10%的情况下,它明显更好。 与任何新function一样,您可能会过度使用它,直到您熟悉它并发现您使用它感觉舒适。

使用像我这样的.stream()是否有一些实际的改进,或者这只是一种有趣的方式来抛弃任何不知道函数式编程的循环的同事?

我怀疑两者。 如果您不了解函数式编程,则它往往是只读代码。 即你仍然可以理解它的作用,问题是如果你必须维护它。

恕我直言,值得鼓励开发人员学习函数式编程,因为它有一些关于如何构造代码的非常有用的想法,即使你没有使用FP语法,你也会从中受益。

如果Streams API在构造中很有用,那么您以前就不会有任何实现。

例如,假设您要按名称索引字段。

 private static Map getDartFields(Class model) { return of(model.getDeclaredFields()) .filter(field -> !isStatic(field.getModifiers())) .map(DartField::getDartField) .collect(groupingBy(f -> f.getName())); } 

在过去,您可能使用了List而不是Map,但通过使Map的组装更容易,您可能会使用您应该更频繁使用的数据结构。

现在让我们看看如果我们使用更multithreading会更快。

 private static Map getDartFields(Class model) { return of(model.getDeclaredFields()).parallel() .filter(field -> !isStatic(field.getModifiers())) .map(DartField::getDartField) .collect(groupingByConcurrent(f -> f.getName())); } 

看看它有多难,当你发现它可能弊大于利时,将它改回来也很容易。

Java 8流特别冗长,大部分是由于转换为流然后又转换回另一个结构。 在FunctionalJava中,等价物是:

 private List getDartFields(Class model) { return List.list(model.getDeclaredFields()) .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> DartField.getDartField(field)) .toJavaList(); } 

我警告不要只计算字符作为复杂性的衡量标准。 这几乎不重要。

函数式编程允许您使用简单的替换模型来推理代码,而不必遍历整个程序。 这使您的程序更容易预测和更容易,因为您一次只需要更少的信息。

我也警告不要回流。 流不是可任意组合的,流是可变数据,其中呼叫者无法知道是否已在流上调用终端操作。 这意味着我们需要知道程序的状态以推断正在发生的事情。 流被引入以帮助消除可变状态,但是使用可变状态实现 – 远非理想状态。

如果你想要一个不可变的流,我推荐使用Functional Java的流, https://functionaljava.ci.cloudbees.com/job/master/javadoc/fj/data/Stream.html 。

如果您专门将您的用例限制为您发布的内容,那么基于流的习语就不会更好。 但是,如果您有兴趣了解Streams API的真正优势,请参阅以下几点:

  • 基于流的习语可以并行化,而不需要你的努力(这实际上是Java首先获得lambda的最强大原因);
  • 流是可组合的:您可以传递它们并添加管道阶段。 这可以极大地有益于代码重用;
  • 正如您已经指出的那样,您也可以传递lambdas:在插入处理的一个方面时,很容易编写模板方法;
  • 一旦你对这个成语感到满意,FP代码实际上更具可读性,因为它更接近于什么而不是如何 。 该优点随着处理逻辑的复杂性而增加。

我还要注意,可读性的差异更多的是历史人工制品,而不是成语本身的内在因素:如果开发人员从一开始就教FP并且日复一日地工作,那么这将是一个奇怪而艰难的命令式成语跟随。

如果你坚持回到collections品,那就不值得了。 但是,您错过了一个机会 – 请考虑以下因素,您应该看到使用流的位置为您的代码增加了一定程度的灵活性和可组合性:

 private static final Predicate isStatic = field -> !Modifier.isStatic(field.getModifiers()); private Stream getDeclaredFields(Class model) { return Stream.of(model.getDeclaredFields()); } private Stream getStaticFields(Class model) { return getDeclaredFields(model).filter(isStatic); } private Stream getDartFields(Class model) { return getStaticFields(model) .map(field -> DartField.getDartField(field)); } 

关键是您可以将流用作集合而不是机制来构建新集合。

通过允许所有自然方法脱离算法,您最终会得到明显明显的代码,这些代码几乎不可避免地可重复使用,并且每个组件自然会做到一件事。

在Java 8中,团队采用了面向对象的编程语言,并应用“Objectification”来生成面向function对象的编程(lol … FOOP)。 需要一些时间来习惯这一点,但我认为任何和所有分层对象操作都应保持其function状态 。 从这个角度来看,Java感觉它弥合了PHP差距; 允许数据以其自然状态存在,并将其转化为应用程序GUI。

从软件工程的角度来看,这是API创建背后的真正理念。

这是StreamEx的一个较短的解决方案

 StreamEx.of(model.getDeclaredFields()) .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(DartField::getDartField) .toList(); 

与原来的for循环相比,我认为它更短/更简单。

 List fields = new ArrayList<>(); for (Field field : model.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { fields.add(DartField.getDartField(field)); } } return fields; 

更重要的是更灵活。 Jut想想如果你想做更多的filter/ map,或者sort / limit / groupBy / …,你只需要添加更多的流API调用,而且代码仍然保持简洁,嵌套for循环/ if else会变得更多和更复杂。

从我的角度来看,java流API(map,filter,forEach,groupBy …)实际上是在日常开发过程中进行数据处理。 您只需告诉流API您不想要的方法,而不是亲自动手。

但是,在阅读使用各种相关流API填充的java代码时,我感到很不舒服。 有时,在代码格式和布局中使用流API时,它非常有线,特别是与function程序一起使用。 很快,它降低了可读性。