【问题标题】:Is there a collection in standard Java for this specific task?标准 Java 中是否有针对此特定任务的集合?
【发布时间】:2016-01-18 11:34:43
【问题描述】:

所以,我经常遇到这个成语:

import java.util.HashSet;
import java.util.Set;

public class Test {

  private static final Set<String> VALUES = new HashSet<String>();
  static {
    VALUES.add("good");
    VALUES.add("bad");
    VALUES.add("ugly");
  }

  public static void main(String[] args) {
    if (VALUES.contains(args[0])) {
      System.out.println("Thumbs up for Sergio Leone!");
    } else {
      System.out.println("Whatever.");
    }
  }
}

产生一个布尔输出,判断输入值(很可能是字符串)是否匹配任何固定值集合(最可能是字符串常量或字符串字面量)。

我看到了几个不同的实现,例如:

  • 带有或运算符的布尔表达式。

  • 一个值数组 + 一个 foreach 循环遍历匹配的值。

  • 前一个的变体:一个值数组,为 Arrays.binarySearch() 排序。

  • 使用 ArrayList 或 HashSet + contains() 等集合,见上文。

我觉得这里缺乏一个通用的、最佳的实现,因为我经常遇到这种成语。尽管如此,所有上述实现都感觉像是临时解决方案。我想知道是否:

a) 标准 Java 集合框架已经为此类任务提供了解决方案。

b) 如果没有,是否已经有合适的第三方实现(例如一些 Apache Commons 库)。

要求(按重要性排序):

  • 最适合几十个元素(相对于复杂的数据结构,例如为快速访问数百万条目而设计的哈希表/树)。

  • 快速实例化,占用内存小。

  • 没有隐晦的第三方依赖,最好建立在纯 Java 之上。

  • 此外,在我的情况下,JRE 1.6 合规性是必须的,尽管我也对基于较新 Java 版本构建的解决方案感到好奇。 (这意味着向我指出基于 JRE 1.8 的解决方案可能会获得支持,但不会获得答案标记,抱歉。)

  • 初始化(实例化?)完成后,它应该是不可变的——我们只需要将它用于类似 contains() 的检查。

  • 如果我们尝试在初始化时冗余地添加值,应该抛出异常。

【问题讨论】:

  • “最有可能”是什么意思? HashSet 有什么问题?
  • 您是否会动态地实例化它们,或者像上面的示例中给出的那样一直使用相同的值静态地实例化它们?如果是后者,那么您可以使用enum
  • @Sleiman Jneidi 最有可能的意思是,它应该针对小尺寸而不是数百万个元素进行优化。这意味着如果有人决定将它用于它不适合的用途,它仍然可以处理数百万个元素,只是与已建立的集合(如 HashSet、TreeSet 等)相比,基于性能考虑,这将是一个糟糕的决定。 ,请参阅 Arrays 中的排序实现,在选择方法时通常会考虑数组长度。
  • @Kevin Esche 按规则进行静态实例化。使用枚举(+ 枚举字段,如有必要)可能是一种解决方案,是的。
  • @Sleiman Jneidi 我确实一直在使用 HashSet :-) 我只是想知道是否有另一种解决方案,也许是更高效和/或更易读和/或更优雅的解决方案。因此问题。

标签: java collections


【解决方案1】:

您的HashSet 解决方案已经在使用standard Java Collections framework 实现,所以我看不出有任何不使用它的理由。如果你想让 Set 在初始化后不可变,你可以添加

VALUES = Collections.unmodifiableSet (VALUES);

初始化完成后。

当然,如果您觉得这样更方便,您可以将所有初始化简化为一行代码:

private static final Set<String> VALUES = 
    Collections.unmodifiableSet(new HashSet<String>(Arrays.asList("good","bad","ugly")));

【讨论】:

  • “最适合几十个元素(相对于复杂的数据结构,例如为快速访问数百万条目而设计的哈希表/树)。”
  • @AttilaCsipak 用于在小集合(几十个元素)中搜索元素,您不必担心性能。担心使您的代码尽可能可读。
