Javaの配列がequals()をオーバーライドしないのはなぜですか?


8

私はHashSet先日、これを仕様書に書いているので作業していました:

[add()]このセットに要素e2が含まれていない場合、指定された要素eをこのセットに追加します(e == null?e2 == null:e.equals(e2))。

私が使っていたchar[]中でHashSet、私は、この契約に基づいて、以下のことを実現するまで、それはよりも良いではなかったですArrayList!オーバーライドされていないを使用しているため.equals()、私の配列は参照の等価性のみがチェックされますが、これは特に有用ではありません。私はそれArrays.equals()が存在することを知っていますが、などのコレクションを使用しているときは役に立ちませんHashSet

だから私の質問は、Java配列が等号をオーバーライドしないのはなぜですか?


3
ネイティブの配列とコレクションを混在させることはあまりお勧めできません
ラチェットフリーク

1
それが問題の原則だと思います。配列は、可変変数に次いで2番目に基本的な命令型データ構造です。これはn、それぞれがT順次接着されるタイプの値を保持するのに十分な大きさの単なるメモリスロットです。これらは、より洗練された一時的なコレクションの構成要素です。あなたが欲しかったのはでしたList
ドバル2014年

1
@ratchetfreak私はよく人々がそう言うのを聞いたことがありますが、決して理由はありません。なぜそれが悪い考えなのですか?
Richard Tingle 2014年

@RichardTingle配列は常にジェネリックスとうまく混合するわけではなく、配列は反復可能ではありません。それらのtoString()表現はほとんど役に立たず、1つをに使用する利点はほとんどありませんArrayList
ドバル2014年

@Dovalしかし、これらはすべて配列をまったく使用しない理由です(私は95%の確率で同意します)。配列は3Dグラフィックスでうまく機能するため、配列を使用する必要がある場合がありますが、キーをそれらにマッピングしたいです。だからMap<Key, int[]>いつも自然に感じてきました。しかし、私は恐ろしい何かが私を待っていることにいつも緊張しています
Richard Tingle

回答:


8

Javaの早い段階で行う設計上の決定がありました。

配列はプリミティブですか?またはそれらはオブジェクトですか?

答えは、どちらでもない...または別の見方をすれば両方です。それらはシステム自体とjvmのバックエンドとかなり密接に連携します。

この1つの例は、任意のタイプの配列を取る必要のあるjava.lang.System.arraycopy()メソッドです。したがって、配列は何かを継承できる必要があり、それはオブジェクトです。また、arraycopyはネイティブメソッドです。

