【问题标题】:Convert ArrayList<String> to Set<ScopeItem> with Java streams使用 Java 流将 ArrayList<String> 转换为 Set<ScopeItem>
【发布时间】:2018-01-29 18:37:50
【问题描述】:

我想使用 Java 流ArrayList&lt;String&gt; 转换为 Set&lt;ScopeItem&gt;

ScopeItem is a enum;
items is an ArrayList<String>;

Set<ScopeItem> scopeItems = items.stream()
                    .map(scopeString -> ScopeItem.valueOf(scopeString))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());

对于不在枚举中的字符串,会抛出以下内容:

java.lang.IllegalArgumentException: No enum const...

理想情况下,我想跳过任何不匹配的字符串。

我想可能是使用平面地图?有什么想法吗?

【问题讨论】:

  • 您的列表是否可以包含与您的枚举不完全匹配的字符串,它们之间的增量是多少?
  • @Makoto 是的,这就是问题所在。我希望它能够处理不匹配的字符串(通过简单地忽略它们)。我不确定您所说的它们之间的增量是什么意思?它应该完全匹配,区分大小写等
  • 假设你可以改变ScopeItem的实现,我建议adding a contains() method to your enumeration
  • 我的意思是你有一个不完整的错误信息。如果你得到一个枚举值“foobasdfkj1236798723”,而你显然不支持它,这就引出了这些值首先来自哪里的问题。
  • 另外,根据您的ScopeItems 的用途,您实际上可能希望它尽快“炸毁”,因为默默地忽略值可能是真正微妙错误的根源。所以,也许只是将默认异常转换为更有用的信息。

标签: java arraylist


【解决方案1】:

您可以将以下方法添加到您的ScopeItem

public static ScopeItem valueOfOrNull(String name) {
    try {
        return valueOf(name);
    } catch (IllegalArgumentException e) {
        // no such element
        return null;
    }
}

并使用它来映射您的枚举值:

.map(scopeString -> ScopeItem.valueOfOrNull(scopeString))

非空值(您已经拥有)上的后续.filter() 将过滤掉那些对应于不匹配字符串的空值。

【讨论】:

  • 我想得越多,就越不喜欢这里有例外作为条件。
【解决方案2】:

您可以在 map 中放置一个 try-catch 以返回 null 而不是抛出异常:

Set<ScopeItem> scopeItems = items.stream()
    .map(scopeString ->
        {
           try
           {
              return ScopeItem.valueOf(scopeString);
           }
           catch (IllegalArgumentException e)
           {
              return null;
           }
        })
    .filter(Objects::nonNull)
    .collect(Collectors.toSet());

您也可以预先使用filter 来检查值数组是否包含您要查找的字符串:

Set<ScopeItem> scopeItems = items.stream()
    .filter(scopeString -> Arrays.stream(ScopeItem.values())
                               .anyMatch(scopeItem -> scopeItem.name().equals(scopeString)))
    .map(ScopeItem::valueOf)
    .collect(Collectors.toSet());

【讨论】:

    【解决方案3】:

    与其他人不同,我不建议使用异常,因为我认为它们应该用于异常情况,而不是用于可能发生的事情。一个简单的解决方案是拥有一组可接受的静态字符串,然后简单地检查您想要使用valueOf 的字符串是否在所述集合中。

    package test;
    
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    public class Test {
    
        public static enum ScopeItem {
            ScopeA,
            ScopeB;
    
            private static Set<String> castableStrings;
    
            static {
                castableStrings = new HashSet<>();
                for (ScopeItem i : ScopeItem.values()) {
                    castableStrings.add(i.name());
                }
            }
    
            public static boolean acceptable(String s) {
                return castableStrings.contains(s);
            }
        }
    
        public static void main(String[] args) {
    
           List<String> items = Arrays.asList("ScopeA", "RandomString", "ScopeB");
           Set<ScopeItem> scopeItems = items.stream()
                   .filter(ScopeItem::acceptable)
                   .map(ScopeItem::valueOf)
                   .collect(Collectors.toSet());
    
           System.out.println(scopeItems.size());
        }
    
    }
    

    【讨论】:

    • +1 用于提及异常应该用于异常行为。当有简单的替代方案时,不应将异常用作条件流(这个答案,还有其他几个)。
    • @Makoto 真的,为什么人们还要为 getter/setter 烦恼,如果这比真正需要的公共类字段要多。
    • @CoderinoJavarino 这种比较有点像苹果和橘子。在这种情况下,样板代码是一个静态查找集(以效率的名义)。 Getter 和 setter 样板代码可以防止未来出现根本性的危险情况(例如,同步对变量的访问,这些变量在以后的开发中会被多个线程访问)。效率可能是一个因素(在这种情况下,作者应该考虑您的解决方案),但保护访问应该始终成为一个问题。
    【解决方案4】:

    这里有一个更优雅的方法。您不必在枚举中添加任何新字段;您也可以简单地针对 it 运行一个流,并确定您的集合中是否有任何匹配项。

    下面的代码假定一个枚举声明:

    enum F {
        A, B, C, D, E
    }
    

    看起来像这样:

    List<String> bad = Arrays.asList("A", "a", "B", "b", "C", "c");
    
    final Set<F> collect = bad.stream()
                                    .filter(e -> Arrays.stream(F.values())
                                                         .map(F::name)
                                                         .anyMatch(m -> Objects.equals(e, m)))
                                    .map(F::valueOf)
                                    .collect(Collectors.toSet());
    

    这里要注意两部分:

    • 我们对枚举值进行内部过滤,并通过F::name 将其映射到字符串。
    • 我们以 null 安全的方式确定我们的基本集合的元素与我们的枚举元素是否存在任何匹配(Objects.equals 在此处使用空值执行 The Right Thing™)

    【讨论】:

      【解决方案5】:

      您可以使用Apache Common's commons-lang3 EnumUtils.getEnum() 代替valueOf()。如果没有匹配的枚举条目,则返回 null(然后您可以完全按照代码中的方式过滤)。

      【讨论】:

        【解决方案6】:

        最简单和最清晰的方法是在 items.contains() 上过滤 Enum 的 values() 方法:

        Set<ScopeItem> enumVals = Arrays.stream(ScopeItem.values())
                .filter(e -> items.contains(e.name()))
                .collect(Collectors.toSet());
        

        没有添加任何功能只是为了让流做你想做的事,而且一目了然。

        【讨论】:

        • 问题是,您将遍历整个items 列表以查找每个缺失的枚举值,这意味着根据items 的大小,此解决方案可能比其他解决方案慢几倍。
        • @CoderinoJavarino Enum.values()items 都由数组支持。如何迭代一个并检查另一个是否包含比反向慢的每个条目?在任一方向上都是O(n * m),其中nm 是各自数组的大小。
        • 另外请注意,如果效率是一个问题,异常处理相对于数组访问/迭代非常慢,并且此解决方案可能会优于此处列出的大多数其他解决方案仅凭这一点。
        • 嗯,没错,我想我写的太快了。我想可以提出一个支持迭代项目的论点,即您可以为枚举创建一个恒定的时间查找(例如我的答案),因为枚举保持不变,而每个代码 sn-p 执行的项目可能不同。你对例外的看法是对的,而不是它的粉丝。
        猜你喜欢
        • 2012-07-22
        • 1970-01-01
        • 2021-06-22
        • 2012-08-31
        • 1970-01-01
        • 1970-01-01
        • 2021-04-26
        • 2011-08-22
        • 2021-07-25
        相关资源
        最近更新 更多