我们知道Java中数据类型分为基本数据类型以及引用数据类型。
一,数据类型的分类
基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
浅拷贝与深拷贝的区别
- 浅拷贝:拷贝对象与原对象的引用时同一个对象
- 深拷贝:拷贝对象与原对象的引用为不同对象
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
引用数据类型使用在栈中,引用变量指向堆中实际存放数据的地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
二,深拷贝与浅拷贝
- 首先需要明白深拷贝与浅拷贝都是针对已有的对象进行操作的
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。(拷贝对象与原对象的引用时同一个对象)
- 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。(拷贝对象与原对象的引用为不同对象)
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
从内存的角度(栈与堆)分析
下面直接上例子吧
String[] str = {"qi","jian"}; String[] s = str;//这就是一个简单的浅拷贝
其中s与str都是指向了同一块内存空间,也就是说str只是把自己的一份地址拷贝给了s,并没有对数据进行拷贝一份给s.
接下来试试深度克隆。
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class ChildClass { private String name; private int age; }
import lombok.Data; @Data public class FatherClass implements Cloneable{ private String name; private int age; public ChildClass child; @Override protected Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { FatherClass fatherA = new FatherClass(); fatherA.setName("qijian"); fatherA.setAge(18); FatherClass fatherB = (FatherClass) fatherA.clone(); System.out.println(fatherB==fatherA); System.out.println(fatherA.hashCode()); System.out.println(fatherB.hashCode()); System.out.println(fatherA.getName()); System.out.println(fatherB.getName()); /** * 输出: * false * 972144312 * 972144312 * qijian * qijian */ System.out.println("##########################"); fatherA.child = new ChildClass(); fatherA.child.setName("son"); fatherA.child.setAge(1); FatherClass fatherC = (FatherClass) fatherA.clone(); System.out.println(fatherC==fatherA); System.out.println(fatherA.hashCode()); System.out.println(fatherC.hashCode()); System.out.println(fatherA.getName()); System.out.println(fatherC.getName()); System.out.println(fatherA.child == fatherC.child); System.out.println(fatherA.child.hashCode()==fatherC.child.hashCode()); System.out.println(fatherA.child.hashCode()); System.out.println(fatherC.child.hashCode()); /**输出: * false * 978877645 * 978877645 * qijian * qijian * true * true * 6733376 * 6733376 */ } }
从上面的代码中可以发现我一度的想要通过Object.clone()方法来实现深度拷贝,之后也就以失败而告终。最后终于去看里看源码。源码中对clone() 方法是这样说的
The method clone for class Object performs a specific cloning operation. First, if the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown. Note that all arrays are considered to implement the interface Cloneable and that the return type of the clone method of an array type T[] is T[] where T is any reference or primitive type. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation.
大致的译文就是:
Object类的 clone 方法执行特定的克隆操作。首先,如果该对象的类没有实现 Cloneable 接口,则抛出 CloneNotSupportedException 异常。值得注意的是所有的数组都被认为是实现 Cloneable 接口的,并且数组类型T[]的 clone 方法的返回类型是T[],其中T是任何引用或原始类型。否则,该方法创建该对象的类的一个新实例,并使用该对象对应字段的内容初始化其所有字段,就像通过赋值一样;字段的内容本身没有被克隆。因此,该方法执行该对象的“浅拷贝”,而不是“深拷贝”操作。
所以我们知道:Object的clone()方法是浅拷贝的,就上面方法的克隆体中 return super.clone(); 不管我是怎么努力都是做不到深度克隆的。
实现深度克隆的方法:
- 实现方法一:
利用构造函数实现深拷贝
import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor @AllArgsConstructor public class AddressConstruct { private String address1; private String address2; }
@Setter @Getter @NoArgsConstructor @AllArgsConstructor public class Stu { private String name; private AddressConstruct address; public static void main(String[] args) { AddressConstruct address = new AddressConstruct("nanchang","ganzhou"); Stu stu = new Stu("qijian",address); // 调用构造函数进行深拷贝 Stu copyStu = new Stu(stu.getName(), new AddressConstruct("shenzhen","ganzhou")); System.out.println(stu+"\n"+copyStu +"\n"+"stu==copy?:"+(stu==copyStu)); System.out.println(stu.getAddress()+"\n"+copyStu.getAddress()+"\n"+"stu.getAddress()==copyStu.getAddress()?:"+(stu.getAddress()==copyStu.getAddress())); } }
- 实现方法二:
重载clone()方法实现深度拷贝
import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter @NoArgsConstructor @AllArgsConstructor public class AddressClone implements Cloneable{ private String address1; private String address2; @Override public AddressClone clone() throws CloneNotSupportedException { return (AddressClone)super.clone(); } }
import lombok.*; @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class StuClone implements Cloneable{ private String name; private AddressClone address; /** * * @return * @throws CloneNotSupportedException * Object类中的clone()方法: * protected native Object clone() throws CloneNotSupportedException; * 克隆时需要注意的几个关键问题: * 几乎肯定要调用super.clone(),以及注意将克隆设为 public。 */ @Override public StuClone clone() throws CloneNotSupportedException { //需要注意的是super.clone()是浅拷贝。 //这里应该使用AddressClone的clone方法。 StuClone stu = (StuClone) super.clone(); stu.setAddress(this.getAddress().clone()); return stu; } public static void main(String[] args) throws CloneNotSupportedException { AddressClone address = new AddressClone("nanchang","ganzhou"); StuClone stu = new StuClone("qijian",address); StuClone copystu = stu.clone(); System.out.println(stu + "\n" +copystu); System.out.println("stu.getAddress()==copystu.getAddress()?:"+(stu.getAddress()==copystu.getAddress())); } }
- 序列化与反序列化
@Setter @Getter @NoArgsConstructor @AllArgsConstructor @ToString public class AdressSerializable implements Serializable { private String address1; private String address2; }
import lombok.*; import java.io.*; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString public class StuSerializable implements Serializable { private String name; private AdressSerializable address; public static void main(String[] args) throws IOException, ClassNotFoundException { StuSerializable stu = new StuSerializable("qijian",new AdressSerializable("nanchang","ganzhou")); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\stu.txt")); oos.writeObject(stu); oos.close(); System.out.println(stu+" \n " ); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\stu.txt")); Object o = ois.readObject(); ois.close(); StuSerializable stu1 = (StuSerializable) o; System.out.println(stu1); System.out.println(stu==stu1); } }