如何将 Map.values 作为 Set 而不是 Collection?

How to get Map.values as a Set rather than a Collection?

我有一个映射 Map<String, SomeType>,其中 SomeType 的每个实例都按名称添加,例如 map.put(object.getName(), object)。最后,map.values().

没有重复

现在,我想要这张地图的 Set<SomeType> 而不是像 new HashSet<>(map.values()) 这样的副本。

这可能吗,只对标准库有优惠?

没有 Collection 包装器是不可能的。

只是因为在您的情况下没有重复,并不意味着它们不能。

Java 集合 API 允许您在 Map 中放置重复值,因此合同必须 return Collection 而不是 Set.

您已经知道如何使用 new HashSet<>(map.values())。您无法直接获取一组值,因为这些值可能包含重复项。即使在您的特定 Map 中没有重复值,在一般 Map 中也可能存在重复值。

您可以使用 Java 8 个 Streams 做一些事情,但它与显式实例化 Set 相比没有任何优势。

Set<SomeType> values = map.values().stream().collect(Collectors.toSet());

创建任何 CollectionSet 视图将是一件简单的事情。

public final class SetView<E> extends AbstractSet<E> {

    private final Collection<? extends E> collection;

    private SetView(Collection<? extends E> collection) {
        this.collection = collection;
    }

    public static <E> SetView<E> of(Collection<? extends E> collection) {
        return new SetView<>(collection);
    }

    @Override
    public boolean contains(Object e) {
        return collection.contains(e);
    }

    // rest omitted.
}

有了这个,你就可以写 Set<E> set = SetView.of(map.values()); 并且对 Map 的所有更改都会自动反映在 Set.

这种方法的问题在于,如果不复制 Collection,某些方法将难以实现。比如size()怎么写?

最明智的做法是

@Override
public int size() {
    return new HashSet<>(collection).size();
}

但这违背了使用视图而不是首先复制元素的目的。如果您知道 Collection 永远不会包含重复项,您 可以 简单地做

@Override
public int size() {
    return collection.size();
}

但是我建议不要采用这种方法。 Map 允许包含重复值,添加两个具有相同值的条目的可能性意味着 Set 的合同将被破坏。

除非你有 非常 不想复制元素的充分理由,否则我只会使用 new HashSet<>(map.values()).