一个数组能否用作HashMap的键?

44
如果一个 HashMap 的键是一个 String[] 数组:
HashMap<String[], String> pathMap;

您能否通过使用新创建的String[]数组访问地图,还是必须使用相同的String[]对象?

pathMap = new HashMap<>(new String[]{"korey", "docs"}, "/home/korey/docs");
String path = pathMap.get(new String[]{"korey", "docs"});
9个回答

55

它必须是同一个对象。在Java中,HashMap使用equals()比较键,只有两个数组是同一个对象时才相等。

如果您想要值相等,则编写自己的容器类来包装String[]并提供适当的equals()hashCode()语义。在这种情况下,最好将容器设置为不可变,因为更改对象的哈希码会对基于哈希的容器类造成破坏。

编辑

正如其他人指出的那样,List<String>具有您似乎想要的容器对象的语义。因此,您可以像这样做:

HashMap<List<String>, String> pathMap;

pathMap.put(
    // unmodifiable so key cannot change hash code
    Collections.unmodifiableList(Arrays.asList("korey", "docs")),
    "/home/korey/docs"
);

// later:
String dir = pathMap.get(Arrays.asList("korey", "docs"));

2
我认为它也使用 hashCode() 来确定对象的哈希值。 - pvorb
1
@pvorb - 的确如此。两个数组很少会有相同的哈希码。但是,这不是任何Java实现的要求。在任何情况下,具有相同hashCode()的两个引用将使用equals()进行比较,以确定它们是否是相同的键。 - Ted Hopp
2
@KoreyHinton - 没事。:) 不过在此之前,请检查 List 是否符合您的需求。请看我的编辑答案。 - Ted Hopp
这听起来非常有前途,但当我尝试使用containsKey时,我得到了一个“ClassCastException: java.util.Collections$UnmodifiableList cannot be cast to java.lang.Comparable”的错误,仍在研究中... - Mark Bennett
@MarkBennett - 奇怪。据我所知,HashMap.containsKey 不需要键实现 Comparable 接口。它只依赖于 hashCode()equals() 方法。你的代码中一定还有其他问题。 - Ted Hopp
显示剩余6条评论

13
不可以,但是你可以使用 List<String>,它会按照你的预期工作!

8
这个方法使用有一个注意事项。如果你要在基于哈希的集合中使用List<String>作为键,那么这个列表应该是不可修改的。如果一个对象的哈希码在它被用作哈希集合的键时发生了改变,那么通常会导致集合出现故障。 - Ted Hopp
List 是一个接口,没有保证实现会正确地覆盖 equalshashCode - Steve Kuo
3
@SteveKuo - 是的,有的。List 的文档要求任何实现都必须使用特定的语义来实现 equals()hashCode()。所需的语义与 OP 所需的相匹配。 - Ted Hopp
实际上,AbstractList类是所有列表实现继承的超类,它重写了hashCode()和equals()方法,以便反映基于元素而不是引用的比较。 - Peng

5

在Java中,数组使用ObjecthashCode(),并且不会重写它(与equals()toString()相同)。因此,不能/不应该使用数组作为哈希映射键。


2
你可以将它们用作键,它只会使用Object为其hashCode执行的任何操作...虽然这不是他想要的,但没有什么能阻止你这样做。 - Lucas

2

你不能将普通的Java Array 作为 HashMap 的键使用。(虽然你可以这样做,但它不会按预期工作。)

但是你可以编写一个包装器类,该类具有对数组的引用,并且还覆盖了 hashCode()equals() 方法。


1
不需要编写新的数组包装类,已经存在一个 - ArrayList - Steve Kuo
@SteveKuo 是的,确实如此。但也许你想自己编写代码,因为 ArrayList 是可变的,底层数组可以在内部被替换而你可能没有注意到。 - pvorb
1
@pvorb - 我们总是可以使用 Collections.unmodifiableList(someList) 将一个 List 转换成一个不可变对象。 - Ted Hopp
@TedHopp:unmodifiableList 并不等同于 immutable list。你仍然可以直接修改原列表。 - Alex
@Alex - 是的,你说得对。这在我上面的答案中被指出,详见这条评论。还请参阅我的回复 - Ted Hopp

