あなたはまだequalsの方法を実現しないそうですか?この文章を収蔵すれば十分だ.

22689 ワード

1、equalsを書き換える必要がある時期
Javaerたちはequalsメソッドを知っていると信じています.それはベースクラスの大物Objectの1つのメソッドなので、javaの下のすべてのクラスがこのメソッドを「持参」しています.メソッド名を見ると,意図は伝達されたターゲットオブジェクトを比較し,自分と「等しい」かどうかを示す.まず、この方法のObjectクラスでの実装を見てみましょう.
    public boolean equals(Object obj) {
        return (this == obj);
    }


この実現も簡単で乱暴で、直接「===」で目標の対象と比較して、この意図は:相手が自分自身でない限り、等しくありません.しかし、このような乱暴さのため、限定が死にすぎた.実際の開発では,本来のequalsが業務のニーズを満たすことができない可能性がある.したがって、Integerで書き換えられたequalsメソッドなどの書き換えが必要です.Integerは、ターゲットオブジェクトが「自己本体」であることを強制するのではなく、自分で包装されたint基本タイプの数値を比較するだけで、実際のビジネス要件に合致していることがわかります.
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();//          
        }
        return false;
    }

equalsメソッドをいつ書き換える必要があるかについては、Effective Javaで次のように記述されています.
クラスに独自の「論理等しい」概念があり、スーパークラスがequalsを上書きしていない場合.これは通常、値クラスの場合です.値クラスは、IntegerやStringなどの値クラスを表すだけです.プログラマはequalsメソッドを使用して値オブジェクトの参照を比較する場合、同じオブジェクトを指しているかどうかを理解するのではなく、論理的に等しいかどうかを知りたいと思っています.
この言葉は少し拗ねて訳されていますが、意味ははっきりしていて、いつequalsを書き直す必要があるのか、以下の2つにまとめることができます.
  • **クラスは特定の「値」のみを表します.**たとえばDouble、Integer、Longなどのパッケージタイプですが、これらのクラスはどんなに複雑でも、タイプと数値が等しい限り、オブジェクトは等しいはずです.
  • **ビジネスロジックが同じオブジェクト.**これは,第一点の延申,すなわち対象間のメンバ変数,行為表現が一致している場合でも,同じ対象として理解されるべきである.例えば1つの商品類Goodsは、その商品ID、商品名などの重要な情報が等しい限り、同じ商品である.

  • 2.equalsメソッドが守るべき約束を書き換える
    2.1 equalsエラーの書き換えの例
    equalsメソッドの書き換えは簡単そうに見えますが、予想外のエラーを引き起こす書き換え方法がたくさんあります.jdkのエラーの例を挙げてjava.sql.Timestampはjavaから継承する.util.Dateの,この2つのクラスはそれぞれequalsメソッドを書き換えた.見てみましょう
    java.util.Date.equals:
      public boolean equals(Object obj) {
            return obj instanceof Date && getTime() == ((Date) obj).getTime();
      }
    

    java.sql.Timestamp:
        public boolean equals(java.lang.Object ts) {
          if (ts instanceof Timestamp) {
            return this.equals((Timestamp)ts);
          } else {
            return false;
          }
        }
    

    この2つのクラスのequalsメソッドは、それぞれ単独で使用する場合は問題ありませんが、DateとTimestampの2つのクラスを一緒に使用すると問題があります.たとえば、2つのクラスのオブジェクトを1つの集合リストに保存すると、問題が発生します.
    	static List<java.util.Date> dateList = new ArrayList<>();
    	public static void addTimeObj(java.util.Date date) {
    		if(!dateList.contains(date)) {
    			dateList.add(date);
    		}
    	}
    	
    	public static void main(String[] args) {
    		long currentTimeMillis = 1586669016742L;
    		java.sql.Timestamp timestamp = new Timestamp(currentTimeMillis);
    		Date date = new java.util.Date(currentTimeMillis);
    		
    		System.out.println(date.equals(timestamp));//true
    		System.out.println(timestamp.equals(date));//false
    		
    		addTimeObj(timestamp);
    		addTimeObj(date);//date      
    		
    	}
    

    上のコードから分かるように、date.equals(timestamp)はtrue,timestampである.equals(date)はfalseであるが、これは以下に述べる対称性に反するため、ArrayListのため、2番目のaddTimeObjメソッドの呼び出しなど、予想外の結果を生じる可能性がある.containsはオブジェクトのequals法を用いて比較するがtimestamp.equals(date)==false.
         public boolean contains(Object o){
            //...      
            for (int i = 0; i < size; i++)
                    if (o.equals(elementData[i]))//timestamp.equals(date)==false
                        return i;           
           return false;
            //...      
         }
    

    2.2 equalsが守らなければならない「軍規」を書き直す
    したがって、equalsを書き換えるには、一定の制約があります.『Effective Java』では、書き換えるために従わなければならない「軍規」:
  • 自己反転:null以外の参照値xに対して、x.equals(x)はtrueを返さなければならない.
  • 対称性:null以外の参照xおよびyについて、y.equals(x)がtrueを返す場合にのみ、x.equals(y)はtrueを返さなければならない.
  • 伝達性:null以外の参照値x、y、zについて、x.equals(y)がtrueを返し、y.equals(z)がtrueを返すと、x.equals(z)がtrueを返す.
  • コンシステンシ:null以外の参照値xとyについて、equalsの比較操作オブジェクトで使用される情報が変更されていない限り、x.equals(y)を複数回呼び出すとtrueが一致して返されるか、falseが一致して返される.
  • null以外の参照xの場合、x.equals(null)はfalseを返さなければならない.

  • 以上のいくつかの規定は、そのうちの1つに違反すると、思いがけない結果をもたらす可能性があり、プログラムが正常に表現されず、崩壊する可能性があります.上のようにutil.Dateとjavasql.Timestampの例では、このようなバグは検出しにくい.
    3、equalsメソッドを正しく、迅速に書き換える方法
    equalsメソッドを書き換えるいくつかの制約を知った後、equalsメソッドを実現するのはどうしてそんなに難しいと思いますか?実際,equalsメソッドを実現するには,難しくないが,実際の開発では,以下の2つの迅速で安定した方法がある.
    3.1 IDEによる自動生成equals
    現在のIDEには、eclipse、「Alt+Shift+S」結合キー->Generate hashCode()and equals()のようなequalsメソッドの迅速な生成方法があり、hashCodeメソッドとequalsメソッド*(equalsメソッドとhashCodeメソッドが結合されていることもわかり、equalsメソッドを実現するには、hashCodeメソッドも実現しなければならない)*
    public class Graph {
    
    	int n;
    	
    	LinkedList<Integer> [] table;
    
       //....     hashCode     
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj)
    			return true;
    		if (obj == null)
    			return false;
    		if (getClass() != obj.getClass())
    			return false;
    		Graph other = (Graph) obj;
    		if (n != other.n)
    			return false;
    		if (!Arrays.equals(table, other.table))
    			return false;
    		return true;
    	}
    }
    
    

    上記のequalsメソッドはeclipseによって生成され(紙面の原因はhashCodeのコードを省略している)、大まかに見ても完備しているように見えますが、実際に開発中のビジネスニーズに合致するとは限らず、少し肥大化しています.だから、普段の開発でIDEで生成されるこのコードは、実際の業務に適応するために調整する必要があることが多い.もちろん、上記のいくつかの軍規に従うことが前提だ.
    3.2 equalsメソッドを書き換えるためのいくつかのコツ(手順)
    IDEはequalsメソッドを生成するのは便利ですが、生成されたコードは「鶏肋」を比較する場合があります.そのため、軍規に従った上で、自分でequalsメソッドを素早く手書きで書くことができますか?もちろん、以下の手順で一歩一歩実現することができます.
  • *==**オペレータを使用して、パラメータがこのオブジェクトの参照であるかどうかを確認します.もしそうであればtrueを返します.
  • instanceofオペレータを使用して、パラメータが正しいタイプかどうかを確認します.そうでない場合はfalseを返します.
  • パラメータを正しいタイプに変換します.2ステップ目のininstanceofチェックを通過したため、タイプは変換に成功します.
  • クラスの各「キードメイン」について、パラメータ内のドメインがオブジェクト内の対応するドメインと一致しているかどうかをチェックします.ドメインをチェックするときは、パフォーマンスオーバーヘッドが最も低いドメイン、または論理が最も一致しない可能性が高いドメインをチェックする必要があります.このような順序で比較の回数をできるだけ減らすことで、パフォーマンスを向上させることができます.
  • equalsを記述した後、対称的、伝達的、一致しているかどうかの3つの問題を考慮し、検証する必要があります.

  • この5つのステップに基づいて、書かれたequalsこそ安全で信頼性があり、以下にGoodsクラスを構築し、そのequalsメソッドとhashCodeメソッドを実現し、最後にその対称性、伝達性、一致性を検出することができます!
    public class Goods {
    
    	int id;	
    	String goodsName;
    	
    	@Override
    	public boolean equals(Object obj) {
    		//  ==                 。   ,   true。
    		if(this == obj) 
    			return true;
    		
    		//  instanceof               。    ,  false。
    		if(!(obj instanceof Goods)) 
    			return false;
    		
    		//          。
    		Goods target = (Goods)obj;
    		
    		/*
    		 *        ”   “,                      。
    		 *        ,             ,            ,
    		 *                      ,      。
    		 */
    		if(this.id != target.id)
    			return false;
    		if(target.goodsName == null || !target.goodsName.equals(this.goodsName))
    			return false;
    		
    		return true;
    	}
        
        @Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((goodsName == null) ? 0 : goodsName.hashCode());
    		result = prime * result + id;
    		return result;
    	}
        
        
    }
    

    4、最後に書く
    equalsメソッドを上書きするには厳密で、やむを得ない場合でなければ、equalsメソッドを簡単に書き直さないでください.書き換える場合は、このクラスのすべてのキードメインを比較し、equalsのいくつかの軍規に従っているかどうかを確認する必要があります.さらにもう一つ重要なのは、equalsメソッドを書き換えると、hashCodeメソッドを同期して実現しなければならないことです.そうしないと、hashCodeの共通の約束に違反します.hashCodeがどのように書き換えるかについては、姉妹編「効率的なhashCodeメソッドを実現する方法」を参照してください.
    equalsメソッド、今日はマスターしましたか?