web-dev-qa-db-ja.com

Java:デフォルトのコンストラクターを持たないクラスのnewInstance

私は学生の宿題のために自動テストフレームワーク(jUnitに基づいていますが、それは重要ではありません)を構築しようとしています。一部のクラスのコンストラクタを作成し、それらにいくつかのメソッドを追加する必要があります。後で、私が提供するテスト機能を使用して、問題なく動作するかどうかを確認します。

私がやりたいのは、by reflection、テストしたいクラスの新しいインスタンスを作成することです。問題は、デフォルトのコンストラクターがないである場合があることです。 自分でインスタンスを作成し、インスタンス変数を初期化したい。これを行う方法はありますか?これが以前に尋ねられた場合は申し訳ありませんが、答えが見つかりませんでした。

前もって感謝します。

39
GermanK

Class.getConstructor()を呼び出し、次にConstructor.newInstance()を呼び出して適切な引数を渡します。サンプルコード:

import Java.lang.reflect.*;

public class Test {

    public Test(int x) {
        System.out.println("Constuctor called! x = " + x);
    }

    // Don't just declare "throws Exception" in real code!
    public static void main(String[] args) throws Exception {
        Class<Test> clazz = Test.class;
        Constructor<Test> ctor = clazz.getConstructor(int.class);
        Test instance = ctor.newInstance(5);           
    }
}
50
Jon Skeet

これは、javassistや別のバイトコード「マニピュレーター」を必要としない一般的なソリューションです。ただし、コンストラクターは単に対応するフィールドに引数を割り当てる以外に何もしないと想定しているため、最初のコンストラクターを選択してデフォルト値(つまり、intの場合は0、Objectの場合はnullなど)を持つインスタンスを作成します。

_private <T> T instantiate(Class<T> cls, Map<String, ? extends Object> args) throws Exception
{
    // Create instance of the given class
    final Constructor<T> constr = (Constructor<T>) cls.getConstructors()[0];
    final List<Object> params = new ArrayList<Object>();
    for (Class<?> pType : constr.getParameterTypes())
    {
        params.add((pType.isPrimitive()) ? ClassUtils.primitiveToWrapper(pType).newInstance() : null);
    }
    final T instance = constr.newInstance(params.toArray());

    // Set separate fields
    for (Map.Entry<String, ? extends Object> arg : args.entrySet()) {
        Field f = cls.getDeclaredField(arg.getKey());
        f.setAccessible(true);
        f.set(instance, arg.getValue());
    }

    return instance;
}
_

追伸Java 1.5+で動作します。このソリューションでは、f.setAccessible(true)の呼び出しを妨げるSecurityManagerマネージャーがないことも想定しています。

6
Vlad

モックフレームワーク(ezmockなど)を使用したことがない場合は、試してみることを強くお勧めします。

私は間違っているかもしれませんし、まったく役に立たないかもしれませんが、あなたの投稿から収集できることから、モックがあなたが探しているものとまったく同じである可能性があります質問 for。

編集:コメントへの応答。

いいえ、現代のモックフレームワークでは、「nothing」から任意のクラスの「Fake」インスタンスを作成し、クラスのインスタンスであるかのように渡すことができます。インターフェイスは必要ありません。どのクラスでもかまいません。また、メソッドは、単純な「7」を返す値から「arg = 7で呼び出されたときに5を最初の呼び出し、6を2番目、7を3番目に返す」から値のシーケンスを返すスクリプトを作成できます。

通常、テストフレームワークと組み合わせて使用​​し、テストするクラスに渡す参照クラスを提供します。

これはまさにあなたが探しているものではないかもしれませんが、ユニットテストと変数を手動で初期化することについて言及したので、これは最終的に便利になるかもしれないように思われました。

2
Bill K

次のコードを使用して、渡されたクラス名のすべてのタイプの汎用オブジェクトのリストを作成しました。クラス内のすべてのsetメソッドを使用して、結果セットを介して渡されたすべての値を設定します。誰もがそれに興味を持っている場合に備えて、私はこれを投稿しています。

protected List<Object> FillObject(ResultSet rs, String className)
    {
        List<Object> dList = new ArrayList<Object>();

        try
        {
            ClassLoader classLoader = GenericModel.class.getClassLoader();

            while (rs.next())
            {
                Class reflectionClass = classLoader.loadClass("models." + className);

                Object objectClass = reflectionClass.newInstance();

                Method[] methods = reflectionClass.getMethods();

                for(Method method: methods)
                {
                    if (method.getName().indexOf("set") > -1)
                    {
                        Class[] parameterTypes = method.getParameterTypes();

                        for(Class pT: parameterTypes)
                        {
                            Method setMethod = reflectionClass.getMethod(method.getName(), pT);

                            switch(pT.getName())
                            {
                                case "int":
                                    int intValue = rs.getInt(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, intValue);
                                    break;

                                case "Java.util.Date":
                                    Date dateValue = rs.getDate(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, dateValue);
                                    break;

                                case "boolean":
                                    boolean boolValue = rs.getBoolean(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, boolValue);
                                    break;

                                default:
                                    String stringValue = rs.getString(method.getName().replace("set", ""));
                                    setMethod.invoke(objectClass, stringValue);
                                    break;
                            }
                        }
                    }
                }

                dList.add(objectClass);
            }
        }
        catch (Exception e)
        {
            this.setConnectionMessage("ERROR: reflection class loading: " + e.getMessage());
        }

        return dList;
    }

次のソースコードを割り当てとともに配布できます。ソースコードに含めるよう生徒に伝えます。適切な署名を使用してAssignmentクラスをコーディングしない限り、それらのコードはコンパイルされません。コンパイラーが署名チェックを行います。

その後、テストプログラムでリフレクションを使用する必要はありません。 AssignmentFactoryをインスタンス化し、適切な引数でmakeメソッドを呼び出すだけです。

このアイデアを使用する場合、あなたの新しい課題は、課題クラスに合わせてAssignmentFactoryを変更する(テストプログラムを中断する)生徒たちです。

package assignment ;

public class AssignmentFactory
{
     public AssignmentFactory ( )
     {
           super ( ) ;
     }

     public AssignmentFactory make ( .... parameters )
     {
           return new Assignment ( .... arguments ) ;
     }
}
0
emory

Class.getConstructor または Class.getConstructors を使用してから、メソッド Constructor.newInstance を使用して、使用するオブジェクトを初期化できます。

0
Dani Cricco