.equals()を生成するときにinstanceofよりgetClass()を優先する理由はありますか?


174

私は生成するEclipseを使用している.equals().hashCode()し、「タイプを比較するために使用『のinstanceof』」ラベルされたオプションがあります。デフォルトでは、このオプションはオフになっており、.getClass()タイプの比較に使用されます。私は好むべきで何らかの理由がある.getClass()以上はinstanceof

使用せずにinstanceof

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

使用instanceof

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

私は通常、instanceofオプションをチェックしてから、「if (obj == null)」チェックを外します。(nullオブジェクトは常に失敗するため、冗長instanceofです。)悪い考えである理由はありますか?


1
x instanceof SomeClass場合、式はfalse xですnull。したがって、2番目の構文はnullチェックを必要としません。
rds

5
@rdsはい、コードスニペットの直後の段落でも同様です。これはEclipseが生成するコードスニペットに含まれています。
キップ

回答:


100

を使用する場合instanceofequals実装をfinal行うと、メソッドの対称コントラクトが保持されますx.equals(y) == y.equals(x)final制限的と思われる場合は、オブジェクトの等価性の概念を注意深く調べて、オーバーライドする実装がObjectクラスによって確立されたコントラクトを完全に維持していることを確認してください。


上記のジョシュ・ブロックの引用を読んだとき、まさにそれが頭に浮かんだ。+1 :)
ヨハネスシャウブ-litb 2009

13
間違いなく重要な決定です。対称性が適用され、instanceofは誤って非対称であることを非常に簡単にそれを作る必要があります
スコットStanchfield

また、「final制限的と思われる場合」(equalsおよびの両方でhashCode)を使用し、コントラクトの対称性と推移性の要件を維持するためgetClass()にでinstanceofはなく、同等性を使用しますequals
シャドウマン

176

Josh Blochはあなたのアプローチを支持しています:

私がこのinstanceofアプローチを支持する理由は、このアプローチを使用する場合、getClassオブジェクトは同じクラスの他のオブジェクトと同じランタイム型であるという制限があるためです。クラスを拡張し、それにいくつかの無害なメソッドを追加する場合は、サブクラスのオブジェクトがスーパークラスのオブジェクトと等しいかどうかを確認します。オブジェクトがすべての重要な面で等しい場合でも、彼らは等しくないという驚くべき答え。実際、これはLiskov置換原理の厳密な解釈に違反しており、非常に驚くべき動作につながる可能性があります。Javaでは、ほとんどのコレクション(HashTableなど)は、equalsメソッドに基づいています。スーパークラスのメンバーをハッシュテーブルにキーとして配置し、サブクラスインスタンスを使用して検索すると、それらは等しくないため、見つかりません。

このSOの回答も参照してください。

効果的なJavaの第3章でもこれについて説明しています。


18
+1 Javaを実行しているすべての人がその本を少なくとも10回読むべきです!
アンドレ・

16
instanceofアプローチの問題は、Object.equals()の「対称」プロパティを破壊することです。これにより、x.equals(y)== trueですが、x.getClassの場合、y.equals(x)== falseになる可能性があります。 ()!= y.getClass()。絶対に必要な場合(つまり、管理オブジェクトまたはプロキシオブジェクトのequals()をオーバーライドする場合)を除いて、このプロパティを壊さないようにしています。
Kevin Sitze 2013年

8
instanceofアプローチは、基本クラスがサブクラスオブジェクト間の等価性の意味を定義する場合にのみ適切です。getClassLSPは既存のインスタンスで実行できることに関連するだけであり、どのような種類のインスタンスを構築できるかに関係しないため、使用してもLSPに違反しません。によって返されるクラスgetClassは、オブジェクトインスタンスの不変のプロパティです。LSPは、そのプロパティがそれを作成したクラス以外のクラスを示すサブクラスを作成することが可能であることを意味しません。
スーパーキャット2014年

66

アンジェリカランガーズシークレットオブイコールズは、Josh BlochやBarbara Liskovを含むいくつかのよく知られた例について長く詳細に議論し、それらのほとんどでいくつかの問題を発見しています。彼女はまたinstanceofvsに入りgetClassます。それからの引用

結論

equals()の実装の任意に選択された4つの例を分析したら、何を結論付けますか?

