多次元配列(配列の配列)をcloneメソッドでディープコピーしたつもりになっているコードを見て、配列のcloneメソッドが要素のディープコピーはしてくれないのをうっかり忘れがちだよね、と思ったところで、ふと、汎用的な多次元配列のディープコピーが(JDK 7付属のクラスライブラリだけで)実装できるのか気になったのでゴニョゴニョしてみたところ、どうやらリフレクションを使ってできそうですが実用性は乏しい感じです。。。ちなみにCloneableでないオブジェトの扱いがいい加減(手抜き)です。
(2019/5/8 追記)
こんな古いガラクタ記事が こちら にノミネートされていることに気づいたため、実装例のコメントから「参照渡し」という表現をなくしてみました(汗)
リフレクションによる実装
ArrayUtils.java
import java.lang.reflect.Array;
import java.lang.reflect.Method;
public class ArrayUtils {
public static Object deepClone(Object original) {
// 引数がnullもしくは非配列の場合はnullを返す
if (original == null || !original.getClass().isArray()) {
return null;
}
// 同期化
synchronized (original) {
// 配列の要素の型
Class<?> componentType = original.getClass().getComponentType();
// 配列の要素の数
int length = Array.getLength(original);
// 配列の複製(ガラ)を生成
Object duplicate = Array.newInstance(componentType, length);
try {
// 配列の要素の数だけループ処理
for (int i = 0; i < length; i++) {
Object item = Array.get(original, i);
if (item == null) {
// 要素がnullの場合
Array.set(duplicate, i, null);
}
else {
if (item.getClass().isArray()) {
// 要素が配列の場合は再帰処理開始
Object clone = deepClone(item);
Array.set(duplicate, i, componentType.cast(clone));
}
else {
//要素が配列ではない場合
Class<?> type = item.getClass();
if (type.cast(item) instanceof Cloneable) {
// Cloneableインターフェイスを実装していればcloneメソッドの戻り値を格納
Method clone = type.getMethod("clone");
Array.set(duplicate, i, type.cast(clone.invoke(item)));
}
else {
// Cloneableインターフェイスを実装していなければそのまま格納(ディープコピーできてないかも)
Array.set(duplicate, i, type.cast(item));
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return duplicate;
}
}
}
Serializableなオブジェクトの配列だけに限定すれば、次のような実装でもできるようですね。だた、パフォーマンスは。。。
シリアライズ・デシリアライズによる実装
ArrayUtils.java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
public class ArrayUtils {
public static Object deepClone(Object original) {
// 引数がnullもしくは非配列、シリアライズ不可の場合はnullを返す
if (original == null || !original.getClass().isArray() || !isSerializable(original)) {
return null;
}
// 同期化
synchronized(original) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
// シリアライズ・デシリアライズ
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(original);
oos.flush();
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// クローズ処理
if (ois != null) try { ois.close(); } catch (IOException ioe) {};
if (bais != null) try { bais.close(); } catch (IOException ioe) {};
if (oos != null) try { oos.close(); } catch (IOException ioe) {};
if (baos != null) try { baos.close(); } catch (IOException ioe) {};
}
}
}
public static boolean isSerializable(Object target) {
// 引数がnullもしくは非配列の場合はfalseを返す
if (target == null || !target.getClass().isArray()) {
return false;
}
// 配列の要素の型
Class<?> componentType = target.getClass().getComponentType();
// 配列の先頭の要素
Object item = Array.get(target, 0);
if (componentType.isArray()) {
// 要素が配列の場合は再帰処理の結果を返す
return isSerializable(item);
}
else {
if (componentType.isPrimitive() || componentType.cast(item) instanceof Serializable) {
// シリアライズ可
return true;
}
else {
// シリアライズ不可
return false;
}
}
}
}