web-dev-qa-db-ja.com

HashSetをJavaでそれ自体に追加できるようにする必要がありますか?

JavaのSetの契約によれば、「セットがそれ自体を要素として含むことは許可されていません」( source )。ただし、以下に示すように、オブジェクトのHashSetの場合はこれが可能です。

Set<Object> mySet = new HashSet<>();
mySet.add(mySet);
assertThat(mySet.size(), equalTo(1));

このアサーションはパスしますが、動作は結果セットを0にするか、例外をスローするかのいずれかになると予想されます。 HashSetの基礎となる実装はHashMapであることに気付きましたが、その契約に違反しないように要素を追加する前に同等性チェックが必要なようです。

52
davidmerrick

ラッセルのパラドックス を参照することで、数学的な観点から疑わしい理由をすでに指摘している人もいます。

ただし、これはtechnicalレベルの質問には答えません。

これを分析してみましょう:

まず、 SetインターフェースのJavaDoc: から関連する部分をもう一度

注:可変オブジェクトをセット要素として使用する場合は、細心の注意が必要です。オブジェクトがセット内の要素であるときに、等しい比較に影響する方法でオブジェクトの値が変更された場合、セットの動作は指定されません。この禁止の特殊なケースは、セットが要素として自身を含むことは許可されないことです。

興味深いことに、 ListインターフェースのJavaDoc は、似たようなものですが、やや弱めであると同時に、より技術的な説明をしています:

リストがそれ自体を要素として含むことは許されますが、極度の注意が推奨されます:equalshashCodeメソッドはそのようなリストではもはや十分に定義されていません。

そして最後に、核心は CollectionインターフェースのJavaDoc にあり、これはSetListインターフェースの両方の共通の祖先です:

コレクションの再帰トラバーサルを実行する一部のコレクション操作は、コレクションが直接または間接的に自身を含む自己参照インスタンスの例外で失敗する場合があります。これには、clone()equals()hashCode()、およびtoString()メソッドが含まれます。実装は、自己参照シナリオをオプションで処理できますが、現在のほとんどの実装は処理しません。

(私によるエンファシス)

太字の部分は、質問で提案したアプローチが十分ではない理由のヒントです。

その契約への違反を避けるために、要素を追加する前に同等性チェックが必要なようです。

これはここでは役に立ちません。重要な点は、コレクションが直接的または間接的に含まれる場合、常に問題が発生することです。このシナリオを想像してください:

Set<Object> setA = new HashSet<Object>();
Set<Object> setB = new HashSet<Object>();
setA.add(setB);
setB.add(setA);

明らかに、どちらのセットにも自分自身は含まれていません直接。しかし、それらのそれぞれには他の-と、したがって、それ自体indirectlyが含まれています。これは、単純な参照等価チェック(addメソッドで==を使用)で回避できませんでした。


このような「矛盾した状態」を回避することは、実際には基本的に不可能です。もちろん、理論的には、参照 Reachability 計算を使用して可能です。実際、ガベージコレクターは基本的にそれを正確に行う必要があります!

しかし、それは不可能になります実際カスタムクラスが関係する場合。次のようなクラスを想像してください:

class Container {

    Set<Object> set;

    @Override 
    int hashCode() {
        return set.hashCode(); 
    }
}

そして、これとそのsetをいじります:

Set<Object> set = new HashSet<Object>();
Container container = new Container();
container.set = set;
set.add(container);

addSetメソッドには、そこに追加されたオブジェクトにセット自体へのsome(間接)参照があるかどうかを検出する方法が基本的にありません。

長い話:

プログラマが物事を台無しにするのを防ぐことはできません。

52
Marco13

コレクションをそれ自体に追加すると、onceがテストに合格します。それを追加するtwiceは、探していたStackOverflowErrorを引き起こします。

個人的な開発者の観点からは、これを防ぐために基礎となるコードでチェックを実施することは意味がありません。これを何度も実行しようとすると、コードでStackOverflowErrorを取得するか、hashCode(瞬時のオーバーフローを引き起こす)を計算すると、正気でないことを確認するのに十分です。開発者はこの種のコードをコードベースに保持します。

22
Makoto

完全なドキュメントを読み、完全に引用する必要があります。

セットの動作指定されていないオブジェクトがセット内の要素であるときに等価比較に影響する方法でオブジェクトの値が変更された場合。この禁止の特別な場合は、セットが要素として自身を含むことは許可されないことです。

実際の制限は最初の文にあります。セットの要素が変更された場合の動作は、unspecifiedです。

