インスタンスは、より具体的なタイプの他のインスタンスと同等ですか?


25

私はこの記事を読みました:Javaで平等メソッドを書く方法

基本的に、継承をサポートするequals()メソッドのソリューションを提供します。

Point2D twoD   = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true

しかし、それは良いアイデアですか?これらの2つのインスタンスは同じように見えますが、2つの異なるハッシュコードを持つ場合があります。それは少し間違っていませんか?

代わりにオペランドをキャストすることで、これがよりよく達成されると思います。


1
リンクに示されている色付きのポイントの例は、私にとってより理にかなっています。2Dポイント(x、y)は、Zコンポーネント(x、y、0)がゼロの3Dポイントとみなすことができると考えます。この場合、等式がfalseを返すようにします。実際、この記事では、ColoredPointは明示的にPointとは異なると言われ、常にfalseを返します。
コアダンプ

10
一般的な慣習を破るチュートリアルほど悪いことはありません...プログラマーからこうした種類の習慣を取り除くには何年もかかります。
corsiKa

3
@coredump 2Dポイントを0 z座標を持つものとして扱うことは、一部のアプリケーションにとって便利な慣習かもしれません(レガシーデータを処理する初期のCADシステムが思い浮かびます)。しかし、それはarbitrary意的な慣習です。3次元以上の空間内の平面は、任意の方向を持つことができます...興味深い問題を面白くするのはそれです。
ベンラッジャーズ

回答:


71

これは推移性を破壊するので平等ではありません。次の2つの式を検討してください。

new Point3D(10, 20, 50).equals(new Point2D(10, 20)) // true
new Point2D(10, 20).equals(new Point3D(10, 20, 60)) // true

平等は推移的であるため、これは次の式も真であることを意味するはずです。

new Point3D(10, 20, 50).equals(new Point3D(10, 20, 60))

しかし、もちろん-そうではありません。

したがって、キャストの考え方は正しいです。Javaでは、キャストとは単に参照の型をキャストすることを意味します。ここで本当に必要なのは、Point2Dオブジェクトから新しいオブジェクトを作成する変換メソッドですPoint3D。これにより、式がより意味のあるものになります。

twoD.equals(threeD.projectXY())

1
この記事では、推移性を破る実装について説明し、さまざまな回避策を提供します。2Dポイントを許可するドメインでは、3番目の次元は重要ではないと既に決定しています。そして、(10, 20, 50)equals (10, 20, 60)は大丈夫です。私たちは気に1020ます。
ベンラッジャーズ

1
必要がありますPoint2D持ってprojectXYZ()提供する方法Point3D自体の表現を?言い換えれば、実装はお互いを知っている必要がありますか?
hjk

4
@hjk Point2D2Dポイントを投影するには、最初に3D空間で平面を定義する必要があるため、取り除くのは簡単です。2Dポイントが平面であることを知っている場合、それはすでに3Dポイントです。そうでない場合、投影できません。アボットのフラットランドを思い出します。
ベンラッジャーズ

@benrudgersただし、Plane3D3D空間で平面を定義するオブジェクトを定義できます。その平面は、「3番目の軸」のをlift受け入れるメソッド(2D-> 3Dが持ち上がり、投影ではない)を持つことができますPoint2D"-平面法線に沿った平面からの距離。あなたのようなことを行うことができるように、使用を容易にするため、あなたは、静的定数として、共通の平面を定義することができますPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
IDAN Arye

@IdanArye 2Dポイントには投影法が必要だという提案についてコメントしていました。リフト方法を使用する平面に関しては、2Dポイントと、それがオンになっていると想定される平面という2つの引数が必要だと思います。つまり、ポイントを所有していない場合は、実際に投影する必要があります。そして、それがポイントを所有しているのであれば、なぜ3Dポイントを所有し、問題のあるデータ型と巧妙なメソッドの匂いを捨てないのですか?YMMV。
ベンラッジャーズ

10

アランJ.ペルリスの知恵について考えている記事を読むことから離れます。

Epigram9。10個のデータ構造で10個の関数を使用するよりも、100個の関数を1つのデータ構造で操作する方が適切です。

「平等」を正しく実現することは、ScalaのMartin Orderskyの発明者を夜にしておくような種類の問題であるという事実equalsは、継承ツリーでのオーバーライドが良いアイデアかどうかについて一時停止を与えるべきです。

を取得できなかった場合に起こるのColoredPointは、1つの良いデータ型を作成するのではなく、継承を使用してデータ型を増殖したため、ジオメトリが失敗することです。これは、戻って機能するために継承ツリーのルートノードを変更する必要があるにもかかわらずequalsです。なぜ単にa zとa colorを追加しないのPointですか?

正当な理由は、PointそれColoredPointが異なるドメインで動作することです...少なくともそれらのドメインが混ざらない場合。ただし、その場合は、オーバーライドする必要はありませんequals。比較ColoredPointPoint平等性は、それらが交わることが許可されている第三のドメインでのみ意味があります。そして、その場合、混合ドメインの一方または他方または両方から等値セマンティクスを適用しようとするよりも、その3番目のドメインに合わせて「平等」を調整する方がおそらく良いでしょう。言い換えれば、「平等」は、6か月前の午前2時に良いアイデアだと思った著者でもColoredPoint.equals(pt)失敗しないようにするために、両側から泥が流れ込む場所のローカルで定義する必要があります。PointColoredPoint


