什么是拷贝
面试中经常会有面试官问什么是浅拷贝、深拷贝,但是实际编码中我们使用的并不多,在了解深拷贝、浅拷贝前,我们先大概了解一下什么是拷贝,通俗的理解就是,创建一个和已存在的对象一模一样的对象。
关于浅拷贝
浅拷贝就是将被拷贝对象的基本类型属性和String类型属性进行进行复制,引用类型属性则指向原对象的引用类型对象的地址,改变基本类型和String类型的属性值时是独立的,不会影响拷贝对象的属性,但是如果改变引用类型对象的属性时,会影响到拷贝对象的属性。
关于深拷贝
和浅拷贝相同,区别就是会将被拷贝对象的引用对象属性重新生成,而不是指向原对象引用类型对象的引用地址,所以更改引用对象时不会阴影拷贝对象的属性。
简单实现深拷贝、浅拷贝
要实现浅拷贝和深拷贝的话要实现Cloneable接口,cloneable其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。
实现浅拷贝
@Setter
@Getter
@ToString
public class FileBo implements Cloneable, Serializable {
private static final long serialVersionUID = -1013261386913302645L;
private String no;
private int count;
private FileDetailBo fileDetailBo;
@Override
public FileBo clone() {
//浅拷贝
try {
return (FileBo) super.clone();
// return CloneUtils.deepClone(this);
} catch (CloneNotSupportedException e) {
System.out.println(Arrays.toString(e.getStackTrace()));
return new FileBo();
}
}
}
实现深拷贝
bean对象
@Setter
@Getter
@ToString
public class FileBo implements Cloneable, Serializable {
private static final long serialVersionUID = -1013261386913302645L;
private String no;
private int count;
private FileDetailBo fileDetailBo;
@Override
public FileBo clone() {
//深拷贝
try {
// return (FileBo) super.clone();
return CloneUtils.deepClone(this);
} catch (CloneNotSupportedException e) {
System.out.println(Arrays.toString(e.getStackTrace()));
return new FileBo();
}
}
}
一个深拷贝工具类
public final class CloneUtils {
private CloneUtils() {
}
;
public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {
// 保存对象为字节数组
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(bout)) {
out.writeObject(t);
}
// 从字节数组中读取克隆对象
try (InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {
ObjectInputStream in = new ObjectInputStream(bin);
return (T) in.readObject();
}
} catch (IOException | ClassNotFoundException e) {
CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
e.initCause(cloneNotSupportedException);
throw cloneNotSupportedException;
}
}
}
测试类-浅拷贝:
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
List<FileBo> listClone = new ArrayList<>();
FileBo fileBo = new FileBo();
fileBo.setNo("克隆体");
fileBo.setCount(100);
FileDetailBo fileDetailBo = new FileDetailBo();
fileDetailBo.setFileName("克隆体文件详情");
fileDetailBo.setFilePath("D://");
fileBo.setFileDetailBo(fileDetailBo);
long start = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
FileBo fileBo1 = fileBo.clone();
fileBo1.setNo("productNo" + i);
fileBo1.setCount(i);
listClone.add(fileBo1);
}
System.out.println("克隆耗时:" + (System.currentTimeMillis() - start));
List<FileBo> fileNew = new ArrayList<>();
long newBo = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
FileBo file = new FileBo();
file.setNo("productNo" + i);
file.setCount(i);
fileNew.add(file);
}
System.out.println("new对象耗时:" + (System.currentTimeMillis() - newBo));
if (listClone.size() < 10) {
System.out.println("克隆生成的:" + listClone);
}
//修改被克隆对象的引用对象属性及基本类型属性
fileBo.getFileDetailBo().setFileName("克隆体文件更改");
fileBo.setNo("克隆体更改");
fileBo.setCount(1000);
if (listClone.size() < 10) {
System.out.println("更改克隆体后克隆生成的:" + listClone);
}
System.out.println("克隆体:" + fileBo);
}
}
输出:
由控制台输出可以看出,浅拷贝后,更改其被克隆对象的基本属性和String属性时,克隆的对象的属性并未改变,如no 和 count,但是引用类型属性却发生了改变-fileDetailBo。
测试类-深拷贝
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
List<FileBo> listClone = new ArrayList<>();
FileBo fileBo = new FileBo();
fileBo.setNo("克隆体");
fileBo.setCount(100);
FileDetailBo fileDetailBo = new FileDetailBo();
fileDetailBo.setFileName("克隆体文件详情");
fileDetailBo.setFilePath("D://");
fileBo.setFileDetailBo(fileDetailBo);
long start = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
FileBo fileBo1 = fileBo.clone();
fileBo1.setNo("productNo" + i);
fileBo1.setCount(i);
listClone.add(fileBo1);
}
System.out.println("克隆耗时:" + (System.currentTimeMillis() - start));
List<FileBo> fileNew = new ArrayList<>();
long newBo = System.currentTimeMillis();
for (int i = 0; i < 3; i++) {
FileBo file = new FileBo();
file.setNo("productNo" + i);
file.setCount(i);
fileNew.add(file);
}
System.out.println("new对象耗时:" + (System.currentTimeMillis() - newBo));
if (listClone.size() < 10) {
System.out.println("克隆生成的:" + listClone);
}
//修改被克隆对象的引用对象属性及基本类型属性
fileBo.getFileDetailBo().setFileName("克隆体文件更改");
fileBo.setNo("克隆体更改");
fileBo.setCount(1000);
if (listClone.size() < 10) {
System.out.println("更改克隆体后克隆生成的:" + listClone);
}
System.out.println("克隆体:" + fileBo);
}
}
输出:
从输出结果看得出,更改被克隆对象的属性时,引用类型属性更改不会影响克隆对象的属性,也就是该引用类型对象是各自独立的,不是指向同一个引用。
注意:测试时记得将FileBO对象的clone更改一下,测试哪个就用哪个拷贝方式。
拷贝和new创建对象的性能简单对比
我们用克隆和new创建150万个对象看一下创建耗时,毫秒:
测试类-浅拷贝效率:
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
List<FileBo> listClone = new ArrayList<>();
FileBo fileBo = new FileBo();
fileBo.setNo("克隆体");
fileBo.setCount(100);
FileDetailBo fileDetailBo = new FileDetailBo();
fileDetailBo.setFileName("克隆体文件详情");
fileDetailBo.setFilePath("D://");
fileBo.setFileDetailBo(fileDetailBo);
long start = System.currentTimeMillis();
for (int i = 0; i < 1500000; i++) {
FileBo fileBo1 = fileBo.clone();
fileBo1.setNo("productNo" + i);
fileBo1.setCount(i);
listClone.add(fileBo1);
}
System.out.println("克隆耗时:" + (System.currentTimeMillis() - start));
List<FileBo> fileNew = new ArrayList<>();
long newBo = System.currentTimeMillis();
for (int i = 0; i < 1500000; i++) {
FileBo file = new FileBo();
file.setNo("productNo" + i);
file.setCount(i);
fileNew.add(file);
}
System.out.println("new对象耗时:" + (System.currentTimeMillis() - newBo));
if (listClone.size() < 10) {
System.out.println("克隆生成的:" + listClone);
}
//修改被克隆对象的引用对象属性及基本类型属性
fileBo.getFileDetailBo().setFileName("克隆体文件更改");
fileBo.setNo("克隆体更改");
fileBo.setCount(1000);
if (listClone.size() < 10) {
System.out.println("更改克隆体后克隆生成的:" + listClone);
}
System.out.println("克隆体:" + fileBo);
}
}
输出:
测试类-深拷贝效率:
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
List<FileBo> listClone = new ArrayList<>();
FileBo fileBo = new FileBo();
fileBo.setNo("克隆体");
fileBo.setCount(100);
FileDetailBo fileDetailBo = new FileDetailBo();
fileDetailBo.setFileName("克隆体文件详情");
fileDetailBo.setFilePath("D://");
fileBo.setFileDetailBo(fileDetailBo);
long start = System.currentTimeMillis();
for (int i = 0; i < 1500000; i++) {
FileBo fileBo1 = fileBo.clone();
fileBo1.setNo("productNo" + i);
fileBo1.setCount(i);
listClone.add(fileBo1);
}
System.out.println("克隆耗时:" + (System.currentTimeMillis() - start));
List<FileBo> fileNew = new ArrayList<>();
long newBo = System.currentTimeMillis();
for (int i = 0; i < 1500000; i++) {
FileBo file = new FileBo();
file.setNo("productNo" + i);
file.setCount(i);
fileNew.add(file);
}
System.out.println("new对象耗时:" + (System.currentTimeMillis() - newBo));
if (listClone.size() < 10) {
System.out.println("克隆生成的:" + listClone);
}
//修改被克隆对象的引用对象属性及基本类型属性
fileBo.getFileDetailBo().setFileName("克隆体文件更改");
fileBo.setNo("克隆体更改");
fileBo.setCount(1000);
if (listClone.size() < 10) {
System.out.println("更改克隆体后克隆生成的:" + listClone);
}
System.out.println("克隆体:" + fileBo);
}
}
输出:
总结:从上面发现,不管是浅拷贝还是深拷贝,效率都没有new创建对象高,而且因为深拷贝涉及流的操作,效率也是低于浅拷贝的。但是结果并不是绝对的,其实如果是对复杂对象的拷贝的话,比如需要对属性进行计算,分割等操作时,进行克隆还是比new要快很多的。
小白一枚,如有错误还请多多指正,非常感谢。