Java-8流表达式将多个枚举值“或”组合在一起

Java-8流表达式将多个枚举值“或”组合在一起,java-8,java-stream,Java 8,Java Stream,我正在聚合一组不同于foreach循环中序数值的枚举值 int output = 0; for (TestEnum testEnum: setOfEnums) { output |= testEnum.getValue(); } 在streams API中有这样做的方法吗 如果我在流中使用这样的lambda: 我得到一个编译时错误,它说,“lambda中使用的变量应该是有效的final”。您需要这样减少枚举流: int output = Arrays.stre

我正在聚合一组不同于foreach循环中序数值的枚举值

   int output = 0;
   for (TestEnum testEnum: setOfEnums) {
        output |= testEnum.getValue();
   }
在streams API中有这样做的方法吗

如果我在流中使用这样的lambda:

我得到一个编译时错误,它说,“lambda中使用的变量应该是有效的final”。

您需要这样减少枚举流:

int output = Arrays.stream(TestEnum.values()).mapToInt(TestEnum::getValue).reduce(0, (acc, value) -> acc | value);
您需要这样减少枚举流:

int output = Arrays.stream(TestEnum.values()).mapToInt(TestEnum::getValue).reduce(0, (acc, value) -> acc | value);

谓词表示一个布尔值函数,您需要使用流的reduce方法来聚合一组枚举值

如果我们认为HASSET被命名为SetOfEnums:

    //int initialValue = 0; //this is effectively final for next stream pipeline if you wont modify this value in that stream
    final int initialValue = 0;//final
    int output = SetOfEnums.stream()
                     .map(TestEnum::getValue)
                     .reduce(initialValue, (e1,e2)-> e1|e2);

谓词表示一个布尔值函数,您需要使用流的reduce方法来聚合一组枚举值

如果我们认为HASSET被命名为SetOfEnums:

    //int initialValue = 0; //this is effectively final for next stream pipeline if you wont modify this value in that stream
    final int initialValue = 0;//final
    int output = SetOfEnums.stream()
                     .map(TestEnum::getValue)
                     .reduce(initialValue, (e1,e2)-> e1|e2);

我喜欢使用减少的建议,但也许一个更完整的答案可以说明为什么这是一个好主意

在lambda表达式中,可以引用lambda表达式定义范围内的变量(如输出),但不能修改值。原因是,在内部,编译器必须能够实现lambda(如果它选择这样做的话),方法是创建一个以lambda为主体的新函数。编译器可以根据需要选择添加参数,以便在参数列表中可以使用此生成函数中使用的所有值。在您的例子中,这样的函数肯定会有lambda的显式参数testEnum,但是因为您也引用lambda主体中的局部变量输出,所以它可以将其作为第二个参数添加到生成的函数中。实际上,编译器可能会从lambda生成此函数:

private void generatedFunction1(TestEnum testEnum, int output) {
    output |= testEnum.getValue();
}
如您所见,输出参数是调用者使用的输出变量的副本,OR操作将仅应用于副本。由于原始输出变量不会被修改,语言设计者决定禁止修改隐式传递给lambdas的值

为了以最直接的方式解决这个问题,暂且不考虑使用reduce是一种更好的方法,您可以将输出变量包装在包装器中,例如大小为1的int[]数组或AtomicInteger。包装器的引用将通过值传递给生成的函数,并且由于您现在将更新输出的内容,而不是输出的值,因此输出实际上仍然是最终的,因此编译器不会抱怨。例如:

