equals()がfalseを返す原因を見つける
-
10-07-2019 - |
質問
equals()がfalseを返す原因を調べるにはどうすればよいですか?
私は、確実で常に正しいアプローチについてではなく、開発プロセスを支援するものについて尋ねています。現在、そのうちの1つがfalseになるまでequals()呼び出し(通常はそれらのツリー)に足を踏み入れ、その後に踏み込んで吐き出します。
オブジェクトグラフを使用して、xmlに出力し、2つのオブジェクトを比較することを考えました。ただし、XMLEncoderにはデフォルトのコンストラクターが必要で、jibxにはプリコンパイルが必要です。x-streamと単純なAPIは私のプロジェクトでは使用されません。単一のクラスまたはパッケージをテスト領域にコピーして使用することは問題ありませんが、このためにjar全体をインポートすることはできません。
オブジェクトグラフトラバーサーを自分で構築することも考えましたが、それでもできるかもしれませんが、特別なケース(順序付きコレクション、順序なしコレクション、マップなど)の処理を開始したくないのです
どのようにすればよいのでしょうか?
編集:jarを追加するのが普通のやり方だと知っています。 jarは再利用可能なユニットです。ただし、これに必要な(私のプロジェクトでの)官僚制度は結果を正当化するものではありません。デバッグとステップインを続けます。
解決
おそらく、完全なグラフ比較ではありません...各クラスにすべてのプロパティが含まれている場合を除き...(== :)を試すことができます)
hamcrestマッチャーを試してください-各マッチャーを「すべて」で作成できます。マッチャー:
Matcher<MyClass> matcher = CoreMatchers.allOf(
HasPropertyWithValue.hasProperty("myField1", getMyField1()),
HasPropertyWithValue.hasProperty("myField2", getMyField2()));
if (!matcher.matches(obj)){
System.out.println(matcher.describeFailure(obj));
return false;
}
return true;
次のようなメッセージが表示されます。「myField1の値が&quot; value&quot;しかし、「異なる値」でした」
もちろん、静的ファクトリをインライン化できます。これは、 apache-commonsを使用するよりも少し重いです。 EqualsBuilder ですが、失敗した内容を正確に説明しています。
これらの式を迅速に作成するために、独自の特殊なマッチャーを作成できます。 apache-commons EqualsBuilderをコピーするのが賢明です。 こちら。
ところで、hamcrestの基本的なjarは32K(ソースを含む!)で、コードを確認して上司に言うオプションを提供します&quot;私はこれを自分のコードとして待機します&quot; (これはインポートの問題だと思われます)。
他のヒント
アスペクトを使用して、「等しい」パッチを適用できます。オブジェクトグラフのクラスで、falseを返すときにオブジェクトの状態をファイルに記録するようにします。オブジェクトの状態をログに記録するには、beanutilsなどを使用してオブジェクトを検査し、ダンプします。これは、ワークスペースで簡単に使用できるjarベースのソリューションの1つです
ツリーに保存されているオブジェクトの階層が非常に単純な場合、クラスに「等しい」条件付きブレークポイントを配置できます。 「等しい」ときにのみトリガーする実装falseを返します。これにより、ステップインする回数が制限されます...デバッガーにアクセスできる場所ならどこでも使用できます。 eclipseはこれをうまく処理します。
java-diff などのように聞こえますそれ。
さて、これはまったく奇妙な見方ですが、新しい静的メソッドの導入についてはどうですか:
public static boolean breakableEquals(Object o1, Object o2)
{
if (o1 == o2)
{
return true;
}
if (o1 == null || o2 == null)
{
return false;
}
// Don't condense this code!
if (o1.equals(o2))
{
return true;
}
else
{
return false;
}
}
最後のビットはおかしく見えますが...違いは、「return false」にブレークポイントを設定できることです。その後、すべての深い等価比較で breakableEquals
を使用すると、最初の&quot; return false
&quot;に到達するとすぐにブレークできます。
多くのプリミティブな値を比較している場合、これはあまり役に立ちませんが、確かに...ですが、役に立つかもしれません。これを実際に使用したことがあるとは言えませんが、なぜ機能しないのかわかりません。もちろん、少し効率が悪くなります。したがって、高性能なコードを扱っている場合は、後で変更することをお勧めします。
別のオプションは、次のようなものを使用することです。
boolean result = // comparison;
return result;
IDEがそれらをサポートしていると仮定すると、returnステートメントに条件付きブレークポイントを配置し、条件を&quot; !result
&quot;に設定できます。
さらに別のオプション:
public static boolean noOp(boolean result)
{
return result;
}
その後、比較内でこれを使用できます:
return Helpers.noOp(x.id == y.id) &&
Helpers.noOp(x.age == y.age);
デバッグしていないときは、これがJITによって最適化されることを望んでいますが、ここでも noOp
で条件付きブレークポイントを使用できます。残念ながら、コードが見苦しくなります。
要するに、ここでは特に魅力的な解決策はありませんが、特定の状況で役立つ可能性のあるいくつかのアイデアだけです。
単一のクラスをコピーしても構いませんが、 またはテストエリアにパッケージさえ そこにそれを使用しますが、インポート これのための全体のjarはちょうどするつもりはありません
ええと...何?クラスパスにjarを追加することは、クラスまたはパッケージ全体をソースコードとしてコピーするよりも、プロジェクトを簡単かつ邪魔になりません。
特定の問題に関しては、同等性を判断するために多くの異なるプロパティを使用する多くの異なるクラスがありますか、それとも本質的に同じクラスの深くネストされたオブジェクトグラフがありますか?後者の場合、equals()メソッドをstrucutreするだけで非常に簡単になり、「return false」にブレークポイントを置くことができます。ステートメント。前者の場合、これは非常に手間がかかるかもしれません。ただし、XMLベースの比較は、意味的に等しいオブジェクト(セットとマップなど)の違いを示すため、機能しない場合があります。
プロジェクトでjarを追加できないことを考えると、他のプロジェクトが達成するためにかなりの量のコードを必要とするソリューションの実装全体を提供することは、SOの答えをはるかに超えているようです(そして、Jarにうまく組み込みます) 。
非コードソリューションについてはどうですか-デバッガーの条件付きブレークポイント?メソッドがfalseを返す場合にのみトリップするブレークポイントを追加し、関連するすべてのクラスにそれらを配置できます。ステッピングなし。
私は、jarを追加することが物事を行う通常の方法であることを知っています。 jarは再利用可能なユニットです。ただし、これに必要な(私のプロジェクトでの)官僚制度は結果を正当化するものではありません。デバッグとステップインを続けます。
これを回避する方法の1つは、Spring(他のjarの負荷を取り込む)のようなライブラリを含めることです。Springにバンドルされているjarを使用できるようにするため、Spring jarを実際に使用しないSpringプロジェクトを見てきました。
commons-jxpath は、オブジェクトツリーをすばやく調べるのに役立つ場合があります。 jarを含めることの問題を完全には理解していませんが、デバッグ中に式を使用できると思われるIDEを使用している場合は、自分のプロジェクトでそれを使用できます。
おそらく、このメソッドのトレースに関する記事が役立ちます。
仕組み
カスタムクラスローダーは、クラスファイルを読み取り、トレースコードで各メソッドをインストルメントします。クラスローダーは、各クラスに静的フィールドも追加します。このフィールドには、「オン」と「オフ」の2つの状態があります。トレースコードは、印刷前にこのフィールドをチェックします。コマンドラインオプションは、この静的フィールドにアクセスして変更し、トレース出力を制御します。
表示されるサンプル出力は、一部のメソッド(isAppletなど)の戻り値を示しているため、問題に適しています。
isApplet = false
等号でfalseを返し始めた正確なクラスを簡単に見つけることができるはずです。 以下は、ページからの完全なサンプル出力です。
%java -jar /home/mike/java/trace.jar -classpath&quot; /home/mike/jdk1.3/demo/jfc/SwingSet2/SwingSet2.jar" -CodeViewer SwingSet2を除外
| SwingSet2。()
| SwingSet2。
| SwingSet2.main([Ljava.lang.String; @ 1dd95c)
|| isApplet(SwingSet2 @ 3d12a6)
|| isApplet = false
|| SwingSet2.createFrame(apple.awt.CGraphicsConfig@93537d)
||SwingSet2.createFrame=javax.swing.JFrame@cb01e3
|| createSplashScreen(SwingSet2 @ 3d12a6)
||| createImageIcon(SwingSet2 @ 3d12a6、&quot; Splash.jpg&quot ;,&quot; Splash.accessible_description&quot;)
|||createImageIcon=javax.swing.ImageIcon@393e97
||| isApplet(SwingSet2 @ 3d12a6)
||| isApplet = false
||| getFrame(SwingSet2 @ 3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
||| getFrame(SwingSet2 @ 3d12a6)
|||getFrame=javax.swing.JFrame@cb01e3
|| createSplashScreen
.run(SwingSet2 $ 1 @ fba2af)
..showSplashScreen(SwingSet2 @ 3d12a6)
... isApplet(SwingSet2 @ 3d12a6)
... isApplet = false
..showSplashScreen
.run
|| initializeDemo(SwingSet2 @ 3d12a6)
||| createMenus(SwingSet2 @ 3d12a6)
|||| getString(SwingSet2 @ 3d12a6、&quot; MenuBar.accessible_description&quot;)
||||| getResourceBundle(SwingSet2 @ 3d12a6)
|||||getResourceBundle=java.util.PropertyResourceBundle@6989e
|||| getString =&quot; Swingデモメニューバー&quot;
XMLEncoderはBeanプロパティのみをエンコードしますが、equalsは明らかに非Beanおよび任意の内部フィールドで機能します。
問題の一部は、実際に何が等しいかを知らないことです。オブジェクトは多くの異なるフィールドを持つことができ、それでも他のオブジェクトと同等であると主張しますが、それは異なるタイプであることさえあります。 (たとえば、カスタムURLクラスは、外部フォームに等しい文字列に対してtrueを返す場合があります。)
だから、バイトコード計測では、実際にクラスのequals()関数を変更して、アクセスするフィールドを確認できます。それでも、関数がfalseを返した理由を「真に」知ることは非常に困難です。ただし、equals()で実際にアクセスされるフィールドを比較するという単純な問題になることを願っています