java 浅拷贝 深拷贝

一,问题背景

最近遇到一块代码,看了半天没有看明白如何实现树形结构的。debugger后发现原理,其实是利用了java对象是引用类型,利用浅拷贝来实现树型结构。

    /**
     * 
     * @param table "树型结构中的所有节点"
     * @param childrenField "固定key,名称为children"
     * @param idField "每个节点id"
     * @param parentIdField "子节点与父节点的关系属性parentId"
     * @return
     */
    public static ArrayList list2Tree(List table, String childrenField, String idField, String parentIdField)
    {
        ArrayList tree = new ArrayList();

        Map hash = new HashMap();//装载所有对象 id,object格式
        for (int i = 0, l = table.size(); i < l; i++)
        {
            Map t = (Map)table.get(i);
            hash.put(t.get(idField), t);
        }
  
        for (int i = 0, l = table.size(); i < l; i++)
        {
            Map t = (Map)table.get(i);
            Object parentID = t.get(parentIdField);
            if (parentID == null || parentID.toString().equals("-1"))//父元素   
            {
                tree.add(t);//向树型结构里放入父节点
                continue;
            }
            Map parent = (Map)hash.get(parentID);//子元素
      //这边修改引用类型的map,修改其属性值会改变,tree里面的父节点会相应发生变化
            if (parent == null)    
            {
                tree.add(t);
                continue;
            }
            List children = (List)parent.get(childrenField);
            if (children == null)
            {
                children = new ArrayList();
                parent.put(childrenField, children);
            }
            children.add(t);
        }
        
        
        return tree;
    }  

针对发现的问题,这边自己查阅资料复习一下浅拷贝深拷贝。

二,浅拷贝

  浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

示例代码:

public class test1Main {
    
    public static void main (String[] args){
        Son son = new Son(66);
        Person p1 = new Person("tony",son);
        Person p2 = new Person(p1);
        p1.setSonName("bella");
        p1.getSon().setAge(88);//s是对象是引用,改变s所有引用s的值都会改变
        System.out.println("p1==="+p1);  //        p1===Person [sonName=bella, son=Son [age=88]]
        System.out.println("p2==="+p2);  //        p2===Person [sonName=tony, son=Son [age=88]]

    }
}

 

三,深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存(会另外开辟一个内存空间)。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。当我们需要new两个完全不一样的对象时,需要序列化来实现深拷贝。

示例代码:

 

public class Test2Main{
    
    public static void main(String[] args) {
        Son son = new Son(66);
        Person p1 = new Person("小汤", son);
        //系列化实现深拷贝
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(p1);
            oos.flush();
            oos.close();
            bos.close();//这里是假关闭
            //ByteArrayOutputStream/ByteArrayInputStream流是以一个字节数组来操作。
            //创建输入流的时候,要先提供一个输入源byte[],之后读取实际上就是读取这个byte[]的内容;
            //而输出流则是将数据写入一个内置的数组,一般磁盘和网络流写完之后就拿不到写完的内容,
            //但由于这个是写到了一个数组中,所以还是可以获取数据的
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            Person p2 = (Person) ois.readObject();
            p1.setSonName("小汤原始");
            p1.getSon().setAge(44);
            System.out.println("p1===="+p1); //p1====Person [sonName=小汤原始, son=Son [age=44]]
            
            System.out.println("p2===="+p2); //p2====Person [sonName=小汤, son=Son [age=66]],完全一个新的对象
            p2.getSon().setAge(33);
            System.out.println("p2-1===="+p2); // 修改一个新的对象的值    p2-1====Person [sonName=小汤, son=Son [age=33]]
            System.out.println("p1==="+p1); //修改新的对象的值并没有改变 p1, p1===Person [sonName=小汤原始, son=Son [age=44]]
            System.out.println("p1.hashCode()===="+ p1.hashCode()); //p1.hashCode()====1118140819
            System.out.println("p2.hashCode()===="+ p2.hashCode()); //p2.hashCode()====1956725890
            //hashCode 不同 证明两个对象的内存地址完全不一样 
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

 可复用的深拷贝工具类:

public class SerializedClone {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) {
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

 

浅拷贝深拷贝示例代码person和son类的定义:

 

public class Person implements Serializable  {
    
    private String sonName;
    private Son son;
    
    public Person(String sonName,Son son) {
        this.sonName = sonName;
        this.son = son;
    }
    
    public Person(Person person) {
        this.sonName = person.sonName;
        this.son = person.son;
    }

    public String getSonName() {
        return sonName;
    }

    public void setSonName(String sonName) {
        this.sonName = sonName;
    }

    public Son getSon() {
        return son;
    }

    public void setSon(Son son) {
        this.son = son;
    }

    @Override
    public String toString() {
        return "Person [sonName=" + sonName + ", son=" + son + "]";
    }
    
}

 

public class Son implements Serializable{

    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Son(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Son [age=" + age + "]";
    }
    
    
}

如果person,son实现Cloneable接口,使用clone方法也可以实现浅拷贝。

 

当写代码时,对象属性有引用类型的时候,操作该对象需要注意浅拷贝深拷贝的区别!

posted @ 2019-10-31 11:27  ID_小汤  阅读(296)  评论(0编辑  收藏  举报