包含自身作为元素的 ArrayList 的哈希码

Jok*_*ker 37 java stack-overflow hashcode java-8

我们可以发现hashcodelist是本身含有的element

我知道这是一个不好的做法,但这是面试官问的。

当我运行以下代码时,它会抛出一个StackOverflowError

public class Main {
    public static void main(String args[]) {
        ArrayList<ArrayList> a = new ArrayList();
        a.add(a);
        a.hashCode();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我有两个问题:

  1. 为什么有一个StackOverflowError
  2. 是否可以通过这种方式找到哈希码?

Hol*_*ger 36

接口中指定了符合List实现的哈希码:

返回此列表的哈希码值。列表的哈希码定义为以下计算的结果:

 int hashCode = 1;
 for (E e : list)
     hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
Run Code Online (Sandbox Code Playgroud)

这可确保list1.equals(list2)意味着list1.hashCode()==list2.hashCode()对于任何两个列表,list1list2根据需要通过的总承包合同Object.hashCode()

这并不要求实现看起来完全一样(请参阅如何以与 List.hashCode() 相同的方式计算流的哈希码以获取替代方案),但仅包含自身的列表的正确哈希码将是一个x == 31 + x必须为 的数字true,换句话说,不可能计算出符合要求的数字。

  • @EricDuminil 我的答案的要点是,合约描述了 ArrayList 从字面上实现的逻辑,导致递归,但也没有一致的替代实现。请注意,我在OP已经理解为什么这个特定的实现会导致“StackOverflowError”时发布了我的答案,这已在其他答案中得到解决。因此,我关注的是在有限时间内完成具有值的一致性实现的一般可能性。 (4认同)
  • @pdem 规范是否使用算法、公式、伪代码或实际代码的冗长描述并不重要。正如答案中所说,规范中给出的代码通常并不排除替代实现。规范的形式没有说明是否进行了分析。接口文档的句子“*虽然允许列表将自身包含为元素,但建议极其谨慎:在此类列表上不再明确定义 equals 和 hashCode 方法*”表明确实发生了这样的分析。 (2认同)
  • @pdem你正在扭转它。我从未说过由于哈希码的原因列表必须相等。根据定义,列表**是**相等的。`Arrays.asList("foo")` 等于 `Collections.singletonList("foo")`,等于 `List.of("foo")` 等于 `new ArrayList&lt;&gt;(List.of( “foo”))`。所有这些列表都是平等的,一点。那么,由于这些列表是相等的,因此它们必须具有相同的哈希码。反之则不然。由于它们必须具有相同的哈希码,因此必须对其进行明确定义。无论他们如何定义它(在 Java 2 中),它都必须定义良好,并得到所有实现的认可。 (2认同)
  • @pdem 正是,一个自定义实现,它要么不实现“List”,要么有一个很大的警告标志“不要与普通列表混合”,与“IdentityHashMap”及其“**这个类*不是*通用列表”进行比较-目的地图实施!**”警告。在前一种情况下,您已经可以很好地实现“Collection”,但还可以添加基于列表样式索引的访问方法。这样,不存在等式约束,但它仍然可以与其他集合类型顺利配合。 (2认同)

Rav*_*ala 23

查看类中hashCode方法的骨架实现AbstractList

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}
Run Code Online (Sandbox Code Playgroud)

对于列表中的每个元素,这会调用hashCode. 在您的案例列表中,它是唯一的元素。现在这个电话永远不会结束。该方法以递归方式调用自身,并且递归不断缠绕,直到遇到StackOverflowError. 所以你找不到hashCode这种方式。

  • 是的,因为递归条件 (3认同)

Ste*_*n C 14

您已经定义了一个包含自身的(病理)列表。

为什么有StackOverflowError

根据javadocs(即规范),a 的hashcodeList被定义为其每个元素的hashcode 的函数。它说:

“列表的哈希码定义为以下计算的结果:”

int hashCode = 1;
    for (E e : list)
         hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
Run Code Online (Sandbox Code Playgroud)

因此,要计算 的哈希码a,首先要计算 的哈希码a。这是无限递归的,并很快导致堆栈溢出。

是否可以通过这种方式找到哈希码?

不。如果您从数学角度考虑上述算法规范,则List包含自身的 a的哈希码是一个不可计算的函数。不可能以这种方式(使用上述算法)或任何其他方式计算它。


Pet*_*ris 9

不,文档有答案

List 结构文档明确指出:

注意:虽然允许列表将自身包含为元素,但建议格外小心:equals 和 hashCode 方法不再在此类列表上明确定义。

除此之外没有什么可说的——根据 Java 规范,您将无法为包含自身的列表计算 hashCode;其他答案详细说明了为什么会这样,但关键是它是已知且有意的。