Java中List淺拷貝深拷貝問題
關注可以查看更多粉絲專享blog~
淺拷貝
- 基本數據類型的成員變量,進行值傳遞(將該屬性值複製一份給新的對象)。
- 引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等進行引用傳遞(將該成員變量的引用值(內存地址)複製一份給新的對象)。
深拷貝
- 基本數據類型的成員變量,進行值傳遞(將該屬性值複製一份給新的對象)。
- 引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,會重新分配內存並將成員變量拷貝一份賦值給新對象(將該成員變量的內容複製一份到新開闢的內存上,新的對象指向新的內存地址)。
在開發過程中如果多處用到同一個List且會修改List元素內容的時候,如果想要不互相干擾就需要進行深拷貝。
混淆點(看上去像深拷貝的淺拷貝)
// 淺拷貝
List<Msg> source = new ArrayList<>();
List<Msg> target= new ArrayList<>(source);
// 淺拷貝
List<Msg> target= new ArrayList<>();
target.addAll(source);
混淆點示例
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Msg implements Serializable {
private static final long serialVersionUID = -3893311742256460336L;
private String detail;
private String body;
}
public static void main( String[] args ) {
List<Msg> msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
for (Msg msg : msgs) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
for (Msg msg : msgs) {
msg.setBody("two" + msg.getBody());
System.out.println(msg);
}
}
// output
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)
// 試試new ArrayList<>(source);
public static void main( String[] args ) {
List<Msg> msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List<Msg> one = new ArrayList<>(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List<Msg> two = new ArrayList<>(msgs);
for (Msg msg : two) {
msg.setBody("two" + msg.getBody());
System.out.println(msg);
}
}
// output 跟new之前一樣
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)
// 看看源碼
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
// 內部採用的Arrays.copyOf,看看裏面是什麼
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
// Arrays.copyOf
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
// 內部調用System.arraycopy 表面上生成了一個新數組,其實指針指向的是同一塊內存
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
// 試試target.addAll(source );
public static void main( String[] args ) {
List<Msg> msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List<Msg> one = new ArrayList<>();
one.addAll(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List<Msg> two = new ArrayList<>();
two.addAll(msgs);
for (Msg msg : two) {
msg.setBody("two" + msg.getBody());
System.out.println(msg);
}
}
// output 還是跟之前一樣
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twoonebody)
// 看看源碼
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
// 同樣使用的是System.arraycopy 表面上生成了一個新數組,其實指針指向的是同一塊內存
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
如何深拷貝
- 使用反射獲取源對象的成員變量進行設置(如:常見的BeanUtils,org.apache.commons.BeanUtils / org.springframework.beans.BeanUtils等)
- BeanUtils.cloneBean()
- BeanUtils.copyProperties()
- 使用IO序列化(HuTool)
- cn.hutool.core.util.ObjectUtil.cloneByStream
深拷貝示例
// BeanUtils.cloneBean
@SneakyThrows
public static void main( String[] args ) {
List<Msg> msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
for (Msg msg : msgs) {
Msg msg2 = (Msg) BeanUtils.cloneBean(msg);
msg2.setBody("one" + msg2.getBody());
System.out.println(msg2);
}
for (Msg msg : msgs) {
Msg msg2 = (Msg) BeanUtils.cloneBean(msg);
msg2.setBody("two" + msg2.getBody());
System.out.println(msg2);
}
}
// output ok
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twobody)
// IO序列化
public static void main( String[] args ) {
List<Msg> msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List<Msg> one = ObjectUtil.cloneByStream(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List<Msg> two = ObjectUtil.cloneByStream(msgs);
for (Msg msg : two) {
msg.setBody("two" + msg.getBody());
System.out.println(msg);
}
}
// output ok
Msg(detail=davids, body=onebody)
Msg(detail=davids, body=twobody)
兩種深拷貝性能比較
反射相對於IO序列化更依賴CPU和內存,IO序列化則相對而言更依賴內存和磁盤,我本機測試10w、100w、500w情況下IO序列化都略優於反射。