まず第一に、equals()の実装で型の一致のチェックを実行するには、2つの実質的に異なる方法があります。クラスでは、instanceof演算子を使用してスーパークラスオブジェクトとサブクラスオブジェクトを混合型で比較できます。または、クラスでgetClass()テストを使用して、異なる型のオブジェクトを不等として扱うことができます。上記の例は、getClass()を使用したequals()の実装が、instanceofを使用した実装よりも一般的に堅牢であることを示しています。

instanceofテストは、最終クラス、または少なくともメソッドequals()がスーパークラスでfinalである場合にのみ正しいです。後者は、サブクラスがスーパークラスの状態を拡張する必要はなく、一時的なフィールドや静的フィールドなど、オブジェクトの状態や動作に関係のない機能やフィールドのみを追加できることを本質的に意味します。

一方、getClass()テストを使用した実装は、常にequals()規約に準拠しています。それらは正しく、堅牢です。ただし、これらは、instanceofテストを使用する実装とは意味的に非常に異なります。getClass()を使用した実装では、サブクラスがフィールドを追加せず、equals()をオーバーライドしたくない場合でも、サブクラスとスーパークラスのオブジェクトを比較できません。そのような「自明な」クラス拡張は、たとえば、まさに「自明な」目的のために定義されたサブクラスにdebug-printメソッドを追加することになります。スーパークラスがgetClass()チェックによる混合型の比較を禁止している場合、自明な拡張はそのスーパークラスに匹敵しません。これが問題であるかどうかは、クラスのセマンティクスと拡張の目的に完全に依存します。


61

使用する理由getClassは、equalsコントラクトの対称性を確保するためです。イコールのJavaDocsから:

これは対称です。null以外の参照値xおよびyについて、x.equals(y)は、y.equals(x)がtrueを返す場合にのみtrueを返します。

instanceofを使用すると、対称にならない可能性があります。例を考えてみましょう:犬は動物を拡張します。動物のはequalsありませんinstanceof動物のチェックを。Dog's equalsはDogのinstanceofチェックを行います。動物aと犬dを与える(他のフィールドも同じ):

a.equals(d) --> true
d.equals(a) --> false

これは対称プロパティに違反しています。

イコールの規約を厳密に守るには、対称性を確保する必要があるため、クラスは同じである必要があります。


8
これが、この質問に対する最初の明確で簡潔な答えです。コードサンプルは千語の価値があります。
Johnride、2014

私はあなたがその日を救った他のすべての冗長な答えを調べていました。この質問のシンプル、簡潔、エレガント、そして完全に最良の答え。

1
あなたが述べたように対称性の要件があります。しかし、「推移性」の要件もあります。もしa.equals(c)およびb.equals(c)、その後、a.equals(b)(作るの単純なアプローチDog.equalsだけreturn super.equals(object)とき!(object instanceof Dog)、それは対称性に違反していないだろうが、推移性に違反する犬のインスタンスであるとき、余分なフィールドをチェック)
シャドーマン

安全のために、getClass()等価チェックを使用するinstanceofか、equalsand hashCodeメソッドとメソッドを作成するかどうかのチェックを使用できますfinal
シャドウマン

26

これは宗教的な議論のようなものです。どちらのアプローチにも問題があります。

  • instanceofを使用すると、重要なメンバーをサブクラスに追加できません。
  • getClassを使用すると、Liskov置換の原則に違反します。

Blochには、Effective Java Second Editionに関連する別のアドバイスがあります

  • 項目17:継承のための設計と文書化、またはそれの禁止

1
getClass基本クラスが、同等に比較するさまざまなサブクラスのインスタンスを作成できるようにする手段を具体的に文書化していない限り、使用については何もLSPに違反しません。どのようなLSP違反が見られますか?
スーパーキャット2014年

おそらく、これはUse getClassと言い換える必要があり、サブタイプをLSP違反の危険にさらしたままにします。Blochには、Effective Javaの例がありますここでも説明します。彼の理論的根拠は、主に、状態を追加しないサブタイプを実装する開発者にとって驚くべき動作を引き起こす可能性があるということです。私はあなたの要点を理解します-ドキュメントが鍵です。
McDowell

