对象的深拷贝与浅拷贝
JVM中对象复制的机制
一、浅复制:只对对象及变量值进行复制,如果这个对象中引用了其他对象的话,这个对象被复制以后,它引用对象的地址不变。(两个完全相同的对象会指向同一个引用对象)
在Object中,存在一个对象克隆的方法,这里的复制是浅复制
Dancer d1 = new Dancer(); d1.setName("Joey"); Dancer d2 = new Dancer(); d2.setName("Lily"); d1.setPartner(d2); //打印原始d1对象中的引用的d2对象的 Hash Code System.out.println("Partner:" + d2.hashCode()); //浅复制: Dancer shallow = (Dancer) d1.clone(); System.out.println("浅复制:" + shallow.getPartner().hashCode());
结果:
两个对象的引用对象的地址相同
Partner:2083562754 浅复制:2083562754
二、深复制:不仅对象及变量值进行复制,引用对象也进行复制。(这两个对象都有独立的引用对象,彼此互不干扰)
1)深复制的实现是基于序列化的方式完成的,因此想要实现深复制的对象,对象类应实现Cloneable和Serializable两个接口。
2)涉及到的类:ByteArrayOutputStream字节数组输出流、ObjectOutputStream对象输出流、ByteArrayInputStream字节数组输入流、ObjectInputStream对象输入流
/** * Cloneable 说明当前类的对象可以被克隆 * Serializable 可序列化接口,支撑深复制 */ public class Dancer implements Cloneable,Serializable{ private String name; private Dancer partner; public String getName() { return name; } public void setName(String name) { this.name = name; } public Dancer getPartner() { return partner; } public void setPartner(Dancer partner) { this.partner = partner; } /** * 深复制是基于序列化的方式完成 * @return 深复制的对象 */ public Dancer deepClone() throws IOException, ClassNotFoundException { //1.将内存中的对象输出为字节数组,bos对象保存了当前对象的字节数组 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //2.将字节数组反序列化,还原成对象,完成深复制的任务 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (Dancer)ois.readObject(); } }
调用方法,完成对象的深复制:
Dancer d1 = new Dancer(); d1.setName("Joey"); Dancer d2 = new Dancer(); d2.setName("Lily"); d1.setPartner(d2); //打印原始d1对象中的引用的d2对象的 Hash Code System.out.println("Partner:" + d2.hashCode()); //浅复制: Dancer shallow = (Dancer) d1.clone(); System.out.println("浅复制:" + shallow.getPartner().hashCode()); //深复制:将引用对象一并复制 Dancer deep = (Dancer) d1.deepClone(); System.out.println("深复制:" + deep.getPartner().hashCode());
结果:两个对象的引用对象的地址不同
Partner:2083562754 浅复制:2083562754 深复制:853119666
LeetCode 138复制带随机指针的链表
题目
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
- val:一个表示 Node.val 的整数。
- random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为null无序列表内容
你的代码只接受原链表的头节点 head 作为传入参数。
思路一:
第一步:创建新节点放在原节点后面,形成2倍长的大链表
第二步:设置新节点的随机节点
第三步:将新链表拆除出来
class Solution { public Node copyRandomList(Node head) { if(head==null) { return null; } Node p = head; //第一步,在每个原节点后面创建一个新节点 //1->1'->2->2'->3->3' while(p!=null) { Node newNode = new Node(p.val); newNode.next = p.next; p.next = newNode; p = newNode.next; } p = head; //第二步,设置新节点的随机节点 while(p!=null) { if(p.random!=null) { p.next.random = p.random.next; } p = p.next.next; } Node dummy = new Node(-1); p = head; Node cur = dummy; //第三步,将两个链表分离 while(p!=null) { cur.next = p.next; cur = cur.next; p.next = cur.next; p = p.next; } return dummy.next; } }