zoukankan      html  css  js  c++  java
  • 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方法也可以实现浅拷贝。

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

  • 相关阅读:
    hdu1069Monkey and Banana(动态规划)
    hdu2571 命运(动态规划)
    hdu1505City Game(动态规划)
    在jvm底层有关于方法区的介绍
    用IDEA查看源码总是跳到.class文件而不是.java文件的解决办法
    如果Son类继承Father类,Father类继承GrandFather类,那么new Son()创建对象的时候是否会执行GrandFather类里面的方法
    看面试题有感:子类构造器(无参或有参)继承的super()方法在何时调用,与静态代码块,普通代码块相比的执行顺序如何的思考及证明
    关于子类构造器调用super()方法的规定
    为什么重写了equals方法后还需要重写hashCode方法
    字符串常量池处在JVM的堆中,那么是在堆的哪个部分呢
  • 原文地址:https://www.cnblogs.com/id-tangrenhui/p/11765098.html
Copyright © 2011-2022 走看看