1
你需要在数组周围创建一个包装类,覆盖相等性和哈希码。
例如:
/** 
 * We can use this instance as HashKey,
 * the same anagram string will refer the same value in the map.
 */
class Anagram implements CharSequence {

    private final char[] anagram;

    public Anagram(String anagram) {

        this.anagram = anagram.toCharArray();
        Arrays.sort(this.anagram);
    }

    @Override
    public boolean equals(Object o) {

        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Anagram that = (Anagram) o;
        return Arrays.equals(this.anagram, that.anagram);
    }

    @Override
    public int hashCode() {

        return Arrays.hashCode(this.anagram);
    }

    @Override
    public int length() {

        return anagram.length;
    }

    @Override
    public char charAt(int index) {

        return anagram[index];
    }

    @Override
    public CharSequence subSequence(int start, int end) {

        return new String(anagram).subSequence(start, end);
    }

    @Override
    public String toString() {

        return Arrays.toString(anagram);
    }
}

否则,将您的地图声明为IdentityHashMap,这样用户就知道我们需要使用相同的实例来进行CRUD操作。

1
在大多数情况下,只要数组中的字符串不是病态且不包含逗号后跟空格,则可以使用 Arrays.toString() 作为唯一键。例如,您的 Map 将是一个 Map<String, T>。对于数组 myKeys[] 的 get/put 操作将如下所示:
T t = myMap.get(Arrays.toString(myKeys));

myMap.put(Arrays.toString(myKeys), myT);

显然,如果需要的话,您可以加入一些包装代码。

一个好的副作用是,现在您的键是不可变的。当然,如果您更改了数组 myKeys,然后尝试使用 get(),您将找不到它。

字符串哈希高度优化。因此,我猜这个解决方案,虽然感觉有点慢和笨重,但比 @Ted Hopp 使用不可变列表的解决方案更快,更节省内存(对象分配更少)。只需考虑 Arrays.toString() 是否对您的键唯一即可。如果不是,或者存在任何疑问(例如,String[] 来自用户输入),请使用 List。


0

Ted Hopp 是正确的,它必须是同一个对象。

有关信息,请参阅此示例:

public static void main(String[] args) {
    HashMap<String[], String> pathMap;
    pathMap = new HashMap<String[], String>();
    String[] data = new String[]{"korey", "docs"};
    pathMap.put(data, "/home/korey/docs");
    String path = pathMap.get(data);
    System.out.println(path);
}

当您运行上述代码时,它将打印“docs”。

哈哈,那是因为你使用了相同的对象作为键,尝试使用相同的数组但新创建的。 - antoniOS

0

自从 Java 9 版本以来,您可以使用 Arrays::compare 方法作为 TreeMap 的比较器,用于比较数组的内容

Map<String[], String> map = new TreeMap<>(Arrays::compare);

String[] key1 = {"one", "two"};
String[] key2 = {"one", "two"};
String[] key3 = {"one", "two"};

map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.size()); // 1
System.out.println(map.get(key1)); // value2
System.out.println(map.get(key2)); // value2
System.out.println(map.get(key3)); // value2

另请参阅:如何在Java中创建一个数组的Set


-1
一个使用Arrays工具和它提供的哈希码的运行示例:
String[] key1 = { "korey", "docs" };
String value1 = "/home/korey/docs";
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(Arrays.hashCode(key1), value1);
System.out.println(map);

{-1122550406=/home/korey/docs}

如果你只关注存储,那么这种方法非常有用。使用可读(原始)键进行检索很简单:

String retrievedValue = map.get(Arrays.hashCode(key1));
System.out.println(retrievedValue);

/home/korey/docs


1
这种方法的问题在于,当两个string[]在Arrays.hashCode()下具有相同的hashCode时,它无法处理哈希冲突。 - Peng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接