web-dev-qa-db-ja.com

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

Eclipseを使用して.equals().hashCode()を生成していますが、「 'instanceof'を使用して型を比較す​​る」というラベルのオプションがあります。デフォルトでは、このオプションはオフになっており、.getClass()を使用して型を比較します。 instanceofよりも.getClass()を優先すべき理由はありますか?

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に失敗するため、冗長です。)悪い考えである理由はありますか?

166
Kip

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

95
erickson

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

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

this SO answer も参照してください。

Effective Java chapter もこれをカバーしています。

167
Michael Myers

Angelika Langers Secrets of equals は、以下を含むいくつかの一般的でよく知られた例について詳細に議論しますJosh BlochとBarbara Liskovによって、それらのほとんどでいくつかの問題が発見されました。彼女はinstanceof vs getClassにも参加します。それからの引用

結論

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

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

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

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

getClassを使用する理由は、equalsコントラクトの対称プロパティを確保するためです。 equalsのJavaDocsから:

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

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

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

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

Equalの契約に厳密に従うには、対称性を確保する必要があります。したがって、クラスが同じである必要があります。

56
Steve Kuo

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

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

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

  • 項目17:継承または禁止するための設計と文書化
26
McDowell

間違っている場合は修正してください。ただし、インスタンスが比較対象のクラスのサブクラスではないことを確認する場合は、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
21

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

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

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

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

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

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

class Organism
{
}

class Gorilla extends Organism
{
}

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

5
Pierre

instanceofは同じクラスのインスタンスで動作しますorそのサブクラス

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

ArryaListとRoleListは両方ともinstanceof List

ながら

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

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

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

3
OscarRyz

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

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

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

実際には、前者の場合のほうが本当らしいと思います。通常、サブクラス化はあなたのアイデンティティの基本的な部分であり、あなたが1つだけのことができてもあなたが平等にならないことを除いて、あなたの親とまったく同じです。

2
James