一文深入 Java 浅拷贝和深拷贝
- 2022 年 4 月 24 日
本文字数:6991 字
阅读完需:约 23 分钟
👨🏻🎓博主介绍:大家好,我是芝士味的椒盐,一名在校大学生,热爱分享知识,很高兴在这里认识大家🌟🌈擅长领域:Java、消息中间件、大数据、运维。
🙏🏻如果本文章各位小伙伴们有帮助的话,🍭关注+👍🏻点赞+🗣评论+📦收藏。
🤝另本人水平有限,旨在创作简单易懂的文章,在文章描述时如有错,恳请各位大佬指正,在此感谢!!!
值类型和引用类型
理解浅拷贝之前我们需要分清楚值类型(int、float...)、包装类(Integer、Double...)以及自己定义类等类,其实就就是值类型和引用类型两种.
就像上面这个图中,int a=1024 是值类型的所以是变量 a 就是直接等于实际的值 1024,而 object obj1、object obj2 显然是引用类型,obj 存储的不是实际的对象,而是对象在堆中的地址。
原型模式-浅拷贝和深拷贝图解
浅拷贝:根据上面来讲解,原型对象在被克隆后克隆出来的新对象和原型对象的地址是不一样的,在克隆出来对象中基础类型的直接复制一份的,也就是说,克隆对象修改值类型和原型对象修改各自的属性是没有半毛钱关系的,而其中的引用对象就不一样了,克隆出来的对象引用地址和原型对象的是同一个,也就说其中任意一个更改引用属性的时候都会影响到对方的属性,都是引用同一个对象。(包装类型比较特殊的引用类型,克隆之后双方都是互不干扰的)。
深拷贝:原型对象在通过深拷贝之后,基础类型仍旧是各自独立的,而各自引用对象的地址指向却是不同的,也就说在克隆对象和原型对象有任意一方修改引用参数都不会影响到对方。
先上个简单的 demo
/**
* <p>
* 学生卡类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class IdCard {
private String cName;
public IdCard(String cName) {
this.cName = cName;
}
public String getcName() {
return cName;
}
public void setcName(String cName) {
this.cName = cName;
}
@Override
public String toString() {
return "IdCard{" +
"cName='" + cName + '\'' +
'}';
}
}
/**
* <p>
* 学生类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class Student implements Cloneable {
private int id;
private String name;
private IdCard idCard;
.....省略get set方法
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", idCard=" + idCard +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
IdCard cId = new IdCard("hhhhh");
Student student1 = new Student(12,"student1", cId);
Student student2 = (Student) student1.clone();
student2.setId(1);
student2.setName("student2");
student2.idCard.setcName("xxxxx");
System.out.println(student1);
System.out.println(student2);
}
}
运行结果如下:
包装类型和值类型的如图可知在浅拷贝后是互不相干的,但是我们自定义的 IdCard 引用对象一个改了就会影响另一个,这就是由于是同一个对象缘由。
那么为什么我们自定义对象是可变包装类又特殊不可变呢?
其原因是遵守不可变原则,即 Immutable 设计模式:
类添加 final 修饰符,保证类不被继承。
保证所有成员变量必须私有,并且加上 final 修饰(不可变指的是引用不可变,也就是不可以重新指向其他对象)
不提供改变成员变量的方法,包括 setter
通过构造器初始化所有成员,进行深拷贝(deep copy)
在 getter 方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
比如我们随便拿一个包装类 Double 类分析一下
public final class Double extends Number implements Comparable<Double> {
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;
public static final double MAX_VALUE = 0x1.fffffffffffffP+1023; // 1.7976931348623157e+308
public static final double MIN_NORMAL = 0x1.0p-1022; // 2.2250738585072014E-308
@SuppressWarnings("unchecked")
public static final Class<Double> TYPE = (Class<Double>) Class.getPrimitiveClass("double");
public static String toString(double d) {
return FloatingDecimal.toJavaFormatString(d);
}
public static Double valueOf(String s) throws NumberFormatException {
return new Double(parseDouble(s));
}
public static Double valueOf(double d) {
return new Double(d);
}
public static double parseDouble(String s) throws NumberFormatException {
return FloatingDecimal.parseDouble(s);
}
public static boolean isNaN(double v) {
return (v != v);
}
public static boolean isInfinite(double v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
public static boolean isFinite(double d) {
return Math.abs(d) <= DoubleConsts.MAX_VALUE;
}
private final double value;
public Double(double value) {
this.value = value;
}
public Double(String s) throws NumberFormatException {
value = parseDouble(s);
}
public boolean isNaN() {
return isNaN(value);
}
public boolean isInfinite() {
return isInfinite(value);
}
首先这个 Double 类类是使用 final 关键字修饰表示不可继承,然后所有成员变量都是使用 final 修饰表示引用不可变,再其次就是不提供 setter 方法,即满足不可变原则。
如何浅拷贝
如上面 IdCard 和 Student 类就是实现 Cloneable 接口,并且重写了 clone 的方法,就可以实现。
如何深拷贝
有两种方法:一种继承 Cloneable 接口或者序列化
继承 Cloneable 接口
/**
* <p>
* 学生卡类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class IdCard implements Cloneable{
private Double cName;
public IdCard(Double cName) {
this.cName = cName;
}
public Double getcName() {
return cName;
}
public void setcName(Double cName) {
this.cName = cName;
}
@Override
public String toString() {
return "IdCard{" +
"cName='" + cName + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* <p>
* 学生类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class Student implements Cloneable {
private int id;
private String name;
private IdCard idCard;
public Student(int id, String name, IdCard idCard) {
this.id = id;
this.name = name;
this.idCard = idCard;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", idCard=" + idCard +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
//需要将引用的对象也clone
Student student = (Student)super.clone();
student.idCard = (IdCard)idCard.clone();
return student;
}
public static void main(String[] args) throws CloneNotSupportedException {
IdCard cId = new IdCard(12.5);
Student student1 = new Student(12,"student1", cId);
Student student2 = (Student) student1.clone();
student2.setId(1);
student2.setName("student2");
student2.idCard.setcName(10.2);
System.out.println(student1);
System.out.println(student2);
}
}
执行结果:
使用序列化和反序列化的方式深度拷贝,和 Cloneable 相比可以解决多层继承带来的深度克隆失效的问题
/**
* <p>
* 学生卡类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class IdCard implements Serializable {
private static final long serialVersionUID = 1L;
private Double cName;
public IdCard(Double cName) {
this.cName = cName;
}
public Double getcName() {
return cName;
}
public void setcName(Double cName) {
this.cName = cName;
}
@Override
public String toString() {
return "IdCard{" +
"cName='" + cName + '\'' +
'}';
}
}
/**
* <p>
* 学生类
* </p>
*
* @author starrysky
* @since 2022/2/8
*/
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private IdCard idCard;
public Student(int id, String name, IdCard idCard) {
this.id = id;
this.name = name;
this.idCard = idCard;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", idCard=" + idCard +
'}';
}
public Student deepClone() {
Student student = null;
try {
// 序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
//反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
student = (Student) objectInputStream.readObject();
}catch (ClassNotFoundException | IOException e){
e.printStackTrace();
}
return student;
}
public static void main(String[] args) throws CloneNotSupportedException {
IdCard cId = new IdCard(12.5);
Student student1 = new Student(12,"student1", cId);
Student student2 = (Student) student1.deepClone();
student2.setId(1);
student2.setName("student2");
student2.idCard.setcName(10.2);
System.out.println(student1);
System.out.println(student2);
}
}
执行结果:
集合浅拷贝和深拷贝
LinkedList 浅拷贝
通过构造方式
IdCard ggc = new IdCard(1, "ggc");
List<IdCard> list1 = new LinkedList<>();
list1.add(ggc);
List<IdCard> list2 = new LinkedList<>(list1);
list2.get(0).setName("mm");
System.out.println(list1);
System.out.println(list2);
执行结果:
通过 forEach 一个个插入
IdCard ggc = new IdCard(1, "ggc");
List<IdCard> list1 = new LinkedList<>();
list1.add(ggc);
List<IdCard> list2 = new LinkedList<>();
list1.forEach(s->list2.add(s));
list2.get(0).setName("mm");
System.out.println(list1);
System.out.println(list2);
执行结果:
数组通过 System.arraycopy
IdCard ggc = new IdCard(1, "ggc");
IdCard[] arr1 = new IdCard[]{ggc};
IdCard[] arr2 = new IdCard[1];
System.arraycopy(arr1,0,arr2,0,arr1.length);
arr2[0].setName("hhhh");
Arrays.asList(arr1).forEach(System.out::println);
Arrays.asList(arr2).forEach(System.out::println);
执行结果
LinkedList 深拷贝
通过 list 自带的 addAll()方法
List<Integer> list1 = new LinkedList<>(Arrays.asList(3,5,2,1));
List<Integer> list2 = new LinkedList<>();
list2.addAll(list1);
list2.remove(0);
System.out.println(list1);
System.out.println(list2);
执行结果:
通过序列化和反向序列化
/**
* <p>
*
* </p>学生卡
*
* @author starrysky
* @since 2022/2/8
*/
public class IdCard implements Serializable {
private static final long serialVersionUID = 1L;
private int Id;
private String name;
public IdCard(int id, String name) {
Id = id;
this.name = name;
}
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "IdCard{" +
"Id=" + Id +
", name='" + name + '\'' +
'}';
}
}
public static <T> List<T> deepClone(List<T> source) throws IOException, ClassNotFoundException {
//序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(source);
//反向序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
List<T> o = (List<T>)inputStream.readObject();
return o;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
List<IdCard> list1 = new LinkedList<>();
list1.add(new IdCard(1,"123"));
list1.add(new IdCard(2,"1233"));
List<IdCard> list2 = deepClone(list1);
list2.remove(0);
System.out.println(list1);
System.out.println(list2);
}
执行结果:
实现 Cloneable 接口,重写 clone 方法
@Override
public Object clone(){
IdCard clone =null;
try {
clone = (IdCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
IdCard idCard = new IdCard(1, "213");
IdCard idCard2 = (IdCard) idCard.clone();
idCard2.setName("gccc");
System.out.println(idCard);
System.out.println(idCard2);
执行结果:
版权声明: 本文为 InfoQ 作者【芝士味的椒盐】的原创文章。
原文链接:【http://xie.infoq.cn/article/5cd08eac267c479da1766474b】。文章转载请联系作者。
芝士味的椒盐
Java、消息中间件、大数据、运维 2022.04.23 加入
华为云云享专家、51CTOtop红人、CSDN博主、2021年第十届“中国软件杯”大学生软件设计大赛-B3-高并发条件下消息队列的设计与实现国赛二等奖、2021年浙江省职业院校技能大赛高职组“大数据技术与应用”赛项一等奖
评论