为什么Set不提供获取与另一个元素相等的元素的操作?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

我可以问Set是否包含一个等于bar的元素,那么为什么我不能得到那个元素呢?:(

为了澄清,equals方法被重写,但它只检查其中一个字段,而不是所有字段。两个相等的Foo对象可以有不同的值,这就是为什么我不能只用Foo。


当前回答

Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

如果你只做一次获取,这将不是很好的执行,因为你将循环所有的元素,但当你在一个大的集合上执行多次检索时,你会注意到区别。

其他回答

不幸的是,Java中的Default Set并不是为提供“get”操作而设计的,正如jschreiner所准确解释的那样。

使用迭代器找到感兴趣的元素(dacwe建议)或删除元素并更新其值重新添加元素(KyleM建议)的解决方案可能有效,但效率非常低。

重写等号的实现以使非等号对象“相等”,正如David Ogren所正确指出的那样,很容易导致维护问题。

恕我直言,使用Map作为显式替换(正如许多人建议的那样)会使代码不那么优雅。

如果目标是访问集合中包含的元素的原始实例(希望我正确理解了您的用例),这里有另一种可能的解决方案。


我个人在用Java开发客户端-服务器视频游戏时也有同样的需求。在我的例子中,每个客户机都有存储在服务器中的组件的副本,问题在于客户机何时需要修改服务器的对象。

通过互联网传递一个对象意味着客户端无论如何都有该对象的不同实例。为了将这个“复制”的实例与原始实例相匹配,我决定使用Java uuid。

因此,我创建了一个抽象类UniqueItem,它自动为其子类的每个实例提供一个随机的惟一id。

这个UUID在客户机和服务器实例之间共享,因此通过这种方式,只需使用Map就可以很容易地匹配它们。

然而,在类似的用例中直接使用Map仍然是不优雅的。有人可能会说,使用Map维护和处理可能更加复杂。

出于这些原因,我实现了一个名为MagicSet的库,它使得Map的使用对开发人员来说是“透明的”。

https://github.com/ricpacca/magicset


与原来的Java HashSet一样,MagicHashSet(库中提供的MagicSet的实现之一)使用一个支持HashMap,但是它使用元素的UUID作为键,使用元素本身作为值,而不是将元素作为键和虚拟值作为值。与普通HashSet相比,这不会导致内存使用的开销。

此外,MagicSet可以完全作为Set使用,但有一些提供额外功能的方法,如getFromId()、popFromId()、removeFromId()等。

使用它的唯一要求是您想要存储在MagicSet中的任何元素都需要扩展抽象类UniqueItem。


下面是一个代码示例,设想从MagicSet中检索一个城市的原始实例,给定该城市的另一个实例,该实例具有相同的UUID(甚至只有它的UUID)。

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}

如果你想要HashSet中的第n个元素,你可以使用下面的解决方案, 这里我在HashSet中添加了ModelClass对象。

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}

如果你有一个NavigableSet(例如TreeSet),你可以这样做:

public static <E> E get(NavigableSet<E> set, E key) {
    return set.tailSet(key, true).floor(key);
}

对于HashSet及其后代(如LinkedHashSet)来说,事情有点棘手:

import java.util.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test {
    private static final Field mapField;
    private static final Method hashMethod;
    private static final Method getNodeMethod;
    private static final Field keyField;
    static {
        try {
            mapField = HashSet.class.getDeclaredField("map");
            mapField.setAccessible(true);
            hashMethod = HashMap.class.getDeclaredMethod("hash", Object.class);
            hashMethod.setAccessible(true);
            getNodeMethod = HashMap.class.getDeclaredMethod("getNode",
                    Integer.TYPE, Object.class);
            getNodeMethod.setAccessible(true);
            keyField = Class.forName("java.util.HashMap$Node").getDeclaredField("key");
            keyField.setAccessible(true);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static <E> E get(HashSet<E> set, E key) {
        try {
            Object map = mapField.get(set);
            Object hash = hashMethod.invoke(null, key);
            Object node = getNodeMethod.invoke(map, hash, key);
            if (node == null)
                return null;
            @SuppressWarnings("unchecked")
            E result = (E)keyField.get(node);
            return result;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public static <E> E get(NavigableSet<E> set, E key) {
        return set.tailSet(key, true).floor(key);
    }

    public static void main(String[] args) {
        HashSet<Integer> s = new HashSet<>();
//      HashSet<Integer> s = new LinkedHashSet<>();
//      TreeSet<Integer> s = new TreeSet<>();
        for (int i = 0; i < 100_000; i++)
            s.add(i);
        Integer key = java.awt.event.KeyEvent.VK_FIND;
        Integer hidden = get(s, key);
        System.out.println(key);
        System.out.println(hidden);
        System.out.println(key.equals(hidden));
        System.out.println(key == hidden);
    }
}

看起来合适的使用对象是番石榴中的Interner:

为其他不可变提供与String.intern()相同的行为 类型。常见的实现可以从Interners获得 类。

它也有一些非常有趣的杠杆,比如concurrencyLevel,或者使用的引用类型(可能值得注意的是,它没有提供softinternet,我认为这比weakinternet更有用)。

我知道,这个问题很久以前就被问过,但如果有人感兴趣,这里是我的解决方案-自定义集类支持HashMap:

http://pastebin.com/Qv6S91n9

您可以轻松实现所有其他Set方法。