Java 8 个流和地图值得吗?

Java 8 streams and maps worth it?

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

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

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

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

但我不确定我是否更喜欢它。与普通样式 java 中的 239 个字符相比,它有 236 个字符。它似乎没有或多或少的可读性。很高兴您不必声明 ArrayList,但需要调用 .collect(Collectors.toList())Arrays.asList(取决于数据类型)也好不到哪儿去。

像这样使用 .stream() 是否有一些我不明白的实际改进,或者这只是一种让不懂函数式编程的同事陷入循环的有趣方式?

我想如果我动态地传递过滤器或映射 lambda 会很有用,但如果你不需要那样做...

你可以改写(不一定更好)

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

使用静态导入看起来像这样

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

It doesn't seem more or less readable.

恕我直言,这种情况经常发生。但是,我会说在 >10% 的情况下它明显更好。与任何新功能一样,您可能会在一开始就过度使用它,直到您熟悉它并发现您使用它的次数让您觉得舒服为止。

Is there some practical improvement to using .stream() like this that I just don't get, or is this just a fun way to throw any coworkers for a loop who don't know functional programming?

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

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

Streams API 在您以前不会费心实施的结构中很有用。

例如假设您想按名称索引该字段。

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

过去您可能使用 List 而不是 Map,但是通过简化 Map 的组装,您可能会使用您真正应该更频繁使用的数据结构。

现在让我们看看如果我们使用更多线程是否会更快。

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

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

如果您专门将用例限制为您发布的内容,那么基于 Stream 的惯用语并不会好得多。但是,如果您有兴趣了解 Streams API 的真正优势在哪里,请参阅以下几点:

  • 基于 Stream 的惯用语可以并行化,您几乎不需要付出任何努力(这实际上是 Java 首先获得 lambda 的最强有力的原因);
  • 流是可组合的:您可以传递它们并添加流水线阶段。这可以大大有利于代码重用;
  • 正如您已经注意到的,您还可以传递 lambda:编写模板方法很容易,您只需插入处理的一个方面;
  • 一旦您习惯了这个习语,FP 代码实际上更具可读性,因为它与 what 而不是 how[=24 的关系更密切=].这种优势随着处理逻辑的复杂性而增加。

我还要指出,可读性方面的差异与其说是惯用语所固有的,不如说是一种历史产物:如果开发人员从一开始就学习 FP 并在日常工作中使用它,那么这将是势在必行的惯用语这很奇怪,很难理解。

如果您坚持要返回集合,那不值得。但是,您错过了一个机会 - 考虑以下内容,您应该会看到使用流在何处为您的代码增加了一定程度的灵活性和可组合性:

private static final Predicate<Field> isStatic
        = field -> !Modifier.isStatic(field.getModifiers());

private Stream<Field> getDeclaredFields(Class<?> model) {
    return Stream.of(model.getDeclaredFields());
}

private Stream<Field> getStaticFields(Class<?> model) {
    return getDeclaredFields(model).filter(isStatic);
}

private Stream<DartField> getDartFields(Class<?> model) {
    return getStaticFields(model)
            .map(field -> DartField.getDartField(field));
}

重点是您可以使用流 作为 集合而不是机制来构建新集合。

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

问题是您没有始终如一地使用 Stream API 。您将用例限制为可以最好地描述为“实际上不使用 Stream API”的东西,因为您坚持 returning Collection。这尤其荒谬,因为它是一个 private 方法,因此您也完全能够调整调用者。

考虑将方法改为

private Stream<DartField> 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 return 上还没有执行任何操作,如果您使用 findFirst 这样的操作,无需处理所有元素。如果 return 包含所有元素的 Collection,您将失去此功能。

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

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

private List<DartField> 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

在 Java 8 中,团队采用了一种面向对象的编程语言,并应用 "Objectification" 来生成面向功能对象的编程(哈哈...FOOP)。适应这一点需要一些时间,但我认为 任何和所有 分层对象操作应保持其 功能状态 。从这个角度来看,Java 感觉像是弥合了 PHP 的鸿沟;让数据以其自然状态存在,并将其塑造到应用程序 GUI 中。

从软件工程的角度来看,这是 API 创作背后的真正哲学。

这是 StreamEx

的一个较短的解决方案
StreamEx.of(model.getDeclaredFields())
        .filter(field -> !Modifier.isStatic(field.getModifiers()))
        .map(DartField::getDartField)
        .toList();

我认为是 shorter/simpler,与原来的 for 循环相比。

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

更重要的是更灵活。想想如果你想做更多的filter/map,或者sort/limit/groupBy/...,你只需要添加更多的流API调用,代码仍然保持简洁,嵌套的for loop/if else会越来越复杂

在我看来,java stream APIs(map, filter, forEach, groupBy...) 实际上在日常开发过程中方便了数据处理。您无需亲自动手,只需告诉流 API 您想要什么而不是如何做。

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