深拷贝vs浅拷贝

深拷贝vs浅拷贝_第1张图片

作者:金良([email protected]) csdn博客:http://blog.csdn.net/u012176591


  • 浅拷贝(shallow clone):拷贝的对象的成员中的基本类型(8大基本类型,见Java之基本类型和引用类型、引用传递和值传递、“==”和equals())与原来的的值相同,但是物理地址与原来不同,及拷贝成员与原成员“equals”为True而“==”为False。但是对象类型的成员则不同,我们知道对象名是引用,而拷贝成员和原成员引用地址相同。
  • 深拷贝(deep clone):深拷贝对基本类型的成员与浅拷贝相同,对对象类型的成员与深拷贝不同。它首相将对象类型的成员所引用的对象拷贝到新的地址,然后将该新的对象的的引用赋值给对应的对象类型的成员。

1.String是引用传递


public class stringTest {
	public static void changeStr(String str){
		str = str+" has been changed";此str与之前的str只是名字不同,但是值相同,指向地址也相同;但是string类不可改变,所以此str只能指向新的一块内存了,而之前的str仍指向原来的内存。
		System.out.println(str);//str has been changed
	}
	public static void changeData(StringBuffer strBuf) {
	    strBuf = strBuf.append(" has been changed");
	    System.out.println(strBuf);//Hello  has been changed
	}
	public static void main(String[] args) {
		String str = "myStr";//new String("myStr") //此str指向内存地址A
		changeStr(str);
		System.out.println(str);	
		
		
		StringBuffer sb = new StringBuffer("Hello ");
        changeData(sb);
        System.out.println(sb);
        
        //输出是
        //myStr has been changed
        //myStr
        //Hello  has been changed
        //Hello  has been changed
	}
}
String与StringBuffer的区别
简单地说,就是一个变量和常量的关系。StringBuffer对象的内容可以修改;而String对象一旦产生后就不可以被修改,重新赋值其实是两个对象。
       StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。
       String:在String类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个java字符串中的某个单独字符,所以在JDK文档中称String类的对象是不可改变的。然而,不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。                              
StringBuffer:StringBuffer类属于一种辅助类,可预先分配指定长度的内存块建立一个字符串缓冲区。这样使用StringBuffer类的append方法追加字符 比 String使用 + 操作符添加字符 到 一个已经存在的字符串后面有效率得多。因为使用 + 操作符每一次将字符添加到一个字符串中去时,字符串对象都需要寻找一个新的内存空间来容纳更大的字符串,这无凝是一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。使用StringBuffer类就避免了这个问题。
StringBuffer是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些

这也解释了下面是一个List的例子的输出为什么是这样的。可以看到当加入的元素是字符串时,将字符串拷贝到List里,所以List里的元素与原来的字符串不相等;当加入的元素是一个自自定义的类对象时,加入的是对象名,由于对象名是对象引用,故实际上加入List的是对象的引用。
     public static void main(String[] args) {
       List<String> myList = new ArrayList<String>();
       String b = "aa";
       myList.add(b);
       b = "cc";
       //下面的两个输出说明List列表添加字符串元素时是吧字符串的内容复制到List列表中,而不是字符串的引用
       System.out.println(myList.get(0));//"aa"
       System.out.println(myList.get(0)==b);//false
       
       class Person {
    	   //String name;
    	   int age;   
       }
       List<Person> myList2 = new ArrayList<Person>();
       Person p = new Person();
       //p.name = "name_1";
       p.age = 10;
       myList2.add(p);
       //p.name = "name_2";
       p.age = 0;
       //System.out.println(myList2.get(0).name);//name_2,说明List列表的元素是原Person对象的同一个引用
       System.out.println(myList2.get(0).age);//0
       System.out.println(myList2.get(0)==p);//true,List列表添加Person对象时,添加的这是该对象的引用
       
       List<String> oldList = new ArrayList<String>();
       oldList.add("0");
       oldList.add("2");
       oldList.add("3");
       List<String> newList = new ArrayList<String>(oldList);//复制了所有的字符串元素
       newList.set(1, "1+2");
       System.out.println(oldList.get(1));//2
       System.out.println(newList.get(1));//1+2
          
     }

