深拷贝vs浅拷贝

来源:互联网 发布:淘宝的电脑主机靠谱吗 编辑:程序博客网 时间:2024/05/16 18:09


  • 浅拷贝(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指向内存地址AchangeStr(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),反过来也成立,也就是拷贝对象和原对象完全脱离关系。

通过实现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);// trueSystem.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);// falseSystem.out.println(s0 == s2);// falseSystem.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);// falses1.intern();s2 = s2.intern();System.out.println(s0 == s1);// falseSystem.out.println(s0 == s1.intern());// trueSystem.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());// falseSystem.out.println(s1 + " " + s2);// kvill kvillSystem.out.println(s2 == s1.intern());// trueSystem.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));// trueSystem.out.println(s0 == s1);// falseSystem.out.println(s2.equals(s1));// trueSystem.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

1 0
原创粉丝点击