【问题标题】:How to set a value to variable based on multiple conditions using Java Streams API?如何使用 Java Streams API 根据多个条件为变量设置值?
【发布时间】:2022-03-05 08:02:30
【问题描述】:

我无法用 Java Streams 编写以下条件。假设我有一个元素周期表中的元素列表。我必须编写一个方法,通过检查列表是否包含硅或镭或两者来返回字符串。如果它只有硅,方法必须返回Silicon。如果它只有镭,方法必须返回Radium。如果两者都有,方法必须返回Both。如果它们都不可用,则方法返回""(默认值)。

目前,我写的代码如下。

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 实现相同目标的更好方法的建议。

【问题讨论】:

  • 元素 { 字符串名称; int atomicNumber; }

标签: java if-statement java-stream


【解决方案1】:

这可以通过 Stream IPA单个语句 中完成,而无需多行 lambdas嵌套条件不纯函数 改变 lambda 之外的状态。

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

除了空字符串之外的所有返回值都可以通过调用从java.lang.Enum派生的方法name()获得。并且只是为了用空字符串解决这个问题,我添加了getName() 方法。

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

流管道的逻辑如下:

  • 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" 开头。您可以轻松打破循环并返回 "BOTH"
  • 必须避免流中的有状态操作according to the documentation,还要了解为什么javadoc 会警告有状态流,您可以查看this question。如果你想玩 AtomicReference 完全没问题,请记住,这种方法不被认为是好的做法。

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

【讨论】:

    【解决方案2】:

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

    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;
    

    【讨论】:

    • 除了使用if-else 构造之外,还有其他方法吗?
    • 什么意思?您自己的问题是使用 if-else。我没有看到任何其他方法来检查一个字符串或另一个
    • @VivekTS:不是,不是。当然没有什么比这更有效了。
    • @LouisWasserman 也许有一种方法可以使用 XOR 和元素周期表的位图?
    • 我会尝试在 Map&lt;Set&lt;String&gt;, String&gt; 中查找。
    【解决方案3】:

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

    
    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(" ");
            }
        }
        
    }
    
    

    【讨论】:

      【解决方案4】:

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

      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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-01
        • 1970-01-01
        • 2020-07-08
        • 2014-10-06
        • 1970-01-01
        • 1970-01-01
        • 2021-12-16
        • 1970-01-01
        相关资源
        最近更新 更多