リフレクションを(過剰に)使用するのは悪い習慣ですか?


16

定型コードの量を大幅に減らす場合は、リフレクションを使用することをお勧めしますか?

基本的に、一方の側でパフォーマンスとおそらく読みやすさと、他方の側で定型コードの抽象化/自動化/削減との間にトレードオフがあります。

編集:推奨されるリフレクションの使用例を次に示します。

例として、Base10個のフィールドと3個のサブクラスSubclassASubclassB持ち、SubclassCそれぞれが10個の異なるフィールドを持つ抽象クラスがあるとします。それらはすべて単純なBeanです。問題は、2つのBase型参照を取得し、対応するオブジェクトが同じ(サブ)型であり、等しいかどうかを確認することです。

ソリューションとしては、最初にタイプが等しいかどうかをチェックしてからすべてのフィールドをチェックするか、リフレクションを使用して同じタイプであるかを動的に確認し、「get」(convention構成上)、両方のオブジェクトでそれらを呼び出し、結果で等しいを呼び出します。

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");
        }
}

20
何でも使いすぎることは悪い習慣です。
Tulainsコルドバ

1
@ user61852そう、自由が多すぎると独裁につながります。いくつかの古いギリシャ人はすでにこれを知っていました。
ott--

6
「あなたにとって水が多すぎると悪いでしょう。明らかに、その量が多すぎると、それが過剰になります。それが意味することです!」-スティーブンフライ
ジョンパーディ

4
「オブジェクトのタイプがT1の場合は何かを実行しますが、T2のタイプの場合は何か他のことを実行します」と自分自身を平手打ちします。javapractices.com/topic/TopicAction.do?Id = 31
rm5248

回答:


25

リフレクションは何に似て、コンパイル時に不明であったクラスの機能を発見するために、特定の目的のために作成されたdlopendlsymすべき機能はC.でいるのいかなる使用の外側を行う重く精査を。

Javaデザイナー自身がこの問題に遭遇したことは、あなたにとってはありませんでしたか?それが、事実上すべてのクラスにequalsメソッドがある理由です。クラスが異なれば、平等の定義も異なります。状況によっては、派生オブジェクトがベースオブジェクトと等しい場合があります。状況によっては、ゲッターのないプライベートフィールドに基づいて平等を判断できます。あなたは知らない。

そのため、カスタムの等価性を必要とするすべてのオブジェクトはequalsメソッドを実装する必要があります。最終的には、オブジェクトをセットに入れるか、ハッシュインデックスとして使用します。その後、equalsとにかく実装する必要があります。他の言語では異なる方法で実行されますが、Javaではを使用しequalsます。あなたはあなたの言語の慣習に従うべきです。

また、「定型的な」コードが正しいクラスに入れられた場合、台無しにするのはかなり困難です。リフレクションにより、複雑さが増し、バグが発生する可能性が増えます。たとえば、メソッドでは、一方がnull特定のフィールドに戻り、他方が戻らない場合、2つのオブジェクトは等しいと見なされます。ゲッターの1つが適切なオブジェクトなしでオブジェクトの1つを返すとどうなりますequalsか?あなたif (!object1.equals(object2))は失敗します。また、バグが発生しやすいのは、リフレクションがほとんど使用されないという事実です。そのため、プログラマーはその落とし穴に慣れていません。


12

リフレクションの過剰使用は、おそらく使用する言語に依存します。ここでは、Javaを使用しています。その場合、リフレクションは注意して使用する必要があります。これは、多くの場合、不適切なデザインに対する回避策にすぎないためです。

異なるクラスを比較しているので、これはメソッドのオーバーライドに最適な問題です。2つの異なるクラスのインスタンスが等しいと見なされるべきではないことに注意してください。同じクラスのインスタンスがある場合にのみ、等しいかどうかを比較できます。等値比較を正しく実装する方法の例については、https://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-javaを参照してください


