如何通过构造函数初始化HashSet的值?

915

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

Set<String> h = new HashSet<String>();
h.add("a");
h.add("b");
有没有一种方法可以用一行代码完成这个操作?例如,对于最终的静态字段非常有用。

2
对于Java 8、Java 9和Java 10,请查看我的答案https://dev59.com/1XI-5IYBdhLWcg3wCztn#37406054。 - akhil_mittal
2
Michael Berdyshev的回答实际上是最好的,因为它是生成可修改Set的最清晰的方法。i_am_zero Answer也有一种不同的方法来创建可修改的Set,但它更冗长/笨重[使用Lambda流];否则,i_am_zero Answer是最佳选择,因为它提供了各种选项(跨Java版本)。 - cellepo
注意:如果您已经知道集合的大小,请使用“new HashSet<T>(int initialCapacity)”参数。 - Christophe Roussy
@ChristopheRoussy,将initialCapacity设置为集合的确切大小将会效率低下,因为它会强制立即重新哈希,除非您也考虑到loadFactor - jaco0646
24个回答

1145

我有一个缩写,虽然不太节省时间,但适合单行显示:

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

再次强调,这种方法不够高效,因为你需要构造一个数组,将其转换为列表,然后再用该列表创建一个集合。

当初始化静态 final 集合时,我通常会按照以下方式编写:

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

稍微好看一点,对于静态初始化来说效率并不重要。


2
尽管在编写时可能是正确的,但从Java 7开始,请使用钻石操作符,例如:Set<String> h = new HashSet<>(Arrays.asList("a", "b")); public static final Set<String> MY_SET = new HashSet<>(Arrays.asList(SET_VALUES)); - Nestor Milyaev
3
可以确认钻石符号表示法有效(即使在Set声明和HashSet赋值之间)。 - Dan Temple
1
@NestorMilyaev,Gennadiy,stream.of(...)怎么样?有了Java 8,这是更好的解决方案吗? - Shilan
40
在Java 9中,Set<String> h = Set.of("a", "b") //Unmodifiable创建的集合是不可修改的。此答案截至2021年9月1日为准 - Mark Peschel
显示剩余3条评论

406

集合字面量原计划在Java 7中推出,但未能实现。因此目前还没有自动化的选项。

您可以使用guava的Sets

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

或者你可以使用以下语法,它会创建一个匿名类,但是这种方法有点hacky:

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

60
这是一个hack,因为它创建了一个匿名内部类HashSet,并带有一个静态初始化部分,调用add()方法,这显然对于这种情况来说是过度的。我喜欢它! - Conan
47
双括号初始化是一种非常危险的技巧。匿名类与外部类有一个隐式链接,可能会导致内存泄漏。持有双括号初始化的集合的人也持有创建集合所在类的引用。 - fhucho
7
这种方法在设置模拟对象期望时非常流行,比如在JMock中,即使考虑到内存泄漏的风险,在这种情况下也是有意义的(测试是短暂的进程,其内存占用不重要)。 - william.berg

316

如果你正在寻找初始化set的最紧凑方式,那么在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 -> (Set<T>)Set.of(set.toArray())所示。

需要注意的一点是方法Collections.unmodifiableSet()返回指定集合的不可修改视图(根据文档)。一个不可修改的视图集合是一个不可修改的集合,也是一个基于后备集合的视图。请注意,对后备集合的更改可能仍然是可能的,如果发生更改,则可以通过不可修改的视图看到它们。但是,方法Collectors.toUnmodifiableSet()Java 10中返回真正的不可变集合。


使用Java 9(不可修改的集合)

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

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

我们有12个重载版本的便利工厂方法:convenience factory

static <E> Set<E> of()

static <E> Set<E> of(E e1)

static <E> Set<E> of(E e1, E e2)

// ....和其他

static <E> Set<E> 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)
此外,查看这个Java 8的答案。

1
为“当我们有可变参数时,为什么需要重载版本?”部分喝彩 - Daniel Pop
1
Set.of()的未来读者添加一个警告。如果您尝试添加重复元素,它将抛出IllegalArgumentException异常。 - Snehasish

