自身を要素として含むArrayListのハッシュコード


38

我々は見つけることができるhashcodeのをlist、自身が含まれていますかelement

私はこれが悪い習慣であることを知っていますが、これは面接官が尋ねたものです。

次のコードを実行すると、がスローされますStackOverflowError

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

ここで私は2つの質問があります:

  1. なぜStackOverflowErrorですか?
  2. この方法でハッシュコードを見つけることは可能ですか?

7
リストを自分自身に追加するからです。addステートメントなしでa.hashCode()を試してください
Jens

オブジェクトをarraylistに入れると、オブジェクトへの参照が格納されます。この場合、ArrayListウィッチ自体を参照にします。
Vishwa Ratna


わかりました。stackoverflowがある理由を理解しました。問題番号2を説明するのに役立つ人がいますか。これを見つける方法
Joker

9
他の人が答えたように、これは不可能です。Listインターフェイスの定義hashCode自体によって、リストのはそのメンバーに依存します。リストが独自のメンバーであることを考えると、ハッシュコードはに依存しますhashCode。これはhashCode...に依存します。これにより、無限の再帰が発生し、StackOverflowError実行されます。ここで問題は、なぜリストにそれ自体を含める必要があるのでしょうか。このような再帰的なメンバーシップを必要とせずに、あなたがしようとしていることをより良い方法で達成できることを保証できます。
アレクサンダー-

回答:


36

適合するList実装のハッシュコードは、インターフェイスで指定されています

このリストのハッシュコード値を返します。リストのハッシュコードは、次の計算の結果として定義されます。

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

この性を保証list1.equals(list2)ことを意味list1.hashCode()==list2.hashCode()任意の二つのリストのために、list1list2の一般規約によって要求されるように、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に戻って)、すべての実装で合意するには、それを明確に定義する必要があります。
Holger

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この方法を見つけることができません。


だから答えはこの方法でハッシュコードを見つける方法はないのですか?
ジョーカー

3
はい、再帰的条件のため
1

それだけでなく、このように定義されています。
クリリス

14

自分自身を含む(病理学的)リストを定義しました。

なぜあるのか StackOverflowErrorか?

javadocs(つまり仕様)によると、List aのハッシュコードは、その各要素のハッシュコードの関数に定義されています。それは言う:

「リストのハッシュコードは、次の計算の結果として定義されます。」

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

したがって、のハッシュコードを計算するにはa、最初にのハッシュコードを計算しますa。これは無限に再帰的であり、すぐにスタックオーバーフローにつながります。

この方法でハッシュコードを見つけることは可能ですか?

いいえ。上記のアルゴリズム仕様を数学的に考えると、Listそれ自体を含むaのハッシュコードは計算できない関数です。この方法(上記のアルゴリズムを使用)または他の方法で計算することはできません。


説明でOPの質問に実際に回答するため、この回答が他の2つよりも低い理由はわかりません。
ネイト

1
@Neyt一部のユーザーは上部の回答のみを読みます。
Holger

8

いいえ、ドキュメントには答えがあります

リスト構造ドキュメンテーションは明示的に述べています:

注:リスト自体を要素として含めることは許可されていますが、極端な注意が必要です。equalsメソッドとhashCodeメソッドは、このようなリストで定義されなくなっています。

それ以上は言うことはありません-Java仕様によると、それ自体を含むリストのhashCodeを計算することはできません。他の答えはなぜそうなのかを詳しく説明しますが、要点はそれが既知で意図的であるということです。


1
仕様外である理由を説明したので、バグではないことが説明されています。それは他の答えから欠けている部分でした。
1

3

ラビンドラの回答は、ポイント1についての適切な説明を提供します。質問2についてコメントするには:

この方法でハッシュコードを見つけることは可能ですか?

ここでは何かが循環しています。これらの2つのうち少なくとも1つは、このスタックオーバーフローエラーのコンテキストで間違っている必要があります。

  • リストのハッシュコードはその要素のハッシュコードを考慮に入れなければならないこと
  • リストが独自の要素であっても問題ないこと

さて、を扱っている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 + xですがtrue...、そしてあなたは、有効なショートカットを実装することができます
ホルガー

@Holgerの発言がよくわかりませんでした。しかし、解決策には2つの問題があります。1つ目は、このリストがそれ自体のアイテムである場合にのみ問題を解決します。リストがそれ自体のアイテムのアイテム(再帰のより深い層)である場合は解決しません2つ目:リストがそれ自体をスキップした場合空のリストと同じかもしれません。
Jonas Michel

1
@JonasMichel私は完全に解決策を提案しませんでした。私は哲学的に質問2の不条理について議論しています。そのようなリストのハッシュコードを計算する必要がある場合、配列リストの制約を削除しない限り機能しません(そしてホルガーはList正式にそれを指示すると言ってそれを強化します実装によって準拠するハッシュコードロジック)
ernest_k

私の(限られた)理解はList、ハッシュコードの計算を提供しますが、それを上書きできることです。唯一の実際の要件は、一般的なハッシュコードの要件です。オブジェクトが等しい場合、ハッシュコードは等しくなければなりません。この実装はその要件に従います。
Teepeemm

1
@Teepeemm Listはインターフェースです。コントラクトを定義し、実装を提供しませ。もちろん、契約は等価性とハッシュコードの両方をカバーしています。等式の一般的な規約は対称でなければならないということなので、1つのリストの実装を変更すると、既存のすべてのリストの実装を変更する必要がありjava.lang.Objectます。
Holger

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);
    }

}   
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.