如何使用 Java Streams API 根据多个条件将值设置为变量?

How to set a value to variable based on multiple conditions using Java Streams API?

我无法全神贯注地使用 Java Streams 编写以下条件。假设我有一个来自周期 table 的元素列表。我必须通过检查列表是否包含 Silicon 或 Radium 或两者来编写 returns 字符串的方法。如果它只有硅,则方法必须 return Silicon。如果它只有 Radium,则方法必须 return Radium。如果两者都有,则方法必须 return Both。如果其中 none 个可用,方法 returns ""(默认值)。

目前我写的代码如下。

String resolve(List<Element> elements) {
    AtomicReference<String> value = new AtomicReference<>("");
    elements.stream()
            .map(Element::getName)
            .forEach(name -> {
                if (name.equalsIgnoreCase("RADIUM")) {
                    if (value.get().equals("")) {
                        value.set("RADIUM");
                    } else {
                        value.set("BOTH");
                    }
                } else if (name.equalsIgnoreCase("SILICON")) {
                    if (value.get().equals("")) {
                        value.set("SILICON");
                    } else {
                        value.set("BOTH");
                    }
                }
            });
    return value.get();
}

我知道代码看起来更混乱,看起来比功能更命令。但我不知道如何使用流以更好的方式编写它。我还考虑过多次浏览列表以过滤元素 SiliconRadium 并基于此进行最终确定的可能性。但是两次遍历列表似乎效率不高。

注意:我也明白这可以用命令式的方式编写,而不是让流和原子变量复杂化。我只想知道如何使用流编写相同的逻辑。

请分享您关于使用 Java Streams 实现相同目标的更好方法的建议。

我在过滤器中使用谓词来处理镭和硅,并使用结果集打印结果。


import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class Test {

    public static void main(String[] args) {
        List<Element> elementss = new ArrayList<>();
        Set<String> stringSet = elementss.stream().map(e -> e.getName())
            .filter(string -> (string.equals("Radium") || string.equals("Silicon")))
            .collect(Collectors.toSet());
        if(stringSet.size()==2){
            System.out.println("both");
        }else if(stringSet.size()==1){
            System.out.println(stringSet);
        }else{
            System.out.println(" ");
        }
    }
    
}

将字符串收集到唯一的集合中。然后在恒定时间内检查收容。

Set<String> names = elements.stream().map(Element::getName).map(String::toLowerCase).collect(toSet());
boolean hasSilicon = names.contains("silicon");
boolean hasRadium = names.contains("radium");
String result = ""; 
if (hasSilicon && hasRadium) {
  result = "BOTH";
} else if (hasSilicon) {
  result = "SILICON";
} else if (hasRadium) {
  result = "RADIUM";
}
return result;

如果您使用正则表达式,您可以节省几行,但我怀疑它是否比其他答案更好:

String resolve(List<Element> elements) {
    String result = elements.stream()
                            .map(Element::getName)
                            .map(String::toUpperCase)
                            .filter(str -> str.matches("RADIUM|SILICON"))
                            .sorted()
                            .collect(Collectors.joining());

    return result.matches("RADIUMSILICON") ? "BOTH" : result;
}

可以在 单语句 中使用 Stream IPA 而无需 多行 lambdas嵌套条件不纯函数,在 lambda 之外改变状态。

我的方法是引入一个 enum,其中的元素对应于所有可能的结果,其常量为 EMPTYSILICONRADIUMBOTH.

除空字符串外的所有return值都可以通过调用派生自java.lang.Enum的方法name()获得。只是为了避免空字符串的情况,我添加了 getName() 方法。

请注意,由于 Java 16 枚举可以在方法内本地声明。

流管道的逻辑如下:

  • stream elements 变成一个 string;
  • 的流
  • 被过滤并转换为 枚举常量流 ;
  • 减少枚举成员;
  • 可选的枚举变成可选的字符串。

实现可以如下所示:

public static String resolve(List<Element> elements) {
    return elements.stream()
            .map(Element::getName)
            .map(String::toUpperCase)
            .filter(str -> str.equals("SILICON") || str.equals("RADIUM"))
            .map(Elements::valueOf)
            .reduce((result, next) -> result == Elements.BOTH || result != next ? Elements.BOTH : next)
            .map(Elements::getName)
            .orElse("");
}

枚举

enum Elements {EMPTY, SILICON, RADIUM, BOTH;
    String getName() {
        return this == EMPTY ? "" : name(); // note name() declared in the java.lang.Enum as final and can't be overridden
    }
}

主要

public static void main(String[] args) {
    System.out.println(resolve(List.of(new Element("Silicon"), new Element("Lithium"))));
    System.out.println(resolve(List.of(new Element("Silicon"), new Element("Radium"))));
    System.out.println(resolve(List.of(new Element("Ferrum"), new Element("Oxygen"), new Element("Aurum")))
                .isEmpty() + " - no target elements"); // output is an empty string
}

输出

SILICON
BOTH
true - no target elements

注:

  • 尽管使用流,您可以在 O(n) 时间内生成结果,但迭代方法可能更适合此任务。这样想:如果列表中有一个 10.000 元素的列表,并且它以 "SILICON""RADIUM" 开头。您可以轻松打破循环并 return "BOTH".
  • 必须避免流中的有状态 操作according to the documentation, also to understand why javadoc warns against stateful streams you might take a look at 。如果您想使用 AtomicReference 完全没问题,请记住,这种方法不被认为是好的做法。

我想如果我用流实现了这样一个方法,整体逻辑将与上面相同,但不使用 enum。由于只需要一个对象,它是一个 reduction,所以我将在字符串流上应用 reduce(),将具有所有条件的归约逻辑提取到 单独的方法。通常,lambdas 必须是 well-readable one-liners.