2.实现Cloneable接口实现深拷贝

首先介绍一下类之间的关系。如下图所示,有两个类Employee和Employer,其中Employee类有一个成员是对象类型Employer。深拷贝Employee的一个对象,就是同时将这两个相关联的对象拷贝。设深拷贝得到的新对象为EmployeeObj2和EmployerObj2,原对象时EmployeeObj和EmployerObj,则对EmployeeObj2.employer(即EmployerObj2)的修改操作不会影响EmployeeObj.employer(即EmployerObj),反过来也成立,也就是拷贝对象和原对象完全脱离关系。
深拷贝vs浅拷贝_第2张图片
通过实现Cloneable接口实现深拷贝的Java代码如下:

public class Employ {
	class Employer implements Cloneable{
	    private String username;

	    public String getUsername() {
	        return username;
	    }

	    public void setUsername(String username) {
	        this.username = username;
	    }

	    @Override
	    public Object clone() throws CloneNotSupportedException {
	        return super.clone();
	    }
	}
	class Employee implements Cloneable{
	    private String username;
	    private Employer employer;
	    public String getUsername() {
	        return username;
	    }
	    public void setUsername(String username) {
	        this.username = username;
	    }
	    public Employer getEmployer() {
	        return employer;
	    }
	    public void setEmployer(Employer employer) {
	        this.employer = employer;
	    }

	    @Override
	    public Object clone() throws CloneNotSupportedException {
	        //克隆Employee对象并手动的进一步克隆Employee对象中包含的Employer对象
	        Employee employee = (Employee)super.clone();
	        employee.setEmployer((Employer) employee.getEmployer().clone());
	        return employee;
	    }
	}
	public static void main(String[] args) throws CloneNotSupportedException {
		Employ employ = new Employ();
	    Employer employer = employ.new Employer();
	    employer.setUsername("Jim");

	    Employee employee = employ.new Employee();
	    employee.setUsername("Linda");
	    employee.setEmployer(employer);

	    //employee2由employee深复制得到
	    Employee employee2 = (Employee) employee.clone();
	    //这样两个employee各自保存了两个employer
	    employee2.getEmployer().setUsername("Jack");
	    System.out.println(employee.getEmployer().getUsername());
	    System.out.println(employee2.getEmployer().getUsername());
	}
}
如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。故要覆盖clone方法必须实现Cloneable接口。



3.序列化实现深拷贝

Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。
对象序列化包括如下步骤:
(a)创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
(b)通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
(a)创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
(b)通过对象输入流的readObject()方法读取对象。
public class EmploySeriable {
	static class Employer implements Serializable{
		private static final long serialVersionUID = 1L;
	    private String name;

	    public String getName() {
	        return name;
	    }

	    public void setName(String name) {
	        this.name = name;
	    }
	}
	static class Employee  implements Serializable{
	    /**
		 * 
		 */
		private static final long serialVersionUID = 1L;
	    private String name;
	    private Employer employer;
	    public String getName() {
	        return name;
	    }
	    public void setName(String name) {
	        this.name = name;
	    }
	    public Employer getEmployer() {
	        return employer;
	    }
	    public void setEmployer(Employer employer) {
	        this.employer = employer;
	    }
	    /**
	     * 实现深复制的方法
	     */
	    public Object deepCopy() throws IOException, ClassNotFoundException{
	        //字节数组输出流,暂存到内存中
	        ByteArrayOutputStream bos = new ByteArrayOutputStream();
	        //序列化
	        ObjectOutputStream oos = new ObjectOutputStream(bos);
	        oos.writeObject(this);
	        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
	        ObjectInputStream ois = new ObjectInputStream(bis);
	        //反序列化
	        return ois.readObject();
	    }
	}
	public static void main(String[] args) throws IOException, ClassNotFoundException {
	    Employer employer = new Employer();
	    employer.setName("Linda");
	    Employee employee = new Employee();
	    employee.setName("jim");
	    employee.setEmployer(employer);
	    //通过深复制创建employee2
	    Employee employee2 = (Employee) employee.deepCopy();
	    employee2.getEmployer().setName("jack");

	    System.out.println(employee.getEmployer().getName());//Linda
	    System.out.println(employee2.getEmployer().getName());//jack
	}
}

