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


38

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

我知道这是一个坏习惯,但这就是面试官的要求。

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

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

现在,我有两个问题:

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

7
因为您将列表添加到自身。尝试不带add语句的a.hashCode()
Jens

当您将对象放入arraylist时,您将存储对该对象的引用。
Vishwa Ratna


好的,我知道为什么会有stackoverflow,有人可以帮我解释一下问题编号2-如何找到它
Joker

9
正如其他人回答的那样,按照List接口的定义,这是不可能的,hashCode列表的取决于其成员。鉴于列表是其自己的成员,因此其哈希码取决于其hashCode,取决于其hashCode...等等,从而导致无限递归,StackOverflowError您将遇到此问题。现在的问题是:为什么您需要一个列表来包含自身?我可以向您保证,您可以以更好的方式实现您想做的所有事情,而无需像这样的递归成员身份。
亚历山大-

Answers:


36

在接口中指定了用于List实现的哈希码:

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

 int hashCode = 1;
 for (E e : list)
     hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

这样可确保list1.equals(list2)暗示list1.hashCode()==list2.hashCode()对于任何两个列表,list1并且list2按的总合同要求Object.hashCode()

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


1
@ Holger,Eirc希望替换整个函数的代码hashCode()以返回0。这从技术上解决了x == 31 + x,但忽略了要求,即x必须至少为1
bxk21

4
@EricDuminil我的回答的重点是,合同描述了按ArrayList字面实现的逻辑,导致递归,但是也没有符合要求的替代实现。请注意,我在OP已经了解了为什么此特定实现导致产生的时候发布了我的答案,StackOverflowError其他答案已经解决了这个问题。因此,我集中讨论了在有限的时间内用值完成一致性实现的一般可能性。
Holger

2
@pdem规范是否使用对算法,公式,伪代码或实际代码的简短描述都没有关系。如答案中所述,规范中给出的代码通常并不排除其他实现。规范的形式没有说明分析是否发生。接口文档的句子“ 虽然允许列表包含自己作为元素,但建议格外小心:equals和hashCode方法在这样的列表上不再得到很好的定义 ”表明这样的分析确实发生了。
Holger

2
@pdem您正在反转它。我从来没有说过,由于哈希码的原因,列表必须相等。根据定义,这些列表相等的。Arrays.asList("foo")等于Collections.singletonList("foo")等于List.of("foo")等于new ArrayList<>(List.of("foo"))。所有这些列表都是相等的。然后,由于这些列表相等,因此它们必须具有相同的哈希码。并非相反。由于它们必须具有相同的哈希码,因此必须定义明确。无论他们如何定义它(早在Java 2中),都必须对其进行良好定义,以使所有实现都同意。
霍尔格

2
@pdem确切地说,是一个自定义实现,该实现要么没有实现,List要么有一个很大的警告标志“请勿与普通列表混在一起”,与相比,IdentityHashMap它的“ 此类不是通用Map实现!“ 警告。在前一种情况下,您已经可以实施,Collection但是还添加了基于列表样式索引的访问方法。这样,不存在相等约束,但是它仍然可以与其他集合类型一起正常工作。
Holger

23

hashCodeAbstractList类中检查该方法的基本实现。

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

对于列表中的每个元素,此调用 hashCode。在您的情况下,列表本身就是唯一的元素。现在,此呼叫永无止境。该方法以递归方式调用其自身,并且递归一直缠绕直到遇到StackOverflowError。所以你找不到hashCode这种方式。


那么答案是没有办法以这种方式找到哈希码吗?
Joker

3
是的,因为存在递归条件
春季

不仅如此,它是通过这种方式定义的
克莱里斯(罢工)-

14

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

为什么有StackOverflowError

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

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

int hashCode = 1;
    for (E e : list)
         hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

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

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

不能。如果您以数学术语考虑上述算法规范,则List包含自身的哈希码是不可计算的函数。无法以这种方式(使用上述算法)或任何其他方式进行计算


我不知道为什么这个答案比其他两个答案要低,因为它实际上回答了OP的问题并给出了解释。
Neyt

1
@Neyt一些用户只阅读顶部的答案。
Holger

8

不,文档有答案

List结构文档明确指出:

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

除此之外,没有更多要说的内容-根据Java规范,您将无法为包含自身的列表计算hashCode;其他答案则详细说明了为什么会这样,但重点是它是已知的和故意的。


1
您确实告诉了它为什么不在规范范围内,所以它说明这不是错误。那是其他答案中缺少的部分。
pdem

3

拉文德拉的答案很好地解释了第1点。对问题2进行评论:

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

这里是圆形的。在此堆栈溢出错误的情况下,这2个中的至少一个必须是错误的:

  • 该列表的哈希码必须考虑其元素的哈希码
  • 列表可以作为自己的元素是可以的

现在,因为我们正在处理 ArrayList,所以第一点是固定的。换句话说,也许您需要一种不同的实现方式才能有意义地计算递归列表的哈希码……可以扩展ArrayList并跳过添加元素的哈希码,例如

for (E e : this)
  if(e == this) continue; //contrived rules
  hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

ArrayList您可以使用此类而不是。

使用ArrayList,第二点是错误的。因此,如果面试官的意思是“是否可以通过这种方式(通过数组列表)找到哈希码?” ,那么答案是否定的,因为那是荒谬的。


1
哈希码的计算是由合同规定List。没有有效的实现可以跳过自身。从规格,你可以得到,如果你找到一个int号码这x == 31 + xtrue,那么你就可以实现有效的短切......
霍尔格

我不太明白@Holger在说什么。但是解决方案存在两个问题:第一:仅当此列表是其自身的一项时才解决问题,而不是如果列表中其自身的一项(更深层次的递归)则无法解决此问题;第二:如果该列表本身跳过了它可能等于一个空列表。
乔纳斯·米歇尔

1
@JonasMichel我还没有提出解决方案。我只是在哲学上就问题2的荒谬性进行辩论。如果我们必须为此类列表计算哈希码,那么除非我们删除数组列表的约束,否则它将无法正常工作(Holger通过说List正式地表明这一点来加强这一点)。实现要遵守的哈希码逻辑)
ernest_k

我的(有限的)理解是List提供哈希码计算,但是我们可以覆盖它。唯一真正的要求是通用哈希码:如果对象相等,则哈希码必须相等。此实现遵循该要求。
Teepeemm

1
@Teepeemm List是一个接口。它定义了一个合同,它并没有提供一个实现。当然,合同涵盖了相等性和哈希码。由于平等的一般契约必须是对称的,因此,如果更改一个列表实现,则必须更改所有现有的列表实现,否则,甚至可能破坏的基本契约java.lang.Object
霍尔格

1

因为当您从相同的函数调用相同的函数时,它将创建递归的条件,该条件永远不会结束。为了防止该操作,JAVA将返回java.lang.StackOverflowError

下面是解释类似情况的示例代码:

public class RefTest {

    public static void main(String... strings) {
        RefTest rf = new RefTest();
        rf.message(message("test"));
    }

    public static String message2(String s){
        return message(s);
    }

    public static String message(String s){
        return message2(s);
    }

}   
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.