任意のオブジェクト間で、特定のフィールドだけをコピーするのに汎用的な方法を考えます。
これを考えるきっかけは、JUnit の assertEquals などを実行する前に、比較したくない属性フィールドは、
コピーして同じ値にしてしまおうという目的で、それを状況に応じていろんなコードを書くのが
面倒くさいからです。
以下、書いたけど、2022-05-16 に、
【修正】オブジェクト間フィールドのコピー - Oboe吹きプログラマの黙示録
として修正。
コピーするオブジェクトは同じ型である前提で、以下の関数型インターフェースを用意します。
import java.io.Serializable; import java.lang.reflect.Field; import java.util.function.Consumer; /** * FieldCopy. 指定属性フィールドのコピー */ @FunctionalInterface public interface FieldCopy<T> extends Serializable{ String get(T t) throws Exception; /** * フィールドコピー実行Consumerの生成 * @param function コピー対象フィールド名を返す関数型インターフェース FieldCopy * @param t コピー先Object * @return コピー元を指定する Consumer */ public static <T> Consumer<T> of(FieldCopy<T> function, T t){ return u->{ try{ String fname = function.get(t); Field f; try{ f = t.getClass().getField(fname); }catch(NoSuchFieldException e){ f = t.getClass().getDeclaredField(fname); } f.setAccessible(true); f.set(u, f.get(t)); }catch(Throwable ex){ throw new RuntimeException(ex); } }; } }
使用方法
@Data public class Item{ private String id; private String name; private LocalDateTime createAt; private LocalDateTime updateAt; }
Item it1 と it2 が存在して、createAt フィールドを it1 → it2 にコピーします。
FieldCopy.of(t->"createAt", it2).accept(it1);
1つの属性ではなく、複数の属性を指定する場合、String[] を返すものを用意します。
import java.io.Serializable; import java.lang.reflect.Field; import java.util.function.Consumer; /** * FieldArrayCopy. 指定属性(配列)フィールドのコピー */ @FunctionalInterface public interface FieldArrayCopy<T> extends { String[] get(T t) throws Exception; /** * フィールドコピー実行Consumerの生成 * @param function コピー対象フィールド名の配列を返す関数型インターフェース FieldArrayCopy * @param t コピー先Object * @return コピー元を指定する Consumer */ public static <T> Consumer<T> of(FieldArrayCopy<T> function, T t){ return u->{ try{ for(String fname:function.get(t)){ Field f; try{ f = t.getClass().getField(fname); }catch(NoSuchFieldException e){ f = t.getClass().getDeclaredField(fname); } f.setAccessible(true); f.set(u, f.get(t)); } }catch(Throwable ex){ throw new RuntimeException(ex); } }; } }
Item it1 と it2 が存在して、createAt フィールドとupdateAtフィールド を it1 → it2 にコピーします。
FieldArrayCopy.of(t->new String[] { "createAt", "updateAt" }, it2).accept(it1);
複数の属性指定、List にする
import java.lang.reflect.Field; import java.util.List; import java.util.function.Consumer; /** * FieldListCopy. 指定属性(リスト)フィールドのコピー */ @FunctionalInterface public interface FieldListCopy<T>{ List<String> get(T t) throws Exception; /** * フィールドコピー実行Consumerの生成 * @param function コピー対象フィールド名のリストを返す関数型インターフェース FieldListCopy * @param t コピー先Object * @return コピー元を指定する Consumer */ public static <T> Consumer<T> of(FieldListCopy<T> function, T t){ return u->{ try{ for(String fname:function.get(t)){ Field f; try{ f = t.getClass().getField(fname); }catch(NoSuchFieldException e){ f = t.getClass().getDeclaredField(fname); } f.setAccessible(true); f.set(u, f.get(t)); } }catch(Throwable ex){ throw new RuntimeException(ex); } }; } }
FieldListCopy.of(t->List.of("createAt", "updateAt"), it2).accept(it1);
これらは、
https://github.com/yipuran/yipuran-core
に入れました。