关注可以查看更多粉丝专享blog~

浅拷贝

基本数据类型的成员变量,进行值传递(将该属性值复制一份给新的对象)。

引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等进行引用传递(将该成员变量的引用值(内存地址)复制一份给新的对象)。

深拷贝

基本数据类型的成员变量,进行值传递(将该属性值复制一份给新的对象)。

引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,会重新分配内存并将成员变量拷贝一份赋值给新对象(将该成员变量的内容复制一份到新开辟的内存上,新的对象指向新的内存地址)。

在开发过程中如果多处用到同一个List且会修改List元素内容的时候,如果想要不互相干扰就需要进行深拷贝。

混淆点(看上去像深拷贝的浅拷贝)

// 浅拷贝
List source = new ArrayList<>();
List target= new ArrayList<>(source);
// 浅拷贝
List 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 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 msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List one = new ArrayList<>(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List 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[] 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 msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List one = new ArrayList<>();
one.addAll(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List 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 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 msgs = new ArrayList<>();
Msg msg1 = new Msg("davids", "body");
msgs.add(msg1);
List one = ObjectUtil.cloneByStream(msgs);
for (Msg msg : one) {
msg.setBody("one" + msg.getBody());
System.out.println(msg);
}
List 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序列化都略优于反射。