224

在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));

42
这比原问题中的简单代码更加冗长且不必要地复杂。 - Natix
14
对于2个元素,这种表示方式更冗长,但如果你不得不硬编码包含更多元素(10-100个)的集合,则这种表示开始变得简洁。 - Schleir
7
@Natix 是的,但在某些情况下,这是一种优势,因为它是内联的。 - deFreitas
3
为什么不使用更紧凑的版本:Stream.of(1, 3, 5).collect(toSet()); 它使用了静态导入 import static java.util.stream.Collectors.toSet; - borjab
3
此外,如果您想要一个只有一个元素的 Set,可以创建一个 Singletonfinal Set<T> SOME_SET = Collections.singleton(t); - Valentin Grégoire

93
有几种方法:
双括号初始化
这是一种创建匿名内部类的技术,该类具有实例初始化程序,当创建实例时会向其添加字符串:
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 中。


21
GoogleCollections还拥有ImmutableSet.of(T...)。大多数情况下,如果您不需要稍后更改集合,则使用不可变集合有许多优势。 - Kevin Bourrillion
4
澄清“每次使用时都会创建一个新的HashSet子类”意味着“每次编码时都会创建一个新的HashSet子类”。每次执行不会创建新的子类。 - Bohemian
双括号初始化如果不知道该怎么做可能会很危险,在使用之前请查阅其影响。 - Alkanshel

66

最方便的一种方式是使用通用的 Collections.addAll() 方法,该方法接受一个集合和可变参数:

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

3
谢谢!很高兴我继续阅读下去,我认为这应该排名更高。对于Java 8及以下版本来说,这非常棒;不需要复制困难的流式数组。谢谢! - redeveloper
1
这是一个很棒的答案,因为与许多其他答案不同,据我所知,它提供了一个可修改的集合 ;) - cellepo
4
我认为这在至少Java 5+版本可用。 - cellepo
1
我刚刚因为花了很多时间试图弄清楚如何从字面值创建一个HashSet而在编码测试中失败了。谢谢你提供这个提示 - 我将来会用到它! - likejudo

58
如果你只有一个值并且想要得到一个不可变的集合,那么这就足够了:
Set<String> immutableSet = Collections.singleton("a");

1
这非常有用,因为我们的代码分析工具会抱怨如果你只使用new HashSet<>(Arrays.asList( _values_ ))方法来处理单个值。 - yokeho
10
注意,生成的Set<String>是不可变的。 - user1706698

36

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

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

你会得到一个包含元素的不可变Set。详见Oracle Set接口文档


33

我认为最易读的方法就是简单地使用Google Guava

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

它是可变的。


8
现在应该用 Sets.newHashSet() - membersound
1
@membersound 我应该导入哪个包才能使用Sets.newHashSet()呢? - Abdennacer Lachiheb
@AbdennacerLachiheb "com.google.common.collect.Sets" @AbdennacerLachiheb "com.google.common.collect.Sets" - Sachintha Hewawasam
@membersound 我查了旧文档,直到guava 2.0版本都没有newSet(),我猜LanceP指的是像gs-collections这样的其他框架。所以,根据你说的修改了答案,谢谢。 - Satish Patro

33

你可以在Java 6中完成这项任务:

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

但为什么呢?我并不觉得这比显式添加元素更易读。


6
我用声明方式创建它。这是一个不可更改的属性。谢谢。 - Serg
18
啊,值得注意的是,你仍然可以向最终集合添加元素。只需要声明集合为final,但是像平常一样在构造函数(或任何其他地方)中添加元素即可。一个final的集合仍然可以添加或删除元素。只有对集合本身的引用是final的。 - Jason Nichols
5
同意Jason Nichols所说的。不要忘记使用Collections.unmodifiableSet(...)。 - Eli Acherkan
我的意思是,如果它是new HashSet<String>("a", "b", "c");,那么它会更易读。 - sam boosalis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接