16
+1-一部の人は、反射の使用はデザインが悪いことを示す赤い旗だと考えています。
ロスパターソン

1
この場合、おそらく等式に基づいたソリューションが望ましいでしょうが、一般的な考えとして、反射ソリューションの何が問題になっていますか?実際、これは非常に汎用的であり、equalsメソッドをすべてのクラスおよびサブクラスで明示的に記述する必要はありません(ただし、優れたIDEで簡単に生成できます)。
m3th0dman

2
@RossPattersonなぜですか?
m3th0dman

2
@ m3th0dman compare()「get」で始まるメソッドはゲッターであり、比較演算の一部として呼び出すのに安全で適切であると想定しているメソッドなどにつながるためです。これはオブジェクトの意図されたインターフェース定義の違反であり、それは便利かもしれませんが、ほとんど常に間違っています。
ロスパターソン

4
@ m3th0dmanカプセル化に違反しています-基本クラスはそのサブクラスの属性にアクセスする必要があります。プライベート(またはゲッタープライベート)の場合はどうなりますか?多型はどうですか?別のサブクラスを追加することを決定し、その中で別のサブクラスを比較したい場合は?基本クラスは既に私のためにそれを行っており、変更することはできません。ゲッターが何かを遅延ロードするとどうなりますか?比較方法でそれを行いたいですか?で始まるメソッドgetがゲッターであり、何かを返すカスタムメソッドではないことをどうすればわかりますか?
スルタン

1

ここには2つの問題があると思います。

  1. 動的コードと静的コードはどれくらい必要ですか?
  2. カスタムバージョンの平等を表現するにはどうすればよいですか?

動的コードと静的コード

これは多年にわたる質問であり、その答えは非常に説得力があります。

一方では、コンパイラはあらゆる種類の悪いコードをキャッチするのに非常に優れています。これは、さまざまな形式の分析を通じて行われます。タイプ分析は一般的な分析です。をBanana予期しているコードでオブジェクトを使用できないことを知っていますCog。これは、コンパイルエラーを通じて通知されます。

これは、受け入れられたタイプとコンテキストから指定されたタイプの両方を推測できる場合にのみこれを行うことができます。どの程度推測でき、その推測がどれほど一般的であるかは、使用されている言語に大きく依存します。Javaは、Inheritance、Interfaces、Genericsなどのメカニズムを通じて型情報を推測できます。マイレージは異なりますが、他の言語ではメカニズムの数が少なくなり、言語の数が増えます。それでも、コンパイラーが真実であると知ることができるものに要約されます。

一方、コンパイラは外部コードの形状を予測できず、言語の型システムでは簡単に表現できない多くの型で一般的なアルゴリズムを表現できる場合があります。これらの場合、コンパイラは常に結果を事前に知ることができず、尋ねるべき質問を知ることさえできない場合があります。リフレクション、インターフェイス、およびObjectクラスは、これらの問題を処理するJavaの方法です。正しいチェックと処理を提供する必要がありますが、この種のコードを持つことは不健康ではありません。

コードを非常に具体的にするか、非常に一般的にするかは、処理しようとしている問題にかかっています。型システムを使用して簡単に表現できる場合はそうしてください。コンパイラーが長所を発揮して、あなたを助けてください。型システムが事前に知ることができなかった場合(外部コード)、または型システムがアルゴリズムの一般的な実装に適していない場合、リフレクション(およびその他の動的手段)を使用するのが適切なツールです。

あなたの言語の型システムの外に出ることは驚くべきことであることに注意してください。友達に近づいて、英語で会話を始めることを想像してください。あなたの考えを正確に表現するスペイン語、フランス語、広東語からいくつかの言葉を突然落としてください。コンテキストは友人に多くを伝えますが、彼らはまた、あらゆる種類の誤解につながるこれらの単語を処理する方法をまったく知らないかもしれません。これらの誤解に対処することは、より多くの単語を使用してそれらのアイデアを英語で説明するより良いですか?

