You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Set集合中禁止插入同类型对象?

Solution for a Set That Allows Only One Instance Per Card Subclass

Great question! The JDK doesn't have a built-in Set implementation that enforces "one instance per subclass" out of the box, but there are straightforward ways to build this yourself. Let's break down the problem and solutions:

Why Overriding equals()/hashCode() Isn't Reliable

You're right to avoid this approach. If you made all instances of the same subclass return true for equals() and the same hashCode(), you'd technically get the behavior you want—but it violates the contract of equals().

For example, two Diamonds objects with different id or materialName values would be considered equal, which doesn't make sense for your domain (these are distinct objects with different state). This could cause bugs elsewhere in your code when you expect to compare these cards by their actual properties.

Custom Set Implementation Using a Map

The cleanest approach is to wrap a Map where the key is the subclass Class (since each subclass has a unique Class object), and the value is the corresponding Card instance. This guarantees only one entry per subclass.

Here's a complete implementation:

import java.util.*;

public class TypeUniqueCardSet implements Set<Card> {
    // Maps each Card subclass to its single instance in the set
    private final Map<Class<? extends Card>, Card> typeInstanceMap = new HashMap<>();

    @Override
    public boolean add(Card card) {
        if (card == null) {
            throw new NullPointerException("Card cannot be null");
        }
        Class<? extends Card> cardType = card.getClass();
        // If the type already exists, reject the add
        if (typeInstanceMap.containsKey(cardType)) {
            return false;
        }
        typeInstanceMap.put(cardType, card);
        return true;
    }

    @Override
    public boolean contains(Object o) {
        if (!(o instanceof Card)) {
            return false;
        }
        Card candidate = (Card) o;
        // Check if the type exists AND the stored instance matches
        Card stored = typeInstanceMap.get(candidate.getClass());
        return stored != null && stored.equals(candidate);
    }

    @Override
    public boolean remove(Object o) {
        if (!(o instanceof Card)) {
            return false;
        }
        Card card = (Card) o;
        return typeInstanceMap.remove(card.getClass()) != null;
    }

    // Delegate other Set methods to the map's values collection
    @Override
    public int size() {
        return typeInstanceMap.size();
    }

    @Override
    public boolean isEmpty() {
        return typeInstanceMap.isEmpty();
    }

    @Override
    public Iterator<Card> iterator() {
        return typeInstanceMap.values().iterator();
    }

    @Override
    public Object[] toArray() {
        return typeInstanceMap.values().toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return typeInstanceMap.values().toArray(a);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        for (Object o : c) {
            if (!contains(o)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean addAll(Collection<? extends Card> c) {
        boolean modified = false;
        for (Card card : c) {
            if (add(card)) {
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new UnsupportedOperationException("retainAll is not supported for this set");
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        for (Object o : c) {
            if (remove(o)) {
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public void clear() {
        typeInstanceMap.clear();
    }
}

How to Use It

Set<Card> cards = new TypeUniqueCardSet();
System.out.println(cards.add(new Diamonds(1, new Universe(), "steel"))); // true
System.out.println(cards.add(new Spades("iron", 7.87))); // true
System.out.println(cards.add(new Spades("copper", 8.96))); // false (Spades already exists)

Simplified Implementation with Guava (Optional)

If you're using Google Guava, you can use ForwardingSet to avoid writing all the boilerplate Set methods. This delegates most work to a standard HashSet while adding your type-checking logic:

import com.google.common.collect.ForwardingSet;
import java.util.HashSet;
import java.util.Set;

public class TypeUniqueCardSet extends ForwardingSet<Card> {
    private final Set<Card> delegate = new HashSet<>();
    private final Set<Class<? extends Card>> registeredTypes = new HashSet<>();

    @Override
    protected Set<Card> delegate() {
        return delegate;
    }

    @Override
    public boolean add(Card card) {
        Class<? extends Card> cardType = card.getClass();
        if (registeredTypes.contains(cardType)) {
            return false;
        }
        registeredTypes.add(cardType);
        return super.add(card);
    }

    @Override
    public boolean remove(Object o) {
        if (o instanceof Card) {
            registeredTypes.remove(((Card) o).getClass());
        }
        return super.remove(o);
    }

    @Override
    public void clear() {
        registeredTypes.clear();
        super.clear();
    }
}

Key Takeaways

  • Avoid overriding equals()/hashCode() for this purpose—it breaks object equality semantics.
  • Use a custom Set backed by a Map (or Guava's ForwardingSet) to track which subclasses are already present.
  • This approach keeps your domain classes clean and adheres to standard collection contracts.

内容的提问来源于stack exchange,提问作者Johann MARTINET

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:48:26