彼らは(プリミティブを保持できるという点で、配列はおかしいでもあるintchardouble他のコレクションはオブジェクトのみを保持することができますが。見て、例えば、で...、などjava.util.Arraysや方法に等しいの醜い。これは、中に入れました。後の考えとして、 deepEquals(Object []、Object [])は1.5まで追加されませんでしたが、残りのArraysクラスは1.2で追加されました。

これらのオブジェクトは配列であるため、メモリレベルまたはメモリレベルに近いいくつかのことを実行できます。Javaは多くの場合、コーダーから隠しているものです。これにより、オブジェクトモデルをほとんど壊すことを犠牲にして、特定の処理をより高速に実行できます。

システムの早い段階で、柔軟性とパフォーマンスの間でトレードオフがありました。パフォーマンスは勝ち、柔軟性の欠如はさまざまなコレクションに含まれていました。Javaの配列は、必要なときにシステムを操作することを目的としたプリミティブ型の上に(当初は)薄く実装されたオブジェクトです。

ほとんどの場合、生の配列は、元の設計者が無視してシステムのみに隠そうとしたものでした。そして、彼らはそれが高速であることを望んでいました(初期のJavaは速度にいくつかの問題がありました)。配列がいい配列ではないことは設計上いぼでしたが、システムにできるだけ近いものを公開したいときに必要でした。さらに言えば、初期のJavaの現代の言語にもこのいぼがあり.equals()、C ++の配列に対しては実行できません。

JavaとC ++はどちらも配列に対して同じパスをとりました-配列ではなく配列に対して必要に応じて操作を実行する外部ライブラリ...そして、彼らが本当に何をしているのか、なぜそうなのかを理解していない限り、より良いネイティブ型を使用するようにコーダーに提案しますそのようにしています。

したがって、配列に.equalsを埋め込む方法は間違っていますが、C ++からのコーダーが知っていたのと同じ問題です。したがって、パフォーマンスの点で最も間違いのないものを選択しました-オブジェクトの実装のままにしておきます。2つのオブジェクトは、それらが同じオブジェクトを参照している場合にのみ等しくなります。

ネイティブバインディングと通信できるようにするには、配列をプリミティブのような構造にする必要があります。これは、従来のC配列に可能な限り近いものです。ただし、他のプリミティブとは異なり、参照として、したがってオブジェクトとして渡すことができる配列が必要です。つまり、いくつかのオブジェクトハックと境界チェックを備えたプリミティブです。


+1と答えてくれてありがとう。これは私が本当に探していたもので、決定の背後にあるデザインの視点でした。それが悪い考えだというだけではありません。
Azar、2014年

1
@Azar C2でいくつかの議論があります。Java配列はファーストクラスオブジェクトである必要があります。これには、配列を使用して舞台裏で行われるハッカーのいくつかを示すコードが含まれています...配列が素敵なオブジェクトではないことを嘆く他のものと一緒に。

2
幸い、C ++では、よりスマートな配列クラスを作成するためにボクシングは必要ありません。Javaはそれほど幸運ではありません。
Thomas Eding、2014年

3

Javaでは、配列は疑似オブジェクトです。オブジェクト参照は配列を保持でき、標準のObjectメソッドを持っていますが、実際のコレクションと比較すると非常に軽量です。配列はオブジェクトの契約を満たすために必要なだけやるとのデフォルトの実装を使用しequalshashCodeし、toString非常に慎重に。

を検討してくださいObject[]。この配列の要素は、別の配列を含む、オブジェクトに収まるものであれば何でもかまいません。ボックス化されたプリミティブ、ソケットなど、何でもかまいません。その場合、平等とはどういう意味ですか?まあ、それは実際に配列にあるものに依存します。これは、言語が設計されたときの一般的なケースでは知られていません。等価性は、配列自体とその内容の両方によって定義されます

これが、Arrays等しい(深い等号を含む)、ハッシュコードなどを計算するメソッドを持つヘルパークラスがある理由です。しかし、これらのメソッドは、それらが何をするかについては明確に定義されています。異なる機能が必要な場合は、独自のメソッドを記述して、プログラムのニーズに基づいて2つの配列が等しいかどうかを比較します。


厳密にはあなたの質問への回答ではありませんが、配列の代わりにコレクションを実際に使用するべきだと言うのは妥当だと思います。配列を必要とするAPIと接続する場合のみ、配列に変換します。それ以外の場合、コレクションは型の安全性が高く、明確に定義されたコントラクトを提供し、一般に配列よりも使いやすいです。


配列は実際のオブジェクトです。それらが唯一のマルチエレメント集約タイプであるという事実は、他のフォームの可変サイズコレクションは、配列またはそれ以外のO(N)オブジェクトのいずれかによってサポートされる必要があることを意味するため、比較すると、配列は「軽量」になります。より根本的な問題は、配列が軽量であるということではなく、配列を使用する方法が非常に多いということです。以下の私の答えを参照してください。
スーパーキャット2014年

1
「その場合、等価とはどういう意味ですか?」-um、配列が同じ長さであることに加えて、ソースとターゲットの両方のすべてのインデックスにあるすべてのオブジェクトは、equals()の規約に従って等しい必要がありますか?あなたが期待するもののようであり、Arrays.equals()によって実装される正確なコントラクトです。
Jeffrey Blattman、2014

@JeffreyBlattman-オブジェクト配列にそれ自体が含まれている場合はどうなりますか?
Jules

@JeffreyBlattmanは、配列の参照の等価性を実装しましたがArrays.equals()、深い等価性を提供した言語の作成者にそれを取り上げます。

@Jules「オブジェクト配列にそれ自体が含まれている場合、どうなりますか?」オブジェクトにそれ自体が含まれている場合にも同じことが起こります。単純なイコールを実装すると、スタックオーバーフローが発生します。
Jeffrey Blattman、2015

1

配列のオーバーライドの根本的な問題equalsは、のような型の変数がint[]少なくとも3つの根本的に異なる方法で使用される可能性があることequalsです。特に、タイプのフィールドint[]...

  1. ...値のシーケンスを配列にカプセル化し、変更されることはありませんが、変更しないコードと自由に共有できます。

  2. ...所有者が自由に変更できる整数保持コンテナの排他的所有権をカプセル化できます。

  3. ...他のエンティティがその状態をカプセル化するために使用している整数保持コンテナを識別し、そのため他のエンティティの状態への接続として機能します。

クラスが持っている場合int[]、フィールドfoo最初の二つの目的のいずれかのために使用され、次いで、インスタンスはxyみなすべきx.fooy.foo同じ状態をカプセル化するように、それらは数字の同じ配列を保持している場合、フィールドが第三の目的のために使用されている場合、しかし、その後、x.foo及びy.fooそれらが識別する場合のみ、同じ状態をカプセル化するであろう同一の配列を [等しい即ち、それらいる参照]。Javaが上記の3つの使用法に異なるタイプを含みequals、参照がどのように使用されているかを識別するパラメーターを取った場合int[]、最初の2つの使用法にはシーケンスの等価性を使用し、3番目の使用法には参照の等価性を使用するのが適切でした。ただし、そのようなメカニズムは存在しません。

int[]ケースが最も単純な種類の配列であったことにも注意してください。Objectまたは配列型以外のクラスへの参照を含む配列の場合、追加の可能性があります。

  1. 決して変更されないものをカプセル化する共有可能な不変の配列への参照。

  2. 他のエンティティが所有するものを識別する共有可能な不変の配列への参照。

  3. 決して変更されないものへの参照をカプセル化する、独占的に所有されている配列への参照。

  4. 専有アイテムへの参照をカプセル化する専有配列への参照。

  5. 他のエンティティによって所有されているものを識別する、独占的に所有されている配列への参照。

  6. 他のエンティティが所有する配列を識別する参照。

1、3、および4の場合、対応する項目が「値が等しい」場合、2つの配列参照は等しいと見なされます。ケース2および5では、2つの配列参照が同じオブジェクトのシーケンスを識別する場合、それらは等しいと見なされます。ケース6では、2つの配列参照は、同じ配列を識別する場合にのみ等しいと見なされます。

以下のためにequals集約型で賢明に振る舞うように、彼らは参照が使用されるかを知ることのいくつかの方法を持っている必要があります。残念ながら、Javaの型システムにはそれを示す方法がありません。


-2

配列equals()をオーバーライドhashCode()してコンテンツに依存すると、コレクションに似たものになります-非定数の可変タイプhashCode()hashCode()ハッシュテーブルや、hashCode()固定値に依存する他のアプリケーションに格納すると、変化のある型の動作が低下します。

Set<List<Integer>> data = new HashSet<List<Integer>>();
List<Integer> datum = new ArrayList<Integer>();
datum.add(1);
data.add(datum);
assert data.contains(datum); // true
datum.add(2);
assert data.contains(datum); // false, WAT???

一方、配列には簡単なhashCode()があり、ハッシュテーブルキーとして使用でき、変更可能です。

Set<int[]> data = new HashSet<int[]>(67);
int[] datum = new int[]{1, 2};
data.add(datum);
System.out.println(data.contains(datum)); //true
datum[0] = 78;
System.out.println(data.contains(datum)); //true
//PROFIT!!!

3
そこに配列はありません。int[]型の配列。

@MichaelT、それがポイントです。
バシレフ2014年

なぜ反対票?回答者は多くの仕事をしました。何か間違えている?
Tom Au

3
@TomAu問題は、Javaでの配列の設計とその疑似オブジェクトの性質、およびその選択の背後にある設計上の決定(配列が深い等価ではなく参照の等価のみを使用するようにする)に関するものです。一方、この回答は、コードがどのように書き直されるかについてのコードソリューションを提示しようとするものです。これは質問が尋ねるものではありません。

@MichaelT、これは、状態に依存するものに対するアイデンティティベースの等価ロジックの熟練度を示しています。
バシレフ2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.