使用字符串元组作为HashMap的键

10

我需要以下Python代码的Java等价代码:

In [1]: d = {}
In [2]: k = ("x","2")
In [3]: d[k] = 1
In [4]: print d[("x","y")]
1

Python有元组,它们是可哈希的。我尝试在Java中进行以下操作,但没有成功:

Map<String[], Integer> d = new HashMap<String[], Integer>();
String[] k = new String[]{"x", "y"};
d.put(k, 1);
System.out.println(d.get(k));
System.out.println(d.get(new String[]{"x", "y"}));

它的输出结果为:

1
null

这意味着对String[]的引用被哈希而不是该值本身。

我能想到的一种低效的方法是将String[]中的元素连接成单个String

有更好的方法吗?


5
不要使用可变对象,尤其是数组,作为映射的键。 - Matt Ball
2
是的,只需创建自己的Tuple类,并使用equals()hashCode()方法即可。 - Andrew Logvinov
1
不要使用字符串连接来将不同的元素归类。如果您想要拥有键 ("aa", "b")("a", "ab"),那么会发生什么? - Matt Ball
@Matt 很棒的观点。在大多数情况下,Arrays.toString()将解决这些连接问题。 - user949300
3个回答

12
HashMap使用Object.hashCode()来创建哈希值。默认情况下,它使用对象的哈希值来为每个实例创建一个唯一的哈希值 - 但不会查看任何内容。
您可能希望创建一个元组,覆盖hashCode()方法,并且在创建后是不可变的:
public class Tuple<T> {
    private final T[] contents;

    public Tuple (T[] contents) {
        if (contents.length != 2)
            throw new IllegalArgumentException();
        this.contents = contents;
    }

    public T[] getContents () {
        return this.contents.clone();
    }

    @Override
    public int hashCode () {
        return Arrays.deepHashCode(this.contents);
    }

    @Override
    public boolean equals (Object other) {
        return Arrays.deepEquals(this.contents, other.getContents());
    }

    @Override
    public String toString () {
        return Arrays.deepToString(this.contents);
    }
}

[编辑]: 请注意,如果使用可变对象而不是字符串,则getter必须执行深拷贝,而不仅仅是简单的clone()以确保不可变性。


1
为什么要克隆每个对hashCode()的调用? - user949300
我并不这样做。我只是在getter中克隆内容数组,以确保不对其进行更改,因此元组是不可变的。 - Johannes H.
不确定两个内容的哈希码相加的行为如何。也许需要一个更详细的hashCode函数。对此有什么评论吗? - Johannes H.
1
дЄЇдЇЖеЃМжХіжАІпЉМеЇФиѓ•йЗНеЖЩequals()еєґе∞ЖT[]еЃЮдЊЛе≠ЧжЃµиЃЊзљЃдЄЇprivate final :) - Bimalesh Jha
@BimaleshJha:同意,已编辑。希望在快速而粗略的编辑中没有犯任何错误 ;) - Johannes H.

5
在Java中,数组没有提供hashCode()equals(Object)方法,因此它们不适合作为映射键。 相反,您可以使用Arrays.asList(string1, string1, etc),这将为您提供一个不可变的List,其中包含了一个Map键所需的所有方法。

如果可能的话,我建议将其创建为不可变列表。不幸的是,Java没有提供这样一个功能,但例如Guava提供了这个功能。当然,你也可以自己编写一个。 - Johannes H.
@Johannes,Collections.unmodifiableList(list)有什么问题吗? - Harald K
@haraldK:请查看Guava中ImmutableList的描述:Collections#unmodifiableList不同,后者是一个可以仍然更改的单独集合的视图,而ImmutableList的实例包含其自己的私有数据并且永远不会更改。因此,简而言之,区别在于:如果您仍然拥有对原始列表的引用,则仍然可以修改它,如果使用了.unmodifiableList() - Johannes H.
@Johannes 有道理。 :-) - Harald K
1
@Johannes H.:由于List的创建者对创建Collections.unmodifiableList(Arrays.asList(varargs))Collections.unmodifiableList(Arrays.asList(array.clone()))具有完全控制权,因此这是足够的。没有理由添加第三方库依赖项来替换此代码,因为代码是安全的,第三方库仍然无法阻止其他代码使用可变列表。 - Holger

1
你可以使用 Arrays.toString(myArray) 作为你的键。

这甚至比OP建议的方法更加不规范(该方法是将字符串连接起来而不是使用数组)。 - Johannes H.
2
为什么说“更脏”?首先,代码很少,你最终得到的是一个不可变的字符串。而且该字符串由[]限定,并由逗号分隔,因此不太可能出现意外匹配。例如,OP的方案无法处理“dog”,“ate”与“do”,“gate”的情况。我的方案可以。 - user949300

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