如何使HashMap与数组一起使用?

gag*_*nbm 21 java arrays hashmap

我使用布尔数组作为HashMap的键.但问题是当一个不同的数组作为键传递时,HashMap无法获取键,尽管元素是相同的.(因为它们是不同的对象).

如何使用数组作为键?这是代码:

public class main {
public static HashMap<boolean[], Integer> h;


public static void main(String[] args){
    boolean[] a = {false, false};

    h = new HashMap<boolean[], Integer>();
    h.put(a, 1);


    if(h.containsKey(a)) System.out.println("Found a");

    boolean[] t = {false, false};

    if(h.containsKey(t)) System.out.println("Found t");
    else System.out.println("Couldn't find t");

}

}
Run Code Online (Sandbox Code Playgroud)

无论是阵列at包含相同的元素,但HashMap的不返回任何东西t.

我如何使其工作?

Viv*_*ath 24

你不能这样做.都ta将有不同的hashCode()值,因为该java.lang.Array.hashCode()方法是从继承Object,它使用参考来计算哈希码(默认实现).因此,对于阵列的哈希码是参照相关的,这意味着你会得到不同的散列码值ta.此外,equals对两个数组不起作用,因为它也基于引用.

您可以这样做的唯一方法是创建一个自定义类,将boolean数组保留为内部成员.然后,您需要覆盖equalshashCode确保包含具有相同值的数组的实例相等且具有相同的哈希代码.

一个更简单的选择可能是List<Boolean>用作关键.根据文档,hashCode()实现List定义为:

int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
    E obj = i.next();
    hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
Run Code Online (Sandbox Code Playgroud)

如您所见,它取决于列表中的值而不是引用,因此这应该适合您.

  • @gaganbm确保`Map <K,V>`的通用键参数是`List <Boolean>`而不是'ArrayList <Boolean>`.最好使用接口而不是具体类型,以便您可以在以后需要时切换实现.所以你的`Map`看起来像`Map <List <Boolean>,Integer>`. (4认同)
  • @gaganbm,你在使用`java.util.List`吗? (2认同)

zch*_*zch 9

使用数组是不可能的,因为任何两个不同的数组都不会比较equals,即使它们具有相同的元素.

例如,您需要从容器类映射ArrayList<Boolean>(或简单地说List<Boolean>.也许BitSet更合适.


Gen*_*zer 5

Map实现依赖于密钥equalshashCode方法。在Java数组都是直接从延伸Object,他们使用默认的equalshashCodeObject,只有比较identity

如果我是你,我会创建一个类 Key

class Key {
    private final boolean flag1;
    private final boolean flag2;

    public Key(boolean flag1, boolean flag2) {
        this.flag1 = flag1;
        this.flag2 = flag2;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Key)) {
            return false;
        }

        Key otherKey = (Key) object;
        return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
    }

    @Override
    public int hashCode() {
        int result = 17; // any prime number
        result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
        result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

之后,您可以使用您的密钥Map

Map<Key, Integer> map = new HashMap<>();

Key firstKey = new Key(false, false);
map.put(firstKey, 1);

Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1
Run Code Online (Sandbox Code Playgroud)

参考: 来自一个字段的 Java 哈希码


Gle*_*son 5

问题

  1. 正如其他人所说,Java数组继承.hashcode()自Object,它使用数组或对象的地址.equals()的散列,完全忽略其内容。解决此问题的唯一方法是将数组包装在一个对象中,该对象根据数组的内容实现这些方法。这就是 Joshua Bloch 写下第 25 条:“比起数组更喜欢列表”的原因之一。Java 提供了几个执行此操作的类,或者您可以使用它们编写自己的类,其中包含这些方法的正确且有效的实现。太糟糕了,它们不是默认实现!Arrays.hashCode()Arrays.equals()

  2. 只要可行,请使用深度不可修改(或不可变)的类作为任何基于哈希的集合的键。如果将数组(或其他可变对象)作为键存储在哈希表中后对其进行修改,则几乎肯定会在该哈希表中的未来.get().contains()测试中失败。另请参阅可变哈希映射键是危险的做法吗?

具体解决方案

// Also works with primitive:    (boolean... items)
public static List<Boolean> bList(Boolean... items) {
    List<Boolean> mutableList = new ArrayList<>();
    for (Boolean item : items) {
        mutableList.add(item);
    }
    return Collections.unmodifiableList(mutableList);
}
Run Code Online (Sandbox Code Playgroud)
  1. ArrayList 根据其内容实现.equals()and .hashCode()(正确且高效),以便每个 都bList(false, false)具有相同的哈希码,并且将等于每个其他bList(false, false)

  2. 将其包裹起来Collections.unmodifiableList()可以防止修改。

修改示例以使用 bList() 只需更改一些声明和类型签名。它和你原来的一样清晰,几乎一样简短:

public class main {
    public static HashMap<List<Boolean>, Integer> h;

    public static void main(String[] args){
        List<Boolean> a = bList(false, false);

        h = new HashMap<>();
        h.put(a, 1);

        if(h.containsKey(a)) System.out.println("Found a");

        List<Boolean> t = bList(false, false);

        if(h.containsKey(t)) System.out.println("Found t");
        else System.out.println("Couldn't find t");
    }
}
Run Code Online (Sandbox Code Playgroud)

通用解决方案

public <T> List<T> bList(T... items) {
    List<T> mutableList = new ArrayList<>();
    for (T item : items) {
        mutableList.add(item);
    }
    return Collections.unmodifiableList(mutableList);
}
Run Code Online (Sandbox Code Playgroud)

上述解决方案的其余部分保持不变,但这将利用 Java 的内置类型推断来处理任何基元或对象(尽管我建议仅使用不可变类)。

图书馆解决方案

而不是bList()使用Google Guava ImmutableList.of()或我自己的Paguro vec()或其他提供此类预先测试方法的库(加上不可变/不可修改的集合等)。


劣质解决方案

这是我在 2017 年的原始答案。我把它留在这里是因为有人觉得它很有趣,但我认为它是二流的,因为 Java 已经包含了 ArrayList 和 Collections.unmodifyingList() 来解决这个问题。使用 .equals() 和 .hashCode() 方法编写自己的集合包装器比使用内置方法需要更多工作,更容易出错,更难以验证,因此更难阅读。

这应该适用于任何类型的数组:

class ArrayHolder<T> {
    private final T[] array;
    @SafeVarargs
    ArrayHolder(T... ts) { array = ts; }
    @Override public int hashCode() { return Arrays.hashCode(array); }
    @Override public boolean equals(Object other) {
        if (array == other) { return true; }
        if (! (other instanceof ArrayHolder) ) {
            return false;
        }
        //noinspection unchecked
        return Arrays.equals(array, ((ArrayHolder) other).array);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是转换为使用 ArrayHolder 的具体示例:

// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);

// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();

h.put(a, 1);

// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));

// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);

// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));

assertFalse(h.containsKey(new ArrayHolder<>(true, false)));
Run Code Online (Sandbox Code Playgroud)

我使用 Java 8,但我认为 Java 7 拥有您所需的一切。我使用TestUtils测试了 hashCode 和 equals 。