2
異なるクラスの2つのインスタンスがそれ自体を報告する必要があるのは、それらが共通の基本クラスまたはインターフェースから継承する場合のみです。getClass()問題のクラスが基本クラスに変換可能であるという事実を超えて、基本クラスコントラクトが意味があると見なすべきでないことを示さない限り、からの戻りはgetClass()、インスタンスが等しいために一致する必要がある他のプロパティと同様に考慮されるべきです。
スーパーキャット2014

1
@eyalzbaインスタンスクラスがequalsコントラクトにメンバーを追加した場合、instanceof_が使用されますが、これは対称等価要件に違反します。getClassサブクラスを使用すると、親タイプと同じになることはありません。とにかく等号をオーバーライドする必要があるわけではありませんが、そうする場合はこれがトレードオフになります。
McDowell

2
「継承のための設計と文書化またはそれを禁止すること」は非常に重要だと思います。equalsをオーバーライドする必要があるのは、不変の値型を作成する場合のみであると私は理解しています。この場合、クラスはfinalとして宣言する必要があります。継承がないので、必要に応じて等号を自由に実装できます。継承を許可している場合、オブジェクトは参照によって等しいかどうかを比較する必要があります。
TheSecretSquad 2015年

23

私が間違っている場合は修正してください。ただし、インスタンスが比較対象のクラスのサブクラスではないことを確認したい場合は、getClass()が役立ちます。そのような状況でinstanceofを使用した場合、次の理由でそれを知ることができません。

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true

私にとってこれが理由です
karlihnos

5

そのクラスのみが一致することを確認したい場合は、を使用しますgetClass() ==。サブクラスを一致させたい場合instanceofは、が必要です。

また、instanceofはnullとは一致しませんが、nullと比較しても安全です。したがって、nullチェックする必要はありません。

if ( ! (obj instanceof MyClass) ) { return false; }

あなたの第二の点について:彼は質問ですでにそれを述べました。「通常、instanceofオプションをチェックしてから、「if(obj == null)」チェックを外します。」
マイケルマイヤーズ

5

特定のクラスのサブクラスがその親と等しいかどうかを検討するかどうかによって異なります。

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

ここでは、LastNameをFamilyNameと比較するため、「instanceof」を使用します。

class Organism
{
}

class Gorilla extends Organism
{
}

ここでは、2つのインスタンスは同等ではないとクラスがすでに言っているため、「getClass」を使用します。


3

instanceofは、同じクラスまたはそのサブクラスのインスタンスに対して機能します

これを使用して、オブジェクトがクラスのインスタンス、サブクラスのインスタンス、または特定のインターフェースを実装するクラスのインスタンスであるかどうかをテストできます。

ArryaListとRoleListはどちらもインスタンスのリストです

ながら

getClass()== o.getClass()は、両方のオブジェクト(thisとo)がまったく同じクラスに属している場合にのみtrueになります。

したがって、比較する必要があるものに応じて、どちらか一方を使用できます。

ロジックが「1つのオブジェクトが他のオブジェクトと等しいのは、それらが両方とも同じクラスである場合のみ」である場合、ほとんどの場合、「等しい」を使用する必要があります。


3

どちらの方法にも問題があります。

サブクラスがIDを変更する場合、実際のクラスを比較する必要があります。それ以外の場合は、対称プロパティに違反します。たとえば、異なるタイプのはPerson、たとえ同じ名前であっても、同等であると見なすべきではありません。

ただし、一部のサブクラスはIDを変更せず、これらを使用する必要がありますinstanceof。たとえば、不変のShapeオブジェクトの束がある場合、Rectangle長さと幅が1のa はunitと等しくなければなりませんSquare

実際には、前者のケースが真実である可能性が高いと思います。通常、サブクラス化はあなたのアイデンティティの基本的な部分であり、あなたが1つの小さなことを行うことができることはあなたを平等にしないことを除いて、あなたの親とまったく同じです。


-1

実際にinstanceofは、オブジェクトが階層に属しているかどうかをチェックします。例:CarオブジェクトはVehicalクラスに属しています。したがって、「Vehicalの新しいCar()インスタンス」はtrueを返します。また、 "new Car()。getClass()。equals(Vehical.class)"はfalseを返しますが、CarオブジェクトはVehicalクラスに属していますが、別のタイプに分類されています。

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