6

古いプログラミングの神がクラスを使ってオブジェクト指向プログラミングを発明していたとき、彼らはオブジェクトに対して「is a」と「has a」という2つの関係を持つことを構成と継承に至りました。
これにより、サブクラスが親クラスと異なるという問題は部分的に解決されましたが、コードを壊すことなく使用できるようになりました。サブクラスインスタンスはスーパークラスオブジェクト「である」ため、サブクラスにさらに多くのメンバー関数またはデータメンバーがある場合でも、直接置換できるため、「持っている」ことにより、親のすべての機能を実行し、メンバー。したがって、Point3Dが「ポイント」であり、Point2Dが「ポイント」であると言えます。さらに、Point3DはPoint2Dのサブクラスになります。

ただし、クラス間の平等はドメイン固有の問題であり、上記の例は、プログラムが正しく機能するためにプログラマが必要とするものに関してあいまいです。一般に、比較の範囲をこの場合は2次元に制限すると、すべてのデータメンバーを比較する場合ではなく、数学ドメインの規則に従い、データの値は同等になります。

したがって、等式を狭める表が得られます。

Both objects have same values, limited to subset of shared members

Child classes can be equal to parent classes if parent and childs
data members are the same.

Both objects entire data members are the same.

Objects must have all same values and be similar classes. 

Objects must have all same values and be the same class type. 

Equality is determined by specific logical conditions in the domain.

Only Objects that both point to same instance are equal. 

通常、問題のドメインで必要なすべての機能を実行できる、最も厳しいルールを選択します。数値の組み込みの等値性テストは、数学的な目的のためにできる限り制限するように設計されていますが、プログラマーは、切り上げ、切り捨て、gt、ltなどを含む、それが目標でない場合、それを回避する多くの方法を持っています。タイムスタンプを持つオブジェクトは、多くの場合、生成時間によって比較されるため、各インスタンスは一意である必要があるため、比較は非常に具体的になります。

この場合の設計要素は、オブジェクトを比較する効率的な方法を決定することです。すべてのオブジェクトのデータメンバーの再帰的な比較が必要な場合があり、多くのデータメンバーを持つオブジェクトがたくさんある場合、非常に高価になる可能性があります。代替手段は、関連するデータ値のみを比較するか、オブジェクトに関連するデータメンバーのハッシュ値を生成させ、他の類似オブジェクトとの迅速な比較を行い、コレクションをソートおよびプルーニングして、比較をより速く、CPU集約度を低くし、おそらくカリングされるデータは同一であり、単一のオブジェクトへの複製ポインターがその場所に配置されます。


2

ルールは、をオーバーライドするたびにhashcode()、をオーバーライドしequals()、その逆も同様です。これが良いアイデアであるかどうかは、使用目的によって異なります。個人的にはisLike()、同じ効果を得るために別の方法(または同様の方法)を使用します。


1
equalsをオーバーライドせずにhashCodeをオーバーライドしてもかまいません。たとえば、同じ等値条件に対して異なるハッシュアルゴリズムをテストするには、これを行います。
パトリシアシャナハン

1

非公開クラスには、異なるタイプのオブジェクトが同じ情報を表す場合にお互いを「等しい」と見なすことができる等価性テストメソッドがあると便利ですが、Javaはクラスがそれぞれを偽装する手段を許可しないためその他、異なる表現を持つ同等のオブジェクトを持つことができる場合に、単一の公開用のラッパー型を持つことはしばしば良いことです。

たとえば、double値の不変の2Dマトリックスをカプセル化するクラスを考えます。外部メソッドの1つがサイズ1000の単位行列を要求する場合、2番目は対角行列を要求して1000を含む配列を渡し、3番目は2D行列を要求し、主対角要素がすべて1.0である1000x1000配列を渡しますそして、他のすべてがゼロである場合、3つのクラスすべてに与えられたオブジェクトは、異なるバッキングストアを内部的に使用できます[1つ目はサイズの単一フィールド、2つ目は1000要素の配列、3つ目は1000要素の配列]相互に同等として報告する必要があります(3つすべてが1000x1000の不変行列をカプセル化し、対角に1を、他のすべてに0を含む)

異なるバッキングストアタイプの存在を隠すという事実以外に、ラッパーは比較を容易にするのにも役立ちます。これは、同等のアイテムをチェックすることは一般にマルチステッププロセスになるためです。最初のアイテムが2番目のアイテムと等しいかどうかを確認してください。わからない場合は、2番目に、最初のものと等しいかどうかを確認します。どちらのオブジェクトも認識していない場合は、各配列に個々の要素の内容を尋ねます[長い個別のアイテム比較ルートを実行する前に他のチェックを追加する場合があります]。

このシナリオの各オブジェクトの等価テストメソッドは、3つの状態の値(「はい、私は同等です」、「いいえ、私は同等ではありません」、または「わからない」)を返す必要があります。したがって、通常の「等しい」方法は適切ではありません。他のことについて尋ねられたとき、オブジェクトは単に「わからない」と答えることができますが、例えば、対角行列にロジックを追加して、主対角線から外れた要素について単位行列または対角行列を求めないようにします。タイプ。

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