リフレクションを使用して、あるクラスのフィールドから別のクラスにすべての値をコピーします


82

基本的に別のクラスのコピーであるクラスがあります。

public class A {
  int a;
  String b;
}

public class CopyA {
  int a;
  String b;
}

私は何をやっていることは、クラスから値を入れているACopyA送信する前にCopyAWebサービスを呼び出して。今、私は反射-メソッドを作成したいと思い、そのクラスから基本的にコピー(名前とタイプ別)同一であり、すべてのフィールドAクラスへCopyA

これどうやってするの?

これは私がこれまでに持っているものですが、それは完全には機能しません。ここでの問題は、ループしているフィールドにフィールドを設定しようとしていることだと思います。

private <T extends Object, Y extends Object> void copyFields(T from, Y too) {

    Class<? extends Object> fromClass = from.getClass();
    Field[] fromFields = fromClass.getDeclaredFields();

    Class<? extends Object> tooClass = too.getClass();
    Field[] tooFields = tooClass.getDeclaredFields();

    if (fromFields != null && tooFields != null) {
        for (Field tooF : tooFields) {
            logger.debug("toofield name #0 and type #1", tooF.getName(), tooF.getType().toString());
            try {
                // Check if that fields exists in the other method
                Field fromF = fromClass.getDeclaredField(tooF.getName());
                if (fromF.getType().equals(tooF.getType())) {
                    tooF.set(tooF, fromF);
                }
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

すでにこれを何とかしてしている人がいるに違いない



ええ、またはApacheJakartaのBeanUtilsです。
ショーンF

回答:


102

サードパーティのライブラリを使用してもかまわない場合は、Apache CommonsのBeanUtilsが、を使用してこれを非常に簡単に処理しcopyProperties(Object, Object)ます。


13
どうやらBeanUtilsはnullのDateフィールドでは機能しません。これが問題になる場合は、Apache PropertyUtilsを使用してください:mail-archive.com/user@commons.apache.org/msg02246.html
ripper234

10
これは、ゲッターとセッターのないプライベートフィールドでは明らかに機能しません。プロパティではなく、フィールドを直接操作するソリューションはありますか?
アンドレアラット2015年

それゲッターのないプレーンな公共分野での作品でもない:stackoverflow.com/questions/34263122/...
ヴァジム・

17

gsonライブラリhttps://github.com/google/gsonを使用してみませんか

クラスAをjson文字列に変換するだけです。次に、以下のコードを使用して、jsonStringをサブクラス(CopyA)に変換します。

Gson gson= new Gson();
String tmp = gson.toJson(a);
CopyA myObject = gson.fromJson(tmp,CopyA.class);

なぜ大きくなる可能性のある別の文字列を生成するのですか?ここで答えとして説明されているより良い選択肢があります。少なくとも我々 (業界)は、文字列表現のためのXMLからJSONに進行し、我々はまだすべてが...任意の機会にその文字列表現に渡されたくない
arntg

反射を使用する場合、ストリングは副産物であることに注意してください。あなたを通してさえそれを保存しませんでした!! これはJava初心者のための答えであり、短くてクリーンな方法を目指しています。@arntg
エリック・ホー

ソースオブジェクトと宛先オブジェクトの両方を反映する必要がありますが、バイナリ/テキスト/バイナリのフォーマットと解析のオーバーヘッドも導入しています。
Evvo

Proguardを使用してコードを難読化する場合は、注意が必要です。これを使用すると、このコードは機能しません。
SebastiaoRealino

8

BeanUtilsはパブリックフィールドのみをコピーし、少し時間がかかります。代わりに、getterメソッドとsetterメソッドを使用してください。

public Object loadData (RideHotelsService object_a) throws Exception{

        Method[] gettersAndSetters = object_a.getClass().getMethods();

        for (int i = 0; i < gettersAndSetters.length; i++) {
                String methodName = gettersAndSetters[i].getName();
                try{
                  if(methodName.startsWith("get")){
                     this.getClass().getMethod(methodName.replaceFirst("get", "set") , gettersAndSetters[i].getReturnType() ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }else if(methodName.startsWith("is") ){
                            this.getClass().getMethod(methodName.replaceFirst("is", "set") ,  gettersAndSetters[i].getReturnType()  ).invoke(this, gettersAndSetters[i].invoke(object_a, null));
                        }

                }catch (NoSuchMethodException e) {
                    // TODO: handle exception
                }catch (IllegalArgumentException e) {
                    // TODO: handle exception
                }

        }

        return null;
    }

BeanUtilsは、ゲッター/セッターがパブリックである限り、プライベートフィールドで問題なく機能します。パフォーマンスに関しては、ベンチマークを行っていませんが、イントロスペクトしたBeanの内部キャッシュを行っていると思います。
グレゴリーケース

2
これは、2つのBeanが同じデータ型のフィールドを持っている場合にのみ機能します。
TimeToCodeTheRoad 2012年

@To Kraそれは、そのフィールドのゲッター/セッターがある場合にのみ機能します。
woLfPwNeR 2017年

8

これが実用的でテスト済みのソリューションです。クラス階層でマッピングの深さを制御できます。

public class FieldMapper {

    public static void copy(Object from, Object to) throws Exception {
        FieldMapper.copy(from, to, Object.class);
    }

    public static void copy(Object from, Object to, Class depth) throws Exception {
        Class fromClass = from.getClass();
        Class toClass = to.getClass();
        List<Field> fromFields = collectFields(fromClass, depth);
        List<Field> toFields = collectFields(toClass, depth);
        Field target;
        for (Field source : fromFields) {
            if ((target = findAndRemove(source, toFields)) != null) {
                target.set(to, source.get(from));
            }
        }
    }

    private static List<Field> collectFields(Class c, Class depth) {
        List<Field> accessibleFields = new ArrayList<>();
        do {
            int modifiers;
            for (Field field : c.getDeclaredFields()) {
                modifiers = field.getModifiers();
                if (!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) {
                    accessibleFields.add(field);
                }
            }
            c = c.getSuperclass();
        } while (c != null && c != depth);
        return accessibleFields;
    }

    private static Field findAndRemove(Field field, List<Field> fields) {
        Field actual;
        for (Iterator<Field> i = fields.iterator(); i.hasNext();) {
            actual = i.next();
            if (field.getName().equals(actual.getName())
                && field.getType().equals(actual.getType())) {
                i.remove();
                return actual;
            }
        }
        return null;
    }
}

1
私は同様のソリューションを作成しました。クラスをフィールド名からフィールドマップにキャッシュしました。
オーデン2018

解決策は素晴らしいですが、この行で問題が発生しi.remove()ます。あなたはイテレータを作成した場合でも、あなたは呼び出すことはできませんremoveListiterator。それはあるべきですArrayList
Farid

collectFields()はArrayListオブジェクトを作成するため、削除は問題になりません。
JHead

5

私の解決策:

public static <T > void copyAllFields(T to, T from) {
        Class<T> clazz = (Class<T>) from.getClass();
        // OR:
        // Class<T> clazz = (Class<T>) to.getClass();
        List<Field> fields = getAllModelFields(clazz);

        if (fields != null) {
            for (Field field : fields) {
                try {
                    field.setAccessible(true);
                    field.set(to,field.get(from));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

public static List<Field> getAllModelFields(Class aClass) {
    List<Field> fields = new ArrayList<>();
    do {
        Collections.addAll(fields, aClass.getDeclaredFields());
        aClass = aClass.getSuperclass();
    } while (aClass != null);
    return fields;
}

これはカスタムオブジェクトでは機能しないと思います。親がなく、プリミティブフィールドのみのクラスがある場合のみ
Shervin Asgari 2016

スーパークラスフィールドをカバーするために、「getAllModelFields」カスタムメソッドを使用しています
Mohsen Kashi

4

の最初の引数はフィールドではなくtooF.set()ターゲットオブジェクト(too)である必要があり、2番目の引数はの元のフィールドではなくである必要があります。(値を取得するには、fromF.get()-を呼び出す必要がありますfrom。この場合は、ターゲットオブジェクトを再度渡します。)

ほとんどのリフレクションAPIはこのように機能します。FieldオブジェクトやMethodオブジェクトなどは、インスタンスからではなくクラスから取得するため、それらを使用するには(静的を除く)、通常はインスタンスを渡す必要があります。



4

これは遅い投稿ですが、将来的にはまだ効果的です。

Springは、BeanUtils.copyProperties(srcObj, tarObj)両方のクラスのメンバー変数の名前が同じ場合に、ソースオブジェクトからターゲットオブジェクトに値をコピーするユーティリティを提供します。

日付変換がある場合(たとえば、文字列から日付へ) 'null'がターゲットオブジェクトにコピーされます。次に、必要に応じて日付の値を明示的に設定できます。

BeanUtils fromApache Commonは、データ型の不一致がある場合にエラーをスローします(特に、Dateとの間の変換)

お役に立てれば!


これは、受け入れられたstackoverflow.com/a/1667911/4589003の回答以外の追加情報を提供しません
Sudip Bhandari 2018

3

ブルドーザーがお試しいただけると思います。BeanからBeanへの変換を適切にサポートします。また、使いやすいです。それをSpringアプリケーションに挿入するか、クラスパスにjarを追加して完了します。

あなたのケースの例として:

 DozerMapper mapper = new DozerMapper();
A a= new A();
CopyA copyA = new CopyA();
a.set... // set fields of a.
mapper.map(a,copyOfA); // will copy all fields from a to copyA

3
  1. BeanUtilsまたはApacheCommonsを使用せずに

  2. public static <T1 extends Object, T2 extends Object>  void copy(T1     
    origEntity, T2 destEntity) throws IllegalAccessException, NoSuchFieldException {
        Field[] fields = origEntity.getClass().getDeclaredFields();
        for (Field field : fields){
            origFields.set(destEntity, field.get(origEntity));
         }
    }
    

これは実用的な解決策ではありませんが、良い出発点です。両方のクラスに存在する非静的フィールドとパブリックフィールドのみを処理するには、フィールドをフィルタリングする必要があります。
JHead 2017

これは親クラスのフィールドを無視しませんか?
Evvo

2

SpringにはBeanUtils.copyPropertiesメソッドが組み込まれています。ただし、ゲッター/セッターのないクラスでは機能しません。JSONのシリアル化/逆シリアル化は、フィールドをコピーするためのもう1つのオプションです。ジャクソンはこの目的に使用できます。Springを使用している場合ほとんどの場合、Jacksonはすでに依存関係リストに含まれています。

ObjectMapper mapper     = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Clazz        copyObject = mapper.readValue(mapper.writeValueAsString(sourceObject), Clazz.class);

1

Orika'sは、バイトコードの生成を介して行われるため、シンプルで高速なBeanマッピングフレームワークです。ネストされたマッピングと異なる名前のマッピングを実行します。詳細については、こちらを確認してください。 サンプルマッピングは複雑に見える場合がありますが、複雑なシナリオの場合は単純です。

MapperFactory factory = new DefaultMapperFactory.Builder().build();
mapperFactory.registerClassMap(mapperFactory.classMap(Book.class,BookDto.class).byDefault().toClassMap());
MapperFacade mapper = factory.getMapperFacade();
BookDto bookDto = mapperFacade.map(book, BookDto.class);

これは、質問が求めていることを行いません。SerializationUtils.clone()同じクラスの新しいオブジェクトを提供します。さらに、シリアル化可能なクラスでのみ機能します。
カービー


1

Kotlinで上記の問題を解決しました。これは、Androidアプリ開発で問題なく機能します。

 object FieldMapper {

fun <T:Any> copy(to: T, from: T) {
    try {
        val fromClass = from.javaClass

        val fromFields = getAllFields(fromClass)

        fromFields?.let {
            for (field in fromFields) {
                try {
                    field.isAccessible = true
                    field.set(to, field.get(from))
                } catch (e: IllegalAccessException) {
                    e.printStackTrace()
                }

            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }

}

private fun getAllFields(paramClass: Class<*>): List<Field> {

    var theClass:Class<*>? = paramClass
    val fields = ArrayList<Field>()
    try {
        while (theClass != null) {
            Collections.addAll(fields, *theClass?.declaredFields)
            theClass = theClass?.superclass
        }
    }catch (e:Exception){
        e.printStackTrace()
    }

    return fields
}

}


0

このため、別のJARファイルに依存関係を追加したくなかったので、自分のニーズに合ったものを作成しました。私はfjormの規則に従いますhttps://code.google.com/p/fjorm/。つまり、一般的にアクセス可能なフィールドは公開されており、セッターやゲッターをわざわざ書く必要はありません。(私の意見では、コードは管理が簡単で、実際には読みやすくなっています)

だから私は自分のニーズに合ったものを書きました(実際にはそれほど難しいことではありません)(クラスに引数のないパブリックコンストラクタがあると仮定します)そしてそれをユーティリティクラスに抽出することができます

  public Effect copyUsingReflection() {
    Constructor constructorToUse = null;
    for (Constructor constructor : this.getClass().getConstructors()) {
      if (constructor.getParameterTypes().length == 0) {
        constructorToUse = constructor;
        constructorToUse.setAccessible(true);
      }
    }
    if (constructorToUse != null) {
      try {
        Effect copyOfEffect = (Effect) constructorToUse.newInstance();
        for (Field field : this.getClass().getFields()) {
          try {
            Object valueToCopy = field.get(this);
            //if it has field of the same type (Effect in this case), call the method to copy it recursively
            if (valueToCopy instanceof Effect) {
              valueToCopy = ((Effect) valueToCopy).copyUsingReflection();
            }
            //TODO add here other special types of fields, like Maps, Lists, etc.
            field.set(copyOfEffect, valueToCopy);
          } catch (IllegalArgumentException | IllegalAccessException ex) {
            Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
        return copyOfEffect;
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        Logger.getLogger(Effect.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    return null;
  }

アンチパターン:車輪の再発明
Spektakulatius 2018年

0

Mladenの基本的な考え方は機能しましたが(ありがとう)、堅牢にするためにいくつかの変更が必要だったので、ここでそれらを提供しました。

このタイプのソリューションを使用する必要がある唯一の場所は、オブジェクトのクローンを作成する場合ですが、管理対象オブジェクトであるため、クローンを作成することはできません。幸運にも、すべてのフィールドに100%副作用のないセッターを持つオブジェクトがある場合は、代わりにBeanUtilsオプションを使用する必要があります。

ここでは、lang3のユーティリティメソッドを使用してコードを簡略化しているため、貼り付ける場合は、最初にApacheのlang3ライブラリをインポートする必要があります。

コードをコピーする

static public <X extends Object> X copy(X object, String... skipFields) {
        Constructor constructorToUse = null;
        for (Constructor constructor : object.getClass().getConstructors()) {
            if (constructor.getParameterTypes().length == 0) {
                constructorToUse = constructor;
                constructorToUse.setAccessible(true);
                break;
            }
        }
        if (constructorToUse == null) {
            throw new IllegalStateException(object + " must have a zero arg constructor in order to be copied");
        }
        X copy;
        try {
            copy = (X) constructorToUse.newInstance();

            for (Field field : FieldUtils.getAllFields(object.getClass())) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }

                //Avoid the fields that you don't want to copy. Note, if you pass in "id", it will skip any field with "id" in it. So be careful.
                if (StringUtils.containsAny(field.getName(), skipFields)) {
                    continue;
                }

                field.setAccessible(true);

                Object valueToCopy = field.get(object);
                //TODO add here other special types of fields, like Maps, Lists, etc.
                field.set(copy, valueToCopy);

            }

        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new IllegalStateException("Could not copy " + object, e);
        }
        return copy;
}

0
    public <T1 extends Object, T2 extends Object> void copy(T1 origEntity, T2 destEntity) {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.map(origEntity,destEntity);
    }

 <dependency>
            <groupId>net.sf.dozer</groupId>
            <artifactId>dozer</artifactId>
            <version>5.4.0</version>
        </dependency>

このmapper.mapには問題があり、エンティティでは主キーをコピーしていません
MohammedRafeeq20年1

0
public static <T> void copyAvalableFields(@NotNull T source, @NotNull T target) throws IllegalAccessException {
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (!Modifier.isStatic(field.getModifiers())
                && !Modifier.isFinal(field.getModifiers())) {
            field.set(target, field.get(source));
        }
    }
}

クラスのすべてのフィールドを読みます。結果から非静的フィールドと非最終フィールドをフィルタリングします。ただし、非公開フィールドへのアクセス中にエラーが発生する可能性があります。たとえば、この関数が同じクラスにあり、コピーされるクラスにパブリックフィールドが含まれていない場合、アクセスエラーが発生します。解決策は、この関数を同じパッケージに配置するか、パブリックへのアクセスを変更するか、ループ呼び出しフィールド内のこのコードに変更することです。フィールドを利用できるようにするもの


このコードは質問の解決策を提供するかもしれませんが、なぜ/どのように機能するかについてのコンテキストを追加することをお勧めします。これは、将来のユーザーが学習し、その知識を自分のコードに適用するのに役立ちます。また、コードが説明されると、賛成票の形でユーザーから肯定的なフィードバックが得られる可能性があります。
borchvm
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.