4.附带一个字符串的性质总结
<span style="font-size:14px;font-weight: normal;">public class javaString {

	private static void test01(){
		String s0 = "kvill";
		String s1 = "kvill";
		String s2 = "kv" + "ill";
		
		System.out.println(s0 == s1);		// true
		System.out.println(s0 == s2);		// true
	}
	
	private static void test02(){
		String s0 = "kvill";
		String s1 = new String("kvill");
		String s2 = "kv" + new String("ill");
		
		System.out.println(s0 == s1);		// false
		System.out.println(s0 == s2);		// false
		System.out.println(s1 == s2);		// false
	}
	
	private static void test03(){
		String s0 = "kvill";
		String s1 = new String("kvill");
		String s2 = new String("kvill");
		
		System.out.println(s0 == s1);				// false
		
		s1.intern();
		s2 = s2.intern();
		System.out.println(s0 == s1);				// false
		System.out.println(s0 == s1.intern());		// true
		System.out.println(s0 == s2);				// true
	}	
	private static void test04(){
		String s1 = new String("kvill");
		String s2 = s1.intern();
		String s3 = "kvill";
		
		System.out.println(s1 == s1.intern());		// false
		System.out.println(s1 + " " + s2);			// kvill kvill
		System.out.println(s2 == s1.intern());		// true
		System.out.println(s2 == s3);				// true
	}	
	private static void test05(){
		String s0 = "kvill";
		String s1 = new String("kvill");
		String s2 = s1;
		
		System.out.println(s0.equals(s1));		// true
		System.out.println(s0 == s1);			// false
		System.out.println(s2.equals(s1));		// true
		System.out.println(s2 == s1);			// true
	}
	private static void test06(){
		String str = "kv" + "ill" + " " + "ans";
		StringBuffer strBuf = new StringBuffer();
		strBuf.append("kv").append("ill").append(" ").append("ans");
		
		System.out.println(str + " : " + strBuf);
	}	
	public static void main(String args[]){	
		test01();
		test02();
		test03();
		test04();
		test05();
		test06();		
	}	
}</span>




  • 对象的深复制与浅复制 实现Cloneable接口实现深复制 序列化实现深复制
  • http://www.itzhai.com/java-based-notebook-the-object-of-deep-and-shallow-copy-copy-copy-implement-the-cloneable-interface-serializing-deep-deep-copy.html#Object的clone方法的说明: 
    一篇好文
  • Java如何复制对象
    http://blog.csdn.net/tounaobun/article/details/8491392 
    原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法。
    首先,如果此对象的类不能实现接口 Cloneable,则会抛出 CloneNotSupportedException。故要覆盖clone方法必须实现Cloneable接口。
    标识接口,没有定义任何的方法,如Cloneable和Serializable接口。
  • 一些不靠谱的java.util.List深复制方法
    http://will-turner.iteye.com/blog/1478194 
  • 如何DEEP COPY(深拷貝) ARRAYLIST
    http://www.ntex.tw/wordpress/538.html 
  • 精简深拷贝ArrayList实例
    http://gghhgame51333.blog.51cto.com/138362/289383 
  • Java序列化和克隆
    http://developer.51cto.com/art/201103/247236.htm

你可能感兴趣的:(深拷贝,浅拷贝)