AtomicInteger output = new AtomicInteger();
setOfEnums.stream().forEach(testEnum -> (output.set(output.get() | testEnum.getValue()));
或者,由于我们使用的是AtomicInteger,我们还可以使其线程安全,以防您以后选择使用并行流

现在,我们已经讨论了一个与您所问的最相似的答案,我们可以讨论使用reduce的更好的解决方案,其他答案已经推荐了这个解决方案

流提供了两种约简,无状态约简和有状态约简。为了想象不同,考虑一个传送带递送汉堡包,你的目标是收集所有汉堡包馅饼到一个大汉堡包。通过有状态的缩减,你可以从一个新的汉堡包子开始,然后在每个汉堡到达时从中取出馅饼,然后将其添加到你设置用来收集它们的汉堡包子中的一堆馅饼中。在无状态还原中,你从一个称为身份的空汉堡面包开始,因为如果传送带是空的,那么这个空汉堡面包就是你最后得到的,当每个汉堡到达传送带时,你复制一份先前累积的汉堡,并从刚到达的新汉堡中添加肉饼,丢弃先前累积的汉堡

无状态的减少可能看起来是一种巨大的浪费,但在某些情况下,复制累积价值是非常便宜的。其中一种情况是在积累基元类型时—基元类型的复制成本非常低,因此在应用程序(如求和、ORing等)中处理基元时,无状态缩减是理想的

因此,使用无状态缩减,您的示例可能会变成:

setOfEnums.stream()
    .mapToInt(TestEnum::getValue)    // or .mapToInt(testEnum -> testEnum.getValue())
    .reduce(0, (resultSoFar, testEnum) -> resultSoFar | testEnum);
需要思考的几点:

您最初的for循环可能比使用streams更快,除非您的集合非常大并且您使用并行流。不要为了使用流而使用流。如果它们有意义,就使用它们。 在我的第一个示例中,我展示了Stream.forEach的用法。如果您发现自己创建了一个流并只调用forEach,那么直接在集合上调用forEach会更有效。 您没有提到您使用的是哪种类型的集合,但我希望您使用的是EnumSet。因为它是作为位字段实现的,所以对于所有操作,甚至是复制,它的O1性能都比任何其他类型的集合要好得多。EnumSet.noneOfTestEnum.class创建一个空集,EnumSet.allOfTestEnum.class提供一组所有枚举值,等等。
我喜欢使用减少的建议,但可能是更完整的answe r将说明为什么这是一个好主意

在lambda表达式中,可以引用lambda表达式定义范围内的变量(如输出),但不能修改值。原因是,在内部,编译器必须能够实现lambda(如果它选择这样做的话),方法是创建一个以lambda为主体的新函数。编译器可以根据需要选择添加参数,以便在参数列表中可以使用此生成函数中使用的所有值。在您的例子中,这样的函数肯定会有lambda的显式参数testEnum,但是因为您也引用lambda主体中的局部变量输出,所以它可以将其作为第二个参数添加到生成的函数中。实际上,编译器可能会从lambda生成此函数:

private void generatedFunction1(TestEnum testEnum, int output) {
    output |= testEnum.getValue();
}
如您所见,输出参数是调用者使用的输出变量的副本,OR操作将仅应用于副本。由于原始输出变量不会被修改,语言设计者决定禁止修改隐式传递给lambdas的值

为了以最直接的方式解决这个问题,暂且不考虑使用reduce是一种更好的方法,您可以将输出变量包装在包装器中,例如大小为1的int[]数组或AtomicInteger。包装器的引用将通过值传递给生成的函数,并且由于您现在将更新输出的内容,而不是输出的值,因此输出实际上仍然是最终的,因此编译器不会抱怨。例如:

AtomicInteger output = new AtomicInteger();
setOfEnums.stream().forEach(testEnum -> (output.set(output.get() | testEnum.getValue()));
或者,由于我们使用的是AtomicInteger,我们还可以使其线程安全,以防您以后选择使用并行流

现在,我们已经讨论了一个与您所问的最相似的答案,我们可以讨论使用reduce的更好的解决方案,其他答案已经推荐了这个解决方案

流提供了两种约简,无状态约简和有状态约简。为了想象不同,考虑一个传送带递送汉堡包,你的目标是收集所有汉堡包馅饼到一个大汉堡包。通过有状态的缩减,你可以从一个新的汉堡包子开始,然后在每个汉堡到达时从中取出馅饼,然后将其添加到你设置用来收集它们的汉堡包子中的一堆馅饼中。在无状态还原中,你从一个称为身份的空汉堡面包开始,因为如果传送带是空的,那么这个空汉堡面包就是你最后得到的,当每个汉堡到达传送带时,你复制一份先前累积的汉堡,并从刚到达的新汉堡中添加肉饼,丢弃先前累积的汉堡

无状态的减少可能看起来是一种巨大的浪费,但在某些情况下,复制累积价值是非常便宜的。其中一种情况是在积累基元类型时—基元类型的复制成本非常低,因此在应用程序(如求和、ORing等)中处理基元时,无状态缩减是理想的

因此,使用无状态缩减,您的示例可能会变成:

setOfEnums.stream()
    .mapToInt(TestEnum::getValue)    // or .mapToInt(testEnum -> testEnum.getValue())
    .reduce(0, (resultSoFar, testEnum) -> resultSoFar | testEnum);
需要思考的几点:

您最初的for循环可能比使用streams更快,除非您的集合非常大并且您使用并行流。不要为了使用流而使用流。如果它们有意义,就使用它们。 在我的第一个示例中,我展示了Stream.forEach的用法。如果您发现自己创建了一个流并只调用forEach,那么直接在集合上调用forEach会更有效。 您没有提到您使用的是哪种类型的集合,但我希望您使用的是EnumSet。因为它是作为位字段实现的,所以对于所有操作,甚至是复制,它的O1性能都比任何其他类型的集合要好得多。EnumSet.noneOfTestEnum.class创建一个空集,EnumSet.allOfTestEnum.class提供一组所有枚举值,等等。
这是一个不可变的归约,但是你正在改变acc是一个int。我认为它应该是acc,value->acc | value,这是一个不可变的归约,但是你正在改变acc是一个int。我认为它应该是acc,value->acc | value,你可以在表单中提供一个标识。reduce0,e1,e2->->| e1;或者不去做一个orElse0@Eugene是的,对于像空SetofEnums这样的情况会更好,否则optional将抛出异常,我将对其进行修改。您可以以以下形式提供标识:reduce0,e1,e2->e1 | e2;或者不去做一个orElse0@Eugene是的,对于像空集合的nums这样的情况会更好,否则optional将抛出异常,我将修改它。