【问题标题】:How to initialize HashSet values by construction?如何通过构造初始化HashSet值?
【发布时间】:2011-01-03 17:57:56
【问题描述】:

我需要创建一个带有初始值的Set

Set<String> h = new HashSet<String>();
h.add("a");
h.add("b");

有没有办法在一行代码中做到这一点?例如,它对 final 静态字段很有用。

【问题讨论】:

  • 对于 Java 8、Java 9 和 Java 10,请查看我的答案 stackoverflow.com/a/37406054/1216775
  • Michael Berdyshev 的回答实际上是最好的,因为它是生成 modifiable 集的最干净的方法。 i_am_zero 答案也有不同的方式来制作可修改的 Set,但它更冗长/笨拙[使用 Lambda 流];否则 i_am_zero 答案是次佳的,因为它的不同选项的广度(跨 Java 版本)。
  • 注意:有些答案会省略`new HashSet(int initialCapacity) 参数,如果您已经知道集合的大小,请使用它。

标签: java collections constructor initialization hashset


【解决方案1】:

集合文字是为 Java 7 安排的,但没有进入。所以还没有自动的。

可以使用guava的Sets:

Sets.newHashSet("a", "b", "c")

或者您可以使用以下语法,这将创建一个匿名类,但它很hacky:

Set<String> h = new HashSet<String>() {{
    add("a");
    add("b");
}};

【讨论】:

  • @DD 这是一个 hack,因为它创建了一个匿名的 HashSet 内部子类,其中包含一个调用 add() 方法的静态初始化部分,这对于这种情况来说绝对是矫枉过正。我喜欢它!
  • 双括号初始化是一种非常危险的技巧。匿名类具有到外部类的隐式链接,这可能导致内存泄漏。持有双括号初始化集合的人也持有创建该集合的类。
  • @fhucho 该方法在(比如)JMock 中设置模拟对象期望值时非常流行,即使考虑到内存泄漏风险(测试是短暂的进程,其内存足迹并不重要)。
【解决方案2】:

有几种方法:

双括号初始化

这是一种创建匿名内部类的技术,该内部类具有实例初始化程序,在创建实例时将Strings 添加到自身:

Set<String> s = new HashSet<String>() {{
    add("a");
    add("b");
}}

请记住,这实际上会在每次使用 HashSet 时创建一个新的子类,即使不必显式编写新的子类。

实用方法

编写一个返回 Set 并使用所需元素进行初始化的方法并不难编写:

public static Set<String> newHashSet(String... strings) {
    HashSet<String> set = new HashSet<String>();

    for (String s : strings) {
        set.add(s);
    }
    return set;
}

上面的代码只允许使用String,但允许使用任何使用泛型的类型应该不会太难。

使用库

许多库都有一个方便的方法来初始化集合对象。

例如,Google Collections 有一个 Sets.newHashSet(T...) 方法,该方法将使用特定类型的元素填充 HashSet

【讨论】:

  • GoogleCollections 也有 ImmutableSet.of(T...)。大多数情况下,您不需要在以后再次更改集合,在这种情况下,使用不可变集合有很多优势。
  • 澄清每次使用都会创建一个新的HashSet子类,意思是“每次编码”。每个执行创建一个新的子类。
  • 如果您不知道自己在做什么,双括号启动可能会很危险,请在使用前查看其含义
【解决方案3】:

你可以在 Java 6 中做到这一点:

Set<String> h = new HashSet<String>(Arrays.asList("a", "b", "c"));

但是为什么呢?我不觉得它比显式添加元素更具可读性。

【讨论】:

  • 我在声明中创建它。这是最终财产。谢谢
  • 啊。可能值得注意的是,您仍然可以将元素添加到最终集合中。只需声明 set final,但像往常一样在构造函数(或其他任何地方)中添加元素。最终的 Collection 仍然可以添加或删除元素。如果是最终的,则仅对 Collection 本身的引用。
  • 对 Jason Nichols 所说的话 +1。不要忘记使用 Collections.unmodifiableSet(...)。
  • 我的意思是,如果是new HashSet&lt;String&gt;("a", "b", "c");,它会更具可读性
【解决方案4】:

我使用的速记不是很省时,但适合一行:

Set<String> h = new HashSet<>(Arrays.asList("a", "b"));

同样,这并不省时,因为您正在构造一个数组、转换为一个列表并使用该列表来创建一个集合。

在初始化静态最终集时,我通常这样写:

public static final String[] SET_VALUES = new String[] { "a", "b" };
public static final Set<String> MY_SET = new HashSet<>(Arrays.asList(SET_VALUES));

稍微不那么丑陋,效率对于静态初始化无关紧要。

【讨论】:

  • 虽然在撰写本文时很可能是正确的,但从 Java 7 开始确实使用 Diamond 运算符,如下所示: Set h = new HashSet(Arrays.asList("a", " b")); public static final Set MY_SET = new HashSet(Arrays.asList(SET_VALUES));
  • @Gennadiy 可以确认菱形表示法工作正常(即使在 Set 声明和 HashSet 分配中也是如此。)
  • @NestorMilyaev,Gennadiy,stream.of(...) 怎么样?使用 java 8 是不是更好的解决方案?
  • 在 Java 9 中:Set&lt;String&gt; h = Set.of("a", "b") //UnmodifiableThis answer is up to date
【解决方案5】:

有点复杂,但适用于 Java 5:

Set<String> h = new HashSet<String>(Arrays.asList(new String[] {  
    "a", "b"
}))

使用辅助方法使其可读:

Set<String> h = asSet ("a", "b");

public Set<String> asSet(String... values) {
    return new HashSet<String>(java.util.Arrays.asList(values));
}

【讨论】:

  • (你不需要new String[] {
【解决方案6】:

coobird's answer's 实用函数的泛化,用于创建新的HashSets:

public static <T> Set<T> newHashSet(T... objs) {
    Set<T> set = new HashSet<T>();
    for (T o : objs) {
        set.add(o);
    }
    return set;
}

【讨论】:

  • 请用大小初始化 + 添加空检查
【解决方案7】:

在 Java 8 中我会使用:

Set<String> set = Stream.of("a", "b").collect(Collectors.toSet());

这给你一个可变的Set,用“a”和“b”预初始化。请注意,虽然在 JDK 8 中这确实返回了 HashSet,但规范并不能保证它,而且这可能会在未来发生变化。如果您特别想要HashSet,请改为:

Set<String> set = Stream.of("a", "b")
                        .collect(Collectors.toCollection(HashSet::new));

【讨论】:

  • 这比原始问题中的普通旧代码更加冗长和不必要的复杂。
  • @Natix 对于 2 个元素,它更冗长,但如果你必须用更多元素(10-100 秒)对集合进行硬编码,这种表示开始变得简洁
  • @Natix 是的,但它是内联的,在某些情况下这是一个优势
  • 为什么不是更紧凑的版本:Stream.of(1, 3, 5).collect(toSet());它使用 import static java.util.stream.Collectors.toSet;
  • 另外,如果你想要一个只有一项的Set,可以创建一个Singletonfinal Set&lt;T&gt; SOME_SET = Collections.singleton(t);
【解决方案8】:

我觉得最易读的就是简单地使用google Guava

Set<String> StringSet = Sets.newHashSet("a", "b", "c");

它是可变的。

【讨论】:

  • 现在是Sets.newHashSet()
  • @membersound 我应该从哪个包导入以使用 Sets.newHashSet() ?
  • @AbdennacerLachiheb "com.google.common.collect.Sets"
  • @membersound 我检查了旧文档直到 guava 2.0,没有 newSet(),我猜 LanceP 引用了其他一些框架,如 gs-collections。所以,按照你说的修改答案。谢谢
【解决方案9】:

这是一个优雅的解决方案:

public static final <T> Set<T> makeSet(@SuppressWarnings("unchecked") T... o) {
        return new HashSet<T>() {
            private static final long serialVersionUID = -3634958843858172518L;
            {
                for (T x : o)
                   add(x);
            }
        };
}

【讨论】:

  • serialVersionUID 在所有情况下都是一个非常糟糕的主意
【解决方案10】:

如果您只有一个值并且想要获得一个 不可变 集,这就足够了:

Set<String> immutableSet = Collections.singleton("a");

【讨论】:

  • 这非常有用,因为如果您使用只有一个值的 new HashSet&lt;&gt;(Arrays.asList( _values_ )) 方法,我们的代码分析工具会报错。
  • 当心,结果 Set 是不可变的。
【解决方案11】:

只是一个小提示,无论您最终使用哪种方法,如果这是通常未修改的默认设置(例如您正在创建的库中的默认设置),最好遵循这个模式:

// Initialize default values with the method you prefer, even in a static block
// It's a good idea to make sure these defaults aren't modifiable
private final static Set<String> DEFAULT_VALUES = Collections.unmodifiableSet(...);
private Set<String> values = DEFAULT_VALUES;

好处取决于您为该类创建的实例数量以及更改默认值的可能性。

如果您决定遵循这种模式,那么您还可以选择最易读的集合初始化方法。由于不同方法之间效率上的微小差异可能并不重要,因为您只需初始化一次集合。

【讨论】:

    【解决方案12】:

    如果 Set 的包含类型是枚举,则有 java 内置工厂方法(从 1.5 开始):

    Set<MY_ENUM> MY_SET = EnumSet.of( MY_ENUM.value1, MY_ENUM.value2, ... );
    

    【讨论】:

      【解决方案13】:

      对于Eclipse Collections,有几种不同的方法可以在一个语句中初始化包含字符“a”和“b”的Set。 Eclipse Collections 包含对象和原始类型的容器,因此我说明了如何使用 Set&lt;String&gt;CharSet 以及两者的可变、不可变、同步和不可修改版本。

      Set<String> set =
          Sets.mutable.with("a", "b");
      HashSet<String> hashSet =
          Sets.mutable.with("a", "b").asLazy().into(new HashSet<String>());
      Set<String> synchronizedSet =
          Sets.mutable.with("a", "b").asSynchronized();
      Set<String> unmodifiableSet =
          Sets.mutable.with("a", "b").asUnmodifiable();
      
      MutableSet<String> mutableSet =
          Sets.mutable.with("a", "b");
      MutableSet<String> synchronizedMutableSet =
          Sets.mutable.with("a", "b").asSynchronized();
      MutableSet<String> unmodifiableMutableSet =
          Sets.mutable.with("a", "b").asUnmodifiable();
      
      ImmutableSet<String> immutableSet =
          Sets.immutable.with("a", "b");
      ImmutableSet<String> immutableSet2 =
          Sets.mutable.with("a", "b").toImmutable();
      
      CharSet charSet =
          CharSets.mutable.with('a', 'b');
      CharSet synchronizedCharSet =
          CharSets.mutable.with('a', 'b').asSynchronized();
      CharSet unmodifiableCharSet =
          CharSets.mutable.with('a', 'b').asUnmodifiable();
      MutableCharSet mutableCharSet =
          CharSets.mutable.with('a', 'b');
      ImmutableCharSet immutableCharSet =
          CharSets.immutable.with('a', 'b');
      ImmutableCharSet immutableCharSet2 =
          CharSets.mutable.with('a', 'b').toImmutable();
      

      注意:我是 Eclipse Collections 的提交者。

      【讨论】:

        【解决方案14】:

        如果您正在寻找最紧凑的初始化集合的方法,那就是Java9

        Set<String> strSet = Set.of("Apple", "Ball", "Cat", "Dog");
        

        ======================下面详细解答======================= ===

        使用 Java 10(不可修改集)

        Set<String> strSet1 = Stream.of("A", "B", "C", "D")
                 .collect(Collectors.toUnmodifiableSet());
        

        这里的收集器实际上会返回 Java 9 中引入的不可修改集合,从源代码中的语句 set -&gt; (Set&lt;T&gt;)Set.of(set.toArray()) 可以看出。

        需要注意的一点Collections.unmodifiableSet() 方法返回指定集合的​​不可修改视图(根据文档)。 不可修改的视图 集合是不可修改的集合,也是支持集合的视图。请注意,对支持集合的更改可能仍然是可能的,如果它们发生,它们通过不可修改的视图可见。但是 Collectors.toUnmodifiableSet() 方法返回 真正不可变Java 10 中设置。


        使用 Java 9(不可修改集)

        以下是初始化集合的最简洁的方式:

        Set<String> strSet6 = Set.of("Apple", "Ball", "Cat", "Dog");
        

        我们有这个 convenience factory 方法的 12 个重载版本:

        static &lt;E&gt; Set&lt;E&gt; of()

        static &lt;E&gt; Set&lt;E&gt; of(E e1)

        static &lt;E&gt; Set&lt;E&gt; of(E e1, E e2)

        // ....等等

        static &lt;E&gt; Set&lt;E&gt; of(E... elems)

        那么一个自然的问题是当我们有 var-args 时为什么我们需要重载版本?答案是:每个 var-arg 方法都会在内部创建一个数组,并且拥有重载的版本可以避免不必要的对象创建,也可以让我们避免垃圾回收的开销。


        使用 Java 8(可修改集)

        在 Java 8 中使用 Stream

        Set<String> strSet1 = Stream.of("A", "B", "C", "D")
                 .collect(Collectors.toCollection(HashSet::new));
        
        // stream from an array (String[] stringArray)
        Set<String> strSet2 = Arrays.stream(stringArray)
                 .collect(Collectors.toCollection(HashSet::new));
        
        // stream from a list (List<String> stringList)
        Set<String> strSet3 = stringList.stream()
                 .collect(Collectors.toCollection(HashSet::new));
        

        使用 Java 8(不可修改集)

        使用 Collections.unmodifiableSet()

        我们可以使用Collections.unmodifiableSet()作为:

        Set<String> strSet4 = Collections.unmodifiableSet(strSet1);
        

        但是看起来有点别扭,我们可以这样写我们自己的收集器:

        class ImmutableCollector {
            public static <T> Collector<T, Set<T>, Set<T>> toImmutableSet() {
                return Collector.of(HashSet::new, Set::add, (l, r) -> {
                    l.addAll(r);
                    return l;
                }, Collections::unmodifiablSet);
            }
        }
        

        然后将其用作:

        Set<String> strSet4 = Stream.of("A", "B", "C", "D")
                     .collect(ImmutableCollector.toImmutableSet());
        

        使用 Collectors.collectingAndThen()

        另一种方法是使用方法Collectors.collectingAndThen(),它可以让我们执行额外的整理转换:

        import static java.util.stream.Collectors.*;
        Set<String> strSet5 = Stream.of("A", "B", "C", "D").collect(collectingAndThen(
           toCollection(HashSet::new),Collections::unmodifiableSet));
        

        如果我们只关心Set,那么我们也可以使用Collectors.toSet() 代替Collectors.toCollection(HashSet::new)

        同时检查 this 对 Java 8 的回答。

        【讨论】:

        • 欢呼“为什么我们有 var-args 时需要重载版本?”部分
        【解决方案15】:

        最方便的方法之一是使用泛型Collections.addAll() 方法,它接受一个集合和可变参数:

        Set<String> h = new HashSet<String>();
        Collections.addAll(h, "a", "b");
        

        【讨论】:

        • 谢谢!很高兴我进一步阅读,imo这应该排名更高。这对于 Java 8 及以下版本非常有用;没有复制数组和笨拙的流。谢谢!
        • 这是一个很好的答案,因为与许多其他答案不同,据我所知,它提供了一个 modifiable Set ;)
        • 我认为这至少在 Java 5+ 中可用。
        【解决方案16】:

        可以使用静态块进行初始化:

        private static Set<Integer> codes1=
                new HashSet<Integer>(Arrays.asList(1, 2, 3, 4));
        
        private static Set<Integer> codes2 =
                new HashSet<Integer>(Arrays.asList(5, 6, 7, 8));
        
        private static Set<Integer> h = new HashSet<Integer>();
        
        static{
            h.add(codes1);
            h.add(codes2);
        }
        

        【讨论】:

          【解决方案17】:

          使用 Java 9,您可以执行以下操作:

          Set.of("a", "b");
          

          你会得到一个包含元素的不可变集合。有关详细信息,请参阅 Oracle documentation of interface Set

          【讨论】:

            【解决方案18】:
            import com.google.common.collect.Sets;
            Sets.newHashSet("a", "b");
            

            import com.google.common.collect.ImmutableSet;
            ImmutableSet.of("a", "b");
            

            【讨论】:

              【解决方案19】:

              (丑陋)没有副作用的双括号初始化:

              Set<String> a = new HashSet<>(new HashSet<String>() {{
                  add("1");
                  add("2");
              }})
              

              但在某些情况下,如果我们提到让最终集合不可变的好味道,它可能真的很有用:

              final Set<String> a = Collections.unmodifiableSet(new HashSet<String>(){{
                  add("1");
                  add("2");
              }})
              

              【讨论】:

                【解决方案20】:

                随着convenience factory methods 的发布,这可能以更简洁的方式实现:

                Set set = Set.of("a", "b", "c");
                

                【讨论】:

                • @cellepo 好吧,该答案的编辑历史表明它是后来编辑的。尽管同意内容是相同的,并且该答案涵盖了解决问题的更多方法。
                【解决方案21】:

                Builder 模式在这里可能有用。今天我遇到了同样的问题。我需要 Set 变异操作来返回 Set 对象的引用,所以我可以将它传递给超类构造函数,以便它们也可以继续添加到同一个集合中,依次从子类的 Set 中构造一个新的 StringSetBuilder刚刚建成。我写的builder类是这样的(在我的例子中它是一个外部类的静态内部类,但它也可以是它自己的独立类):

                public interface Builder<T> {
                    T build();
                }
                
                static class StringSetBuilder implements Builder<Set<String>> {
                    private final Set<String> set = new HashSet<>();
                
                    StringSetBuilder add(String pStr) {
                        set.add(pStr);
                        return this;
                    }
                
                    StringSetBuilder addAll(Set<String> pSet) {
                        set.addAll(pSet);
                        return this;
                    }
                
                    @Override
                    public Set<String> build() {
                        return set;
                    }
                }
                

                注意 addAll()add() 方法,它们是 Set 返回 Set.add()Set.addAll() 的对应项。最后注意build() 方法,它返回对构建器封装的Set 的引用。下面说明了如何使用这个 Set builder:

                class SomeChildClass extends ParentClass {
                    public SomeChildClass(String pStr) {
                        super(new StringSetBuilder().add(pStr).build());
                    }
                }
                
                class ParentClass {
                    public ParentClass(Set<String> pSet) {
                        super(new StringSetBuilder().addAll(pSet).add("my own str").build());
                    }
                }
                

                【讨论】:

                  【解决方案22】:

                  使用 Java 8 我们可以创建 HashSet 为:

                  Stream.of("A", "B", "C", "D").collect(Collectors.toCollection(HashSet::new));
                  

                  如果我们想要不可修改的集合,我们可以创建一个实用方法:

                  public static <T, A extends Set<T>> Collector<T, A, Set<T>> toImmutableSet(Supplier<A> supplier) {
                          return Collector.of(
                                  supplier,
                                  Set::add, (left, right) -> {
                                      left.addAll(right);
                                      return left;
                                  }, Collections::unmodifiableSet);
                      }
                  

                  这个方法可以用作:

                   Stream.of("A", "B", "C", "D").collect(toImmutableSet(HashSet::new));
                  

                  【讨论】:

                    【解决方案23】:

                    Michael Berdyshev 的回答与泛型相结合,并使用带有 initialCapacity 的构造函数,与 Arrays.asList 变体进行比较:

                      import java.util.Collections;
                      import java.util.HashSet;
                      import java.util.Set;
                    
                      @SafeVarargs
                      public static <T> Set<T> buildSetModif(final T... values) {
                        final Set<T> modifiableSet = new HashSet<T>(values.length);
                        Collections.addAll(modifiableSet, values);
                        return modifiableSet;
                      }
                    
                      @SafeVarargs
                      public static <T> Set<T> buildSetModifTypeSafe(final T... values) {
                        return new HashSet<T>(Arrays.asList(values));
                      }
                    
                      @SafeVarargs
                      public static <T> Set<T> buildeSetUnmodif(final T... values) {
                        return Collections.unmodifiableSet(buildSetModifTypeSafe(values));
                        // Or use Set.of("a", "b", "c") if you use Java 9
                      }
                    
                    • 如果您为 init 传递一些值,这很好,对于任何大的 使用其他方法
                    • 如果您不小心将类型与 buildSetModif 混合,则生成的 T 将 是? extends Object,这可能不是你想要的,buildSetModifTypeSafe 变体不会发生这种情况,这意味着buildSetModifTypeSafe(1, 2, "a"); 不会编译

                    【讨论】:

                      【解决方案24】:

                      你也可以使用vavr:

                      import io.vavr.collection.HashSet;
                      
                      HashSet.of("a", "b").toJavaSet();
                      

                      【讨论】:

                        猜你喜欢
                        • 2018-06-15
                        • 2020-05-31
                        • 2012-09-15
                        • 2021-02-17
                        • 2014-02-03
                        • 2017-06-23
                        • 2018-07-22
                        • 1970-01-01
                        相关资源
                        最近更新 更多