カスタム平等

Javaはequals一般に2つのオブジェクトを比較する方法に大きく依存していることを理解していますが、特定のコンテキストでは必ずしも適切ではありません。

別の方法がありますが、これもJava標準です。そのコンパレータと呼ばれます。

コンパレータの実装方法は、比較対象と方法によって異なります。

  • 特定のequals実装に関係なく、任意の2つのオブジェクトに適用できます。
  • 任意の2つのオブジェクトを処理するための一般的な(リフレクションベースの)比較方法を実装できます。
  • 一般的に比較されるオブジェクトタイプにボイラープレート比較関数を追加して、タイプの安全性と最適化を実現できます。

1

リフレクティブプログラミングを可能な限り避けることを好む

  • コンパイラによるコードの静的なチェックを困難にします
  • コードの推論を難しくします
  • コードのリファクタリングが難しくなります

また、単純なメソッド呼び出しよりもパフォーマンスがはるかに低くなります。以前は1桁以上遅くなりました。

コードの静的チェック

反射コードは、文字列を使用してクラスとメソッドを検索します。元の例では、「get」で始まるメソッドを探しています。ゲッターを返しますが、「gettysburgAddress()」などの他のメソッドも追加で返されます。コードでルールを強化することもできますが、ポイントはランタイムチェックであるという点は変わりません。IDEとコンパイラは役に立たない。一般に、「文字列型」コードや「プリミティブな取りつかれた」コードは嫌いです。

理由が難しい

反射コードは、単純なメソッド呼び出しよりも冗長です。より多くのコード=より多くのバグ、または少なくともより多くのバグの可能性、より多くのコードの読み取り、テストなど。

リファクタリングが難しい

コードは文字列/動的に基づいているため。IDEがリフレクションの使用を選択できないため、リフレクションがシーンに反映されるとすぐに、IDEのリファクタリングツールを使用して100%の自信を持ってコードをリファクタリングすることはできません。

基本的に、可能な限り一般的なコードへの反映を避けます。改善されたデザインを探してください。


0

定義によるものの使いすぎは悪いことですよね?とりあえず、(オーバー)を取り除きましょう。

あなたは春の反射の驚くほど重い内部使用を悪い習慣と呼びますか?

Springはアノテーションを使用してリフレクションを調整します-Hibernate(およびおそらく数十/数百の他のツール)も使用します。

独自のコードで使用する場合は、これらのパターンに従ってください。注釈を使用して、ユーザーのIDEが引き続き役立つことを確認します(コードの唯一の「ユーザー」である場合でも、リフレクションの不注意な使用はおそらく最終的にはあなたに噛み付くでしょう)。

ただし、開発者がコードをどのように使用するかを考慮することなく、最も単純なリフレクションの使用でさえ、おそらく使いすぎです。


0

これらの答えのほとんどはポイントを逃していると思います。

  1. はい、あなたはおそらく書くべきequals()hashcode()@KarlBielefeldtで述べたように、。

  2. しかし、多くのフィールドを持つクラスの場合、これは退屈な定型的なものになる可能性があります。

  3. だから、それは依存します。

    • あなただけのequalsとhashCodeを必要としない場合はめったに、それは汎用の反射計算を使用するために、実用的、そしておそらく大丈夫です。少なくとも早くて汚い最初のパスとして。
    • しかし、多くの要素が必要な場合、たとえば、これらのオブジェクトがHashTablesに格納されるため、パフォーマンスが問題になる場合は、間違いなくコードを書き出す必要があります。ずっと速くなります。
  4. 別の可能性:クラスに本当に多くのフィールドがあり、equalsを書くのが面倒な場合は、

    • フィールドをマップに入れます。
    • あなたはまだカスタムのゲッターとセッターを書くことができます
    • Map.equals()比較に使用します。(またはMap.hashcode()

例(:nullチェックを無視し、おそらく表示されないStringキーの代わりにEnumを使用する必要があります...)

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