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

Posted

技术标签:

【中文标题】如何通过构造初始化HashSet值?【英文标题】:How to initialize HashSet values by construction? 【发布时间】: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,请查看我的答案 ***.com/a/37406054/1216775 Michael Berdyshev 的回答实际上是最好的,因为它是生成 modifiable 集的最干净的方法。 i_am_zero 答案也有不同的方式来制作可修改的 Set,但它更冗长/笨拙[使用 Lambda 流];否则 i_am_zero 答案是次佳的,因为它的不同选项的广度(跨 Java 版本)。 注意:有些答案会省略`new HashSet(int initialCapacity) 参数,如果您已经知道集合的大小,请使用它。 【参考方案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");,它会更具可读性 请注意Arrays#asList returns a structurally immutable List【参考方案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 请注意Arrays#asList returns a structurally immutable List【参考方案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】:

随着java9 和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();

【讨论】:

以上是关于如何通过构造初始化HashSet值?的主要内容,如果未能解决你的问题,请参考以下文章

如何用值初始化 HashSet<Long> [重复]

无法将整数发送到HashSet构造函数

C#中List怎么转换成hashset

如何通过可变参数模板将多个构造函数参数转发到数组初始值设定项列表?

java集合之HashSet

Java源码初学_HashSet&LinkedHashSet