ボイラープレートコードの量を大幅に減らす場合は、リフレクションを使用することをお勧めしますか?
基本的に、パフォーマンスと、おそらく読みやすさとの間のトレードオフと、ボイラープレートコードの抽象化/自動化/削減が反対側にあります。
編集: これはリフレクションの推奨される使用例です 。
例として、10個のフィールドと3つのサブクラスBase
、SubclassA
およびSubclassB
があり、それぞれ10個の異なる抽象クラスSubclassC
があるとします。田畑;それらはすべて単純な豆です。問題は、2つのBase
型参照を取得し、対応するオブジェクトが同じ(サブ)型であり、等しいかどうかを確認することです。
ソリューションとして、最初にタイプが等しいかどうかを確認してからすべてのフィールドを確認する生のソリューションがあります。または、リフレクションを使用してそれらが同じタイプであるかどうかを動的に確認し、「get」で始まるすべてのメソッドを反復することができます(慣例)構成より)、両方のオブジェクトでそれらを呼び出し、結果でequalsを呼び出します。
boolean compare(Base base1, Base, base2) {
if (base1 instanceof SubclassA && base2 instanceof SubclassA) {
SubclassA subclassA1 = (SubclassA) base1;
SubclassA subclassA2 = (SubclassA) base2;
compare(subclassA1, subclassA2);
} else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
//the same
}
//boilerplate
}
boolean compare(SubclassA subA1, SubclassA subA2) {
if (!subA1.getField1().equals(subA2.getField1)) {
return false;
}
if (!subA1.getField2().equals(subA2.getField2)) {
return false;
}
//boilerplate
}
boolean compare(SubclassB subB1, SubclassB subB2) {
//boilerplate
}
//boilerplate
//alternative with reflection
boolean compare(Base base1, Base base2) {
if (!base1.getClass().isAssignableFrom(base2.getClass())) {
System.out.println("not same");
System.exit(1);
}
Method[] methods = base1.getClass().getMethods();
boolean isOk = true;
for (Method method : methods) {
final String methodName = method.getName();
if (methodName.startsWith("get")) {
Object object1 = method.invoke(base1);
Object object2 = method.invoke(base2);
if(object1 == null || object2 == null) {
continue;
}
if (!object1.equals(object2)) {
System.out.println("not equals because " + object1 + " not equal with " + object2);
isOk = false;
}
}
}
if (isOk) {
System.out.println("is OK");
}
}
リフレクションは、dlopen
およびdlsym
関数がCで行うのと同様に、コンパイル時に不明だったクラスの機能を発見するために特定の目的で作成されました。 be heavly精査されます。
Javaデザイナ自身がこの問題に遭遇したことがありますか?これが事実上、すべてのクラスがequals
メソッドを持っている理由です。クラスによって、等価性の定義が異なります。状況によっては派生オブジェクトは基本オブジェクトと等しい場合があります。状況によっては、ゲッターなしのプライベートフィールドに基づいて等しいかどうかを判断できます。
そのため、カスタムの等価性を必要とするすべてのオブジェクトがequals
メソッドを実装する必要があります。最終的には、オブジェクトをセットに入れるか、またはそれらをハッシュインデックスとして使用したい場合は、とにかくequals
を実装する必要があります。他の言語では方法が異なりますが、Javaはequals
を使用します。言語の規則に従う必要があります。
また、「ボイラープレート」コードを正しいクラスに入れると、失敗するのがかなり難しくなります。リフレクションはさらに複雑になり、バグが発生する可能性が高くなります。たとえば、メソッドでは、2つのオブジェクトは、一方が特定のフィールドに対してnull
を返し、もう一方が返さない場合、等しいと見なされます。ゲッターの1つが適切なequals
なしでオブジェクトの1つを返す場合はどうなりますか? if (!object1.equals(object2))
は失敗します。また、バグが発生しやすくなることは、リフレクションがほとんど使用されないため、プログラマーはその問題に慣れていません。
リフレクションの多用は、おそらく使用する言語に依存します。ここではJavaを使用しています。その場合、リフレクションは慎重に使用する必要があります。これは、多くの場合、設計が悪い場合の回避策にすぎないためです。
だから、あなたは異なるクラスを比較しています、これは完璧な問題です メソッドのオーバーライド。 2つの異なるクラスのインスタンスが等しいと見なしてはならないことに注意してください。同じクラスのインスタンスがある場合にのみ、等しいかどうかを比較できます。等価比較を正しく実装する方法の例については、 https://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-Java を参照してください。
ここで2つの問題があると思います。
動的コードと静的コード
これは多年にわたる質問であり、答えは非常に根拠があります。
一方で、コンパイラはあらゆる種類の不正なコードの捕捉に非常に優れています。これはさまざまな形式の分析を通じて行われ、タイプ分析が一般的なものです。 Banana
が必要なコードではCog
オブジェクトを使用できないことがわかっています。これは、コンパイルエラーによって通知されます。
現在、これは、受け入れられたタイプと指定されたタイプの両方をコンテキストから推測できる場合にのみ可能です。どれだけ推論できるか、そしてその推論がどれほど一般的であるかは、使用されている言語に大きく依存します。 Javaは、継承、インターフェース、ジェネリックスなどのメカニズムを介して型情報を推測できます。マイレージは異なります。他の言語はより少ないメカニズムを提供し、いくつかはより多くのメカニズムを提供します。本当であることを知っています。
一方、コンパイラは外部コードの形状を予測できません。また、一般的なアルゴリズムは、言語の型システムでは簡単に表現できない多くの型で表現できる場合があります。これらの場合、コンパイラーは常に結果を事前に知ることができず、どの質問をするかを知ることさえできない場合があります。リフレクション、インターフェース、およびObjectクラスは、これらの問題を処理するJavaの方法です。正しいチェックと処理を提供する必要がありますが、この種のコードを使用することは不健康ではありません。
コードを非常に具体的にするか、非常に一般的にするかは、処理しようとしている問題に帰着します。型システムを使用して簡単に表現できる場合は、そうしてください。コンパイラーにその長所を発揮させ、あなたを助けましょう。型システムが事前に知ることができない(外部コード)場合、または型システムがアルゴリズムの一般的な実装に適さない場合、リフレクション(およびその他の動的な手段)が適切なツールです。
あなたの言語の型システムの外に出ることは意外であることに注意してください。友達に近づいて英語で会話を始めることを想像してみてください。突然、スペイン語、フランス語、広東語からいくつかの単語を落として、あなたの考えを正確に表現してください。コンテキストはあなたの友人に多くのことを伝えますが、彼らはまた、あらゆる種類の誤解につながるそれらの単語を処理する方法をまったく知らないかもしれません。これらの誤解に対処することは、より多くの単語を使用して英語でそれらのアイデアを説明することよりも良いですか?
カスタム平等
Java=は、2つのオブジェクトを比較するためにequals
メソッドに大きく依存していると理解していますが、特定のコンテキストでは必ずしも適切ではありません。
別の方法があります、それはJava標準でもあります。それは Comparator と呼ばれます。
コンパレーターの実装方法は、比較対象とその方法によって異なります。
equals
実装に関係なく、2つのオブジェクトに適用できます。リフレクティブプログラミングはできるだけ避けたいので
また、単純なメソッド呼び出しよりもパフォーマンスが大幅に低下します。以前は1桁以上遅くなっていました。
静的チェックコード
リフレクションコードは、文字列を使用してクラスとメソッドを検索します。元の例では、「get」で始まるメソッドを探しています。ゲッターを返しますが、 "gettysburgAddress()"などの他のメソッドも返します。ルールをコードで強化することもできますが、重要な点はランタイムチェックです。 IDEそしてコンパイラは役に立たない。一般的に、私は「文字列型」または「プリミティブにこだわる」コードが嫌いだ。
理由が難しい
リフレクティブコードは、単純なメソッド呼び出しよりも詳細です。より多くのコード=より多くのバグ、または少なくともより多くのバグの可能性、より多くのコードを読み、テストするなど。
リファクタリングが難しい
コードは文字列ベース/動的ベースなので、 IDEはリフレクションの用途を選択できないため、IDEのリファクタリングツールを使用して100%信頼できるコードをリファクタリングすることはできません。
基本的に、可能な場合は一般的なコードでのリフレクションを避けてください。改善されたデザインを探します。
定義によるものの使いすぎは悪いですよね?それでは、(over)を今のところ取り除きましょう。
春の驚くほど重い反射の内部使用を悪い習慣と呼びますか?
Springはアノテーションを使用してリフレクションを飼いならします-Hibernateもそうです(そしておそらく他の何十、何百ものツール)。
独自のコードで使用する場合は、これらのパターンに従ってください。注釈を使用して、ユーザーのIDEが引き続きそれらを助けることができることを確認します(コードの唯一の「ユーザー」である場合でも、リフレクションを不注意に使用すると、結局お尻に噛み付くようになるでしょう) 。
ただし、開発者がコードをどのように使用するかを考慮しないと、リフレクションの最も単純な使用法でもおそらく使いすぎです。
これらの回答のほとんどは要点を逃していると思います。
はい、@ KarlBielefeldtによって指摘されているように、おそらくequals()
およびhashcode()
を記述する必要があります。
しかし、多くのフィールドを持つクラスの場合、これは退屈な定型文になる可能性があります。
したがって、依存しますです。
別の可能性:クラスに非常に多くのフィールドがあり、equalsを作成するのが面倒な場合は、
Map.equals()
を使用してください。 (またはMap.hashcode()
)例えば(note:nullチェックを無視します。文字列キーの代わりにEnumsを使用する必要がありますが、あまり表示されていません...)
class TooManyFields {
private HashMap<String, Object> map = new HashMap<String, Object>();
public setFoo(int i) { map.put("Foo", Integer.valueOf(i)); }
public int getFoo() { return map.get("Foo").intValue(); }
public setBar(Sttring s) { map.put("Bar", s); }
public String getBar() { return map.get("Bar").toString(); }
... more getters and setters ...
public boolean equals(Object o) {
return (o instanceof TooManyFields) &&
this.map.equals( ((TooManyFields)o).map);
}