【解决方案2】:

在 JDK 9 中可以这样做:

Set<String> VALUES = Set.of("good", "bad", "ugly");

生成的Set 将是不可变的,如果给定重复值,此静态工厂方法将引发异常。

(在撰写本文时,JDK 9 还不是最终版本,可能会发生变化。有关这些新 API 的更多信息,请参阅 JEP 269。)

但您还要求提供 JDK 6 解决方案。我不知道什么是“最佳”解决方案,因为您没有指定应该优化的标准。 Set.contains() 方法似乎对你想要的有正确的语义,所以我会用它来运行。

听起来这种情况经常发生,值得创建一个帮助方法来初始化集合。这是一个接受一个字符串数组并返回一个不可修改的集合,如果有任何重复则抛出异常:

static Set<String> init(String[] a) {
    Set<String> set = new HashSet<String>(Arrays.asList(a));
    if (set.size() != a.length) {
        throw new IllegalArgumentException("duplicate keys");
    }
    return Collections.unmodifiableSet(set);
}

以下是您的使用方法:

private static final String[] VALUE_ARRAY = { "good", "bad", "ugly" };
private static final Set<String> VALUES = init(VALUE_ARRAY);

这里的想法是使用漂亮的数组初始值设定项语法,同时使用辅助方法将数组转换为集合,同时检查重复项。

HashSet 不是最小的数据结构,但是如果不引入其他库,就没有任何明显的替代方案可用。如果您愿意用时间换取空间,您可以创建一个不可修改的List(可能只是包装Arrays.asList 的结果)然后调用contains()。这将提供一个很好的紧凑的基于数组的表示,但它退化为线性而不是恒定时间查找。创建HashSet 仍然是检查重复项的最简单方法,但这只是暂时的。

【讨论】:

    【解决方案3】:

    如果我认为它们是参数的可能“集合”总是相同的,那么您可以使用给定的可能参数创建一个枚举。 这些将在编译时创建,并且也是不可变的。

    public class ContainsTest {
        private enum Container {
            VAL1,
            VAL2,
            VAL3;
        }
    
        public static void main(String[] args) {
            String arg1 = "VAL1";
            String arg2 = "VAL4";
            if(ContainsTest.existsVal(arg1)) {
                System.out.println("Command " + arg1 + " is going to be executed");
            } else {
                System.out.println("Command " + arg1 + " not aviable");
            }
            if(ContainsTest.existsVal(arg2)) {
                System.out.println("Command " + arg2 + " is going to be executed");
            } else {
                System.out.println("Command " + arg2 + " not aviable");
            }
    
        }
    
        private static boolean existsVal(String arg) {
            try {
                // This method does check if an enum with the given arg exists
                Container.valueOf(arg);
                return true;
            } catch(IllegalArgumentException e) {
                // If it does not exist then an exception will occur that has to be caught.
                return false;
            }
        }
    }
    

    【讨论】:

    • 增强existVal() 以接受自定义枚举类作为参数看起来很容易。只覆盖String类型,所以其他回复建议的HashMap方案可能更灵活。
    【解决方案4】:

    如果您正在处理少量原语或枚举,在我看来,您似乎需要 switch 语句,而不是数据结构。见here。话虽如此,在 java 7 之前你不能用 Strings 做到这一点。不过,对于这种模式,这是一个需要牢记的选项。

    让您了解模式:

    int i = 0;
    switch (i) {
          case 1:
          case 2:
          case 3:
              System.out.println("1,2,3");
              break;
          default:
              System.out.println("other");
    }
    

    我认为这为您打勾(尽管与其他海报一样,我不会与 HashSets 分享您的担忧)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-24
      • 1970-01-01
      • 2014-05-02
      • 1970-01-01
      • 2021-06-22
      • 2013-12-17
      • 1970-01-01
      • 2012-12-07
      相关资源
      最近更新 更多