配列のShallowCopyとDeepCopyの実現方法
- 概要
Javaで、配列をDeepCopyしListに追加する要件があったのでその時に試したサンプルコードをうpする。
DeepCopyの場合は面倒くさいことに新しいインスタンスに値を移す作業が必要になる。
クラスが保持するプロパティが多くなると更に面倒くさいことになる。
CommonsのBeanUtils#cloneBeanを利用してみた。
利用するにはクローンを作成するクラスには引数なしのコンストラクタが必要。
それがないとInstantiationExceptionが発生する。
- クラス構成
DataObject内部にListを保持する。
さらに、DataObjectをListで宣言する。つまり、リストの中にリストを保持するようにする。
- 処理速度
BeanUtils#cloneBeanを利用しているためか処理時間は平均100ms
new DataObject宣言すると平均5ms以下となる。つまり、リフレクションは時間が掛かる。
処理速度を追い求めるなら自分でインスタンスを生成して値を写したほうが超絶早い。
package org.sample.koozz;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;
public class Client {
/** ロガー */
private static final Logger logger = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch(UUID.randomUUID().toString());
stopWatch.start();
Arrarys arrarys = new Arrarys();
DataObject[] dtos = new DataObject[] { new DataObject(UUID.randomUUID().toString()),
new DataObject(UUID.randomUUID().toString()),
new DataObject(UUID.randomUUID().toString()) };
arrarys.addAllToDeepCopy(dtos);
arrarys.console();
logger.info("以下、インスタンスの使い回し");
for (int i = 0; i < dtos.length; i++) {
dtos[i].setId(UUID.randomUUID().toString());
dtos[i].add(dtos[i].getId());
}
arrarys.addAllToDeepCopy(dtos);
arrarys.console();
logger.info("-----------------------------");
for (int i = 0; i < dtos.length; i++) {
dtos[i].setId(UUID.randomUUID().toString());
dtos[i].add(dtos[i].getId());
}
arrarys.addAllToDeepCopy(dtos);
// DeepCopyであることを証明するためにリストをクリアする。
for (DataObject dataObject : dtos) {
dataObject.clear();
}
arrarys.console();
logger.info("-----------------------------");
stopWatch.stop();
logger.info("処理時間 {}{}", stopWatch.getTotalTimeMillis(),"ms");
}
private static class Arrarys {
// リストの同期化
List<DataObject> list = Collections.synchronizedList(new ArrayList<DataObject>());
/**
* 受け取った{@code dtos}を{@code list}に追加する際に{@code DataObject}を新規追加し、
* 値を移し替えることでディープコピーを実現する。
* @param dtos {@code list}に追加する{@code DateObject}
*/
public void addAllToDeepCopy(DataObject... dtos) {
synchronized (list) {
DataObject[] deepCopy = new DataObject[dtos.length];
for (int i = 0; i < deepCopy.length; i++) {
deepCopy[i] = new DataObject(dtos[i].getId());
deepCopy[i].addAll(dtos[i].getList());
// try {
// deepCopy[i] = (DataObject)BeanUtils.cloneBean(dtos[i]);
// deepCopy[i].addAll(dtos[i].getList());
// // shallowCopy
// // Collections.copy(dtos[i].getList(), deepCopy[i].getList());
// }
// catch (IllegalAccessException e) {
// e.printStackTrace();
// }
// catch (InstantiationException e) {
// e.printStackTrace();
// }
// catch (InvocationTargetException e) {
// e.printStackTrace();
// }
// catch (NoSuchMethodException e) {
// e.printStackTrace();
// }
}
list.addAll(Arrays.asList(deepCopy));
}
}
@SuppressWarnings("unused")
public void addAllToShallowCopy(DataObject... dtos) {
synchronized (list) {
DataObject[] shallowCopy = new DataObject[dtos.length];
System.arraycopy(dtos, 0, shallowCopy, 0, shallowCopy.length);
list.addAll(Arrays.asList(shallowCopy));
}
}
public void console() {
synchronized (list) {
Iterator<DataObject> iterator = list.iterator();
while (iterator.hasNext()) {
DataObject dto = (DataObject) iterator.next();
logger.info(dto.toString());
}
}
}
}
}
package org.sample.koozz;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
public class DataObject {
private String id;
private List<String> list = new ArrayList<>();
/**
*
* @see java.util.List#clear()
*/
public void clear() {
list.clear();
}
/**
* @return list
*/
public List<String> getList() {
return list;
}
/**
* @param c
* @return
* @see java.util.List#addAll(java.util.Collection)
*/
public boolean addAll(Collection<? extends String> c) {
return list.addAll(new ArrayList<>(c));
}
/**
* @return
* @see java.util.List#toArray()
*/
public Object[] toArray() {
return list.toArray();
}
/**
* @param e
* @return
* @see java.util.List#add(java.lang.Object)
*/
public boolean add(String e) {
return list.add(e);
}
/**
* @param id
*/
public DataObject(String id) {
super();
this.id = id;
}
public DataObject() {
super();
}
/**
* @return id
*/
public String getId() {
return id;
}
/**
* @param id
* セットする id
*/
public void setId(String id) {
this.id = id;
}
/* (非 Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("DataObject [id=");
builder.append(id);
builder.append("]");
builder.append(" Size" + list.size());
builder.append(Arrays.toString(list.toArray()));
return builder.toString();
}
}
イミュータブルという特異性
イミュータブル(immutable)なオブジェクトとは、作成後にその状態を変えることのできないオブジェクトのことである。
対義語はミュータブル(mutable)なオブジェクトで、作成後も状態を変えることができる。
リストからのコピーについてStringとそれ以外のオブジェクトではこの特異性によってコピーに大きな差が生じる。
Stringはimmutableであり、それ以外の大体のクラスと自分が定義したクラスはmutableである。
※だいたいと記述したのはStringしかしらないから。
mutableをディープコピーする場合は、必ずシャローコピーになっていないことを確認すること。
package org.sample.koozz;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Client {
// Stringをリスト
static List<String> ranger = new ArrayList<String>() {
private static final long serialVersionUID = 1L;
{
add("アカレンジャー");
add("アオレンジャー");
add("キレンジャー");
add("ピンクレンジャー");
add("ミドレンジャー");
}
};
// Hogeをリスト
static List<Hoge> hoges = new ArrayList<Hoge>() {
private static final long serialVersionUID = 1L;
{
add(new Hoge("赤戦闘員"));
add(new Hoge("青戦闘員"));
add(new Hoge("黄戦闘員"));
add(new Hoge("桃戦闘員"));
add(new Hoge("緑戦闘員"));
}
};
public static void main(String[] args) {
// リストから配列 to DeepCopy
String[] rangerAry = ranger.toArray(new String[0]);
ranger.set(0, "赤レンジャー"); // 新しいインスタンス
System.out.println(ranger);
System.out.println(Arrays.toString(rangerAry));
// [赤レンジャー, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー] ------> コピー元
// [アカレンジャー, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー] ----> コピー先
// リストから配列 to DeepCopy
Hoge[] hhgg = hoges.toArray(new Hoge[0]);
hoges.set(0, new Hoge("朱戦闘員")); // 新しいインスタンス
System.out.println(hoges);
System.out.println(Arrays.toString(hhgg));
// [Hoge [piyo=朱戦闘員], Hoge [piyo=青戦闘員], Hoge [piyo=黄戦闘員], Hoge [piyo=桃戦闘員], Hoge [piyo=緑戦闘員]] ---> コピー元
// [Hoge [piyo=赤戦闘員], Hoge [piyo=青戦闘員], Hoge [piyo=黄戦闘員], Hoge [piyo=桃戦闘員], Hoge [piyo=緑戦闘員]] ---> コピー先
// ----------
// リストから配列 リストに追加後に内容を変更 to DeepCopy
String target = "ホワイトレンジャー";
ranger.add(target);
String[] rangerAry2 = ranger.toArray(new String[0]);
target = "ムラサキレンジャー"; // インスタンスの使い回し
ranger.set(ranger.size()-1, target);
System.out.println(ranger);
System.out.println(Arrays.toString(rangerAry2));
// [赤レンジャー, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー, ムラサキレンジャー] ---> コピー元
// [赤レンジャー, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー, ホワイトレンジャー] ---> コピー先
// リストから配列 リストに追加後に内容を変更 to DeepCopy?????? >> shallow Copy
Hoge hoge = new Hoge("白戦闘員");
hoges.add(hoge);
Hoge[] hhgg2 = hoges.toArray(new Hoge[0]);
hoge.setPiyo("ハイハイ戦闘員"); // インスタンスの使い回し
hoges.set(hoges.size()-1, hoge);
System.out.println(hoges);
System.out.println(Arrays.toString(hhgg2));
// !!!!! コピー先まで変わってる Shallow Copy
// [Hoge [piyo=朱戦闘員], Hoge [piyo=青戦闘員], Hoge [piyo=黄戦闘員], Hoge [piyo=桃戦闘員], Hoge [piyo=緑戦闘員], Hoge [piyo=ハイハイ戦闘員]]
// [Hoge [piyo=朱戦闘員], Hoge [piyo=青戦闘員], Hoge [piyo=黄戦闘員], Hoge [piyo=桃戦闘員], Hoge [piyo=緑戦闘員], Hoge [piyo=ハイハイ戦闘員]]
}
static class Hoge {
private String piyo;
/**
* @param piyo
*/
private Hoge(String piyo) {
super();
this.piyo = piyo;
}
/**
* @return piyo
*/
public String getPiyo() {
return piyo;
}
/**
* @param piyo
* セットする piyo
*/
public void setPiyo(String piyo) {
this.piyo = piyo;
}
/* (非 Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Hoge [piyo=");
builder.append(piyo);
builder.append("]");
return builder.toString();
}
}
}
コレクションのshallow copy と deep copy
コレクションのdeepcopyは簡単である。
ArrayListであればインスタンス生成時にコピー元をコンストラクタの引数にしてしまえばいい。
package org.sample.koozz;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class Client {
// Stringをリスト
static List<String> ranger = new ArrayList<String>() {
private static final long serialVersionUID = 1L;
{
add("アカレンジャー");
add("アオレンジャー");
add("キレンジャー");
add("ピンクレンジャー");
add("ミドレンジャー");
}
};
public static void main(String[] args) {
// shallow copy
Collection<String> copy1 = ranger;
ranger.set(0, "Hoge");
System.out.println(ranger + "コピー元");
System.out.println(copy1 + "コピー先");
// deep copy
Collection<String> copy2 = new ArrayList<>(ranger);
ranger.set(1, "Fuge");
System.out.println(ranger + "コピー元");
System.out.println(copy2 + "コピー先");
// [Hoge, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー]コピー元
// [Hoge, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー]コピー先
// [Hoge, Fuge, キレンジャー, ピンクレンジャー, ミドレンジャー]コピー元
// [Hoge, アオレンジャー, キレンジャー, ピンクレンジャー, ミドレンジャー]コピー先
}
}