オブジェクト間フィールドのコピー

任意のオブジェクト間で、特定のフィールドだけをコピーするのに汎用的な方法を考えます。
これを考えるきっかけは、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
に入れました。