浅拷贝就是指两个对象共同拥有同一个值,一个对象改变了该值,也会影响到另一个对象。
深拷贝就是两个对象的值相等,但是互相独立。
构造函数的参数是该类的一个实例。
Operator = | 拷贝构造函数 | clone方法 | ||
预定义非集合类型 | 深拷贝 | 如果支持拷贝构造函数的类型,则是深拷贝 | 不支持 | |
自定义类型 | 浅拷贝 | 取决于实现 | 取决于实现 | |
预定义集合类型 | 浅拷贝 | 会逐个调用每个元素的operator=方法 |
|
1.介绍
Java 中的拷贝构造方法是一种使用该类的一个对象构造另外一个对象的构造方法。
当需要拷贝一个带有多个成员变量的复杂对象或者想构造已存在对象的深拷贝对象时非常有用。
译者注:本文内容很简单,但是很实用。拷贝构造方法实战中用虽然用的不多,但是是一个非常不错的技巧。
2.如何创造拷贝构造方法
要创建拷贝构造方法,首先需要声明带有和本类相同类型的参数构造函数:
public class Employee {
private int id;
private String name;
public Employee(Employee employee) {
}
}
然后,将参数对象的每个属性都复制给新的实例。
public class Employee {
private int id;
private String name;
public Employee(Employee employee) {
this.id = employee.id;
this.name = employee.name;
}
}
上面的做法属于浅拷贝。
上面定义的属性不是int 就是 String, 只包含基本类型和不可变类型,因此使用前拷贝就没问题。
但是如果类中包含可变类型就要通过该构造函数实现深拷贝。
为了实现深拷贝,我们需要根据原始可变对象类型构造新的实例。
public class Employee {
private int id;
private String name;
private Date startDate;
public Employee(Employee employee) {
this.id = employee.id;
this.name = employee.name;
this.startDate = new Date(employee.startDate.getTime());
}
}
3.拷贝构造方法 VS Clone
在 Java 中,我们还可以使用 clone 方法实现根据已有对象创建新对象。
但是拷贝构造方法更有优势:
拷贝构造方法实现更简单。不需要实现 Cloneable 接口,也不需要处理 CloneNotSupportedException
clone 函数返回一个普通的 Object 类型的引用。还需要转成特定的类型。
在 clone 方法中不能为 final 属性赋值,但是在拷贝构造方法中就可以。
4.继承问题
Java 中的拷贝构造方法不会被子类继承。
因此,如果我们尝试初始化一个带有父类引用的子类对象,就会面临着类型转换问题。
为了更好地说明这个问题,我们首先创建 Employee的子类型和拷贝构造方法。
public class Manager extends Employee {
private List directReports;
// ... 其他构造方法
public Manager(Manager manager) {
super(manager.id, manager.name, manager.startDate);
this.directReports = directReports.stream()
.collect(Collectors.toList());
}
}
然后,我们声明一个 Employee 类型的引用指向通过 Manager 构造方法构造的 Manager 实例。
Employee source = new Manager(1, "Baeldung Manager", startDate, directReports);
由于引用类型为 Employee, 如果我们想使用 Manager 的拷贝构造函数就必须将 source 强转为 Manager 类型。
Employee clone = new Manager((Manager) source);
如果参数不是 Manager 类型,运行时会抛出 ClassCastException。
其中一种避免使用拷贝构造方法时类型转换的方法是创建一个继承的拷贝函数:
public class Employee {
public Employee copy() {
return new Employee(this);
}
}
public class Manager extends Employee {
@Override
public Employee copy() {
return new Manager(this);
}
}
在每个类的拷贝函数中调用自己类型的拷贝构造函数即可。
这样就保证了生成的对象和调用的对象类型相同。
Employee clone = source.copy();
5.结论
本文介绍了拷贝构造方法,给出了避免使用 clone 函数的原因。
如果引用类型为父类型而实际对象类型为子类型时,使用子类型的拷贝构造函数需要将父类型强制类型转换为子类型,容易出现转换问题。本文也针对这个问题提供了解决方案。
如果你觉得本文对你有帮助,欢迎点赞、转发、评论,你的支持是我创作的最大动力。
另外想学习,更多开发和避坑技巧,少走弯路,请关注我的专栏:《阿里巴巴Java 开发手册》详解专栏
浅拷贝就是指两个对象共同拥有同一个值,一个对象改变了该值,也会影响到另一个对象。
深拷贝就是两个对象的值相等,但是互相独立。
构造函数的参数是该类的一个实例。
Operator = 拷贝构造函数 clone方法 预定义非集合类型 深拷贝 如果支持拷贝构造函数的类型,则是深拷贝 不支持 自定义类型 浅拷贝 取决于实现 取决于实现 预定义集合类型 浅拷贝 会逐个调用每个元素的operator=方法
会逐个调用每个元素的operator=方法 转载于:https://www.cnblogs.com/newcoder/p/5771840.html
浅复制(浅克隆) :被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制(深克隆) :被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
1 package com.itinfo; 2 3 /** 4 * 测试实体类 5 * 6 * @author Wáng Chéng Dá 7 * @create 2017-02-24 10:41 8 */ 9 public class PersonT { 10 11 private String name; 12 13 private int age; 14 15 private String sex; 16 17 public PersonT() { 18 } 19 20 public PersonT(String name, int age, String sex) { 21 this.name = name; 22 this.age = age; 23 this.sex = sex; 24 } 25 26 public PersonT(PersonT personT) { 27 this.name = personT.name; 28 this.age = personT.age; 29 this.sex = personT.sex; 30 } 31 32 public String getName() { 33 return name; 34 } 35 36 public void setName(String name) { 37 this.name = name; 38 } 39 40 public int getAge() { 41 return age; 42 } 43 44 public void setAge(int age) { 45 this.age = age; 46 } 47 48 public String getSex() { 49 return sex; 50 } 51 52 public void setSex(String sex) { 53 this.sex = sex; 54 } 55 56 @Override 57 public String toString() { 58 return "PersonT{" + 59 "name='" + name + '\'' + 60 ", age=" + age + 61 ", sex='" + sex + '\'' + 62 '}'; 63 } 64 }
深拷贝:
1 package com.itinfo; 2 3 /** 4 * 浅析浅拷贝和深拷贝 5 * 6 * @author Wáng Chéng Dá 7 * @create 2017-02-24 8:22 8 */ 9 public class Asian { 10 private String skin; 11 private PersonT person; 12 13 public Asian() { 14 } 15 16 public Asian(String skin, PersonT person) { 17 this.skin = skin; 18 this.person = person; 19 } 20 21 public Asian(Asian asian) { 22 this.skin = asian.skin; 23 this.person = new PersonT(asian.person); 24 } 25 26 // public Asian(Asian asian) { 27 // this(asian.skin, asian.person); 28 // } 29 30 public String getSkin() { 31 return skin; 32 } 33 34 public void setSkin(String skin) { 35 this.skin = skin; 36 } 37 38 public PersonT getPerson() { 39 return person; 40 } 41 42 public void setPerson(PersonT person) { 43 this.person = person; 44 } 45 46 @Override 47 public String toString() { 48 return "Asian{" + 49 "skin='" + skin + '\'' + 50 ", person=" + person.toString() + 51 '}'; 52 } 53 54 55 public static void main(String[] args) { 56 PersonT p1 = new PersonT("张三", 14, "男"); 57 PersonT p2 = new PersonT(p1); 58 System.out.println(p1.toString()); 59 System.out.println(p2.toString()); 60 61 //修改p1 62 System.out.println("---------修改p1----------"); 63 p1.setName("李四"); 64 p1.setAge(19); 65 p1.setSex("女"); 66 System.out.println(p1.toString()); 67 System.out.println(p2.toString()); 68 69 //修改p2 70 System.out.println("---------修改p2----------"); 71 p2.setName("王二"); 72 p2.setAge(23); 73 p2.setSex("男"); 74 System.out.println(p1.toString()); 75 System.out.println(p2.toString()); 76 77 78 System.out.println("-------------------------"); 79 Asian a1 = new Asian("yellow", p1); 80 Asian a2 = new Asian(a1); 81 System.out.println(a1.toString()); 82 System.out.println(a2.toString()); 83 84 //修改a1 85 System.out.println("---------修改a1----------"); 86 a1.setSkin("back"); 87 a1.getPerson().setName("麻子"); 88 a1.getPerson().setAge(28); 89 a1.getPerson().setSex("男"); 90 System.out.println(a1.toString()); 91 System.out.println(a2.toString()); 92 93 //修改a2 94 System.out.println("---------修改a2----------"); 95 a2.setSkin("red"); 96 a2.getPerson().setName("诗诗"); 97 a2.getPerson().setAge(25); 98 a2.getPerson().setSex("女"); 99 System.out.println(a1.toString()); 100 System.out.println(a2.toString()); 101 102 } 103 }
控制台输出:
PersonT{name='张三', age=14, sex='男'}
PersonT{name='张三', age=14, sex='男'}
---------修改p1----------
PersonT{name='李四', age=19, sex='女'}
PersonT{name='张三', age=14, sex='男'}
---------修改p2----------
PersonT{name='李四', age=19, sex='女'}
PersonT{name='王二', age=23, sex='男'}
-------------------------
Asian{skin='yellow', person=PersonT{name='李四', age=19, sex='女'}}
Asian{skin='yellow', person=PersonT{name='李四', age=19, sex='女'}}
---------修改a1----------
Asian{skin='back', person=PersonT{name='麻子', age=28, sex='男'}}
Asian{skin='yellow', person=PersonT{name='李四', age=19, sex='女'}}
---------修改a2----------
Asian{skin='back', person=PersonT{name='麻子', age=28, sex='男'}}
Asian{skin='red', person=PersonT{name='诗诗', age=25, sex='女'}}
内存分析:
浅拷贝:
1 package com.itinfo; 2 3 /** 4 * 浅析浅拷贝和深拷贝 5 * 6 * @author Wáng Chéng Dá 7 * @create 2017-02-24 8:22 8 */ 9 public class Asian { 10 private String skin; 11 private PersonT person; 12 13 public Asian() { 14 } 15 16 public Asian(String skin, PersonT person) { 17 this.skin = skin; 18 this.person = person; 19 } 20 21 // public Asian(Asian asian) { 22 // this.skin = asian.skin; 23 // this.person = new PersonT(asian.person); 24 // } 25 26 public Asian(Asian asian) { 27 this(asian.skin, asian.person); 28 } 29 30 public String getSkin() { 31 return skin; 32 } 33 34 public void setSkin(String skin) { 35 this.skin = skin; 36 } 37 38 public PersonT getPerson() { 39 return person; 40 } 41 42 public void setPerson(PersonT person) { 43 this.person = person; 44 } 45 46 @Override 47 public String toString() { 48 return "Asian{" + 49 "skin='" + skin + '\'' + 50 ", person=" + person.toString() + 51 '}'; 52 } 53 54 55 public static void main(String[] args) { 56 PersonT p1 = new PersonT("张三", 14, "男"); 57 PersonT p2 = new PersonT(p1); 58 System.out.println(p1.toString()); 59 System.out.println(p2.toString()); 60 61 //修改p1 62 System.out.println("---------修改p1----------"); 63 p1.setName("李四"); 64 p1.setAge(19); 65 p1.setSex("女"); 66 System.out.println(p1.toString()); 67 System.out.println(p2.toString()); 68 69 //修改p2 70 System.out.println("---------修改p2----------"); 71 p2.setName("王二"); 72 p2.setAge(23); 73 p2.setSex("男"); 74 System.out.println(p1.toString()); 75 System.out.println(p2.toString()); 76 77 78 System.out.println("-------------------------"); 79 Asian a1 = new Asian("yellow", p1); 80 Asian a2 = new Asian(a1); 81 System.out.println(a1.toString()); 82 System.out.println(a2.toString()); 83 84 //修改a1 85 System.out.println("---------修改a1----------"); 86 a1.setSkin("back"); 87 a1.getPerson().setName("麻子"); 88 a1.getPerson().setAge(28); 89 a1.getPerson().setSex("男"); 90 System.out.println(a1.toString()); 91 System.out.println(a2.toString()); 92 93 //修改a2 94 System.out.println("---------修改a2----------"); 95 a2.setSkin("red"); 96 a2.getPerson().setName("诗诗"); 97 a2.getPerson().setAge(25); 98 a2.getPerson().setSex("女"); 99 System.out.println(a1.toString()); 100 System.out.println(a2.toString()); 101 102 } 103 }
控制台输出:
PersonT{name='张三', age=14, sex='男'}
PersonT{name='张三', age=14, sex='男'}
---------修改p1----------
PersonT{name='李四', age=19, sex='女'}
PersonT{name='张三', age=14, sex='男'}
---------修改p2----------
PersonT{name='李四', age=19, sex='女'}
PersonT{name='王二', age=23, sex='男'}
-------------------------
Asian{skin='yellow', person=PersonT{name='李四', age=19, sex='女'}}
Asian{skin='yellow', person=PersonT{name='李四', age=19, sex='女'}}
---------修改a1----------
Asian{skin='back', person=PersonT{name='麻子', age=28, sex='男'}}
Asian{skin='yellow', person=PersonT{name='麻子', age=28, sex='男'}}
---------修改a2----------
Asian{skin='back', person=PersonT{name='诗诗', age=25, sex='女'}}
Asian{skin='red', person=PersonT{name='诗诗', age=25, sex='女'}}
内存分析:
转载于:https://www.cnblogs.com/goodcheap/p/6438365.html
浅拷贝与深拷贝
浅拷贝就是指两个对象共同拥有同一个值,一个对象改变了该值,也会影响到另一个对象。深拷贝就是两个对象的值相等,但是互相独立。
Java中常用的拷贝操作有三个,operator =、拷贝构造函数和 clone()方法。由于Java不支持运算符重载,我们无法在自己的自定义类型中定义operator=。拷贝构造函数大家应该很熟悉,如果我们要使自己定义的对象能够深拷贝,就改写从 Object继承而来的 clone方法,使它的访问权限为 public,因为为了防止意外的支持 clone操作,Object的 clone方法是 protected权限。
浅拷贝:
深拷贝:
利用序列化来做深复制,把对象写到流里的过程是序列化(Serilization)过程,而把对象从流中读出来的过程则叫做反序列化(Deserialization)过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。,利用这个特性,可以做深拷贝
java序列化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
如果被写对象的类型是String,或数组,或Enum,或Serializable,那么就可以对该对象进行序列化,否则将抛出NotSerializableException。
如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大
无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。如果是单例模式下的话,可以在readResolve中直接返回单例对象。