セットをそれ自体に追加すると変更され、再度追加すると再び変更されるため、結果は不定です。

制限は、動作がunspecifiedであり、特別な場合その制限は、セットをそれ自体に追加することです。

つまり、このドキュメントでは、セットをそれ自体に追加すると、不特定の動作が発生する、と言っています。対処する(またはしない)具体的な実装次第です。

12
Polygnome

数学的な観点から見ると、この振る舞いは本当に意味をなさないことに同意します。

ここには2つの興味深い質問があります。まず、Setインターフェースの設計者はどの程度まで数学セットを実装しようとしましたか?第二に、たとえ彼らが were n't であったとしても、それはセット理論のルールからどの程度まで免除されますか?

最初の質問については、セットの documentation を示します。

重複する要素を含まないコレクション。より正式には、セットにはe1.equals(e2)などの要素e1とe2のペアは含まれず、最大1つのnull要素が含まれます。 その名前が示すように、このインターフェイスは数学的なセットの抽象化をモデル化します。

集合論の現在の定式化は集合が自分自身のメンバーになることを許可しないことをここで言及する価値がある。 ( 規則の公理 を参照)。これは、一部は ラッセルのパラドックス によるもので、これは 素朴な集合論 (集合を any オブジェクトのコレクション-自分自身を含むセットに対する禁止事項はありませんでした)。これは、多くの場合、 Barber Paradox :によって示されます。特定の町で、理髪師がすべての男性を剃る-および only 男性-剃っていない。質問:理容師は自分自身を剃りますか?もしそうなら、それは2番目の制約に違反します。そうしないと、最初の制約に違反します。これは明らかに論理的に不可能ですが、実際には単純な集合論のルールの下では完全に許容されます(集合論の新しい「標準」定式化は集合の包含を明示的に禁止します)。

Math.SEに関するこの質問 には、セットがそれ自体の要素になれない理由についての詳細な議論があります。

そうは言っても、2番目の質問があります。デザイナーが /数学セットを明示的にモデル化しようとしても、関連する問題から完全に「免除」されますか素朴な集合論?私はそうは思いません-素朴な集合論を苦しめた問題の多くは、素朴な集合論に類似した方法で十分に制約されていないコレクションの種類を悩ませると思います any 。実際、私はこれを読みすぎているかもしれませんが、ドキュメンテーションのSetの定義の最初の部分は、ナイーブセット理論のセットの直感的な概念のように疑わしく聞こえます。

重複する要素を含まないコレクション。

確かに(そして彼らの名誉のために)、彼らはこれに少なくとも some 制約を設定します(実際にはSetにそれ自身を含めてはならないということを述べます)が、素朴な集合論の問題を回避するのに本当に「十分」かどうかを疑問視する。これが、たとえば、自分自身を含むHashSetのハッシュコードを計算しようとしたときに、「亀がずっと下にいる」問題を抱えている理由です。これは、他の一部の人が示唆しているように、単に実際的な問題ではありません-これは、このタイプの定式化の基本的な理論上の問題の例です。

簡単な余談として、コレクションクラスが数学セットを実際にどの程度厳密にモデル化できるかには、もちろんいくつかの制限があることを認識しています。たとえば、Javaのドキュメントは、可変オブジェクトをセットに含めることの危険性について警告しています。 Pythonのような他の言語は、少なくとも 多くの種類の可変オブジェクトを完全に禁止する を試みます:

セットクラスは、辞書を使用して実装されます。したがって、セット要素の要件は辞書キーの要件と同じです。つまり、要素は__eq__()__hash__()の両方を定義します。 結果として、セットにはリストや辞書などの可変要素を含めることはできません。ただし、タプルやImmutableSetのインスタンスなどの不変コレクションを含めることはできます。セットのセットを実装する際の便宜のために、内部セットは自動的に不変の形式に変換されます。たとえば、Set([Set(['dog'])])Set([ImmutableSet(['dog'])])に変換されます。

他の人が指摘した他の2つの大きな違いは

  • Javaセットは変更可能です
  • Javaセットは有限です。明らかに、これは any コレクションクラスにも当てはまります。 実際の無限大 に関する懸念は別として、コンピューターのメモリは有限です。 (Haskellのような一部の言語は、レイジーな無限のデータ構造を持っていますが、私の意見では、 lawlike choice sequence は、古典集合論よりもこれらをより自然な方法でモデル化しているように見えますが、それは私の意見です).

TL; DRいいえ、セットはメンバーになれないため、実際に許可されるべきではありません(または、少なくとも、それを行うべきではありません)自分自身。

8
EJoshuaS