Map的putAll方法踩坑实记(对象深拷贝浅拷贝)

导读:本篇文章讲解 Map的putAll方法踩坑实记(对象深拷贝浅拷贝),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

问题描述

在一个产品管理系统中,产品信息需要封装一份同步业务订单系统,封装同步信息的时候需要对产品信息做一些修改,同步完信息再将产品信息进行入库等操作。开发中就是使用的Map对象封装信息,但是总是发现入库信息和创建信息不一致的情况。

操作步骤伪代码如下:

//创建产品信息
createProdInfo();
//执行订单同步信息封装
packageProdInfo();
//执行订单同步服务操作
synProdInfo();
//上面操作都成功了,执行入库操作
insertProdInfo();

编写测试代码模拟问题场景

场景1:Map中不包含对象

使用Map封装产品信息,产品中value都是字符串类型,并没有其他Object对象的情况下

    @Test
    public void putAllTest1() throws InterruptedException {
        String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
        Map<String, Object> prodInfo = new HashMap<>();//产品
        prodInfo.put("prodId", "1");//产品ID
        prodInfo.put("prodName", "AI产品");//产品名称
        prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
        prodInfo.put("isMain", "1");//是否是主产品
        prodInfo.put("crtTime", crtTime);//创建时间

        System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));

        Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
        TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
        String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        synProdInfo.putAll(prodInfo);
        synProdInfo.put("systemId", "order");//同步系统ID
        synProdInfo.put("systemName", "订单系统");//同步系统名称
        synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间

        System.out.println("同步信息内容:" + JSON.toJSONString(synProdInfo));
        System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
    }

测试结果:

原产品信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:45:59","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}
同步信息内容:{"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:46:02","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:45:59","prodId":"1","prodDesc":"这是个AI产品,智能连接未来"}

日志解释1
上面这种情况是符合要求的情况,同步信息crtTime的修改并没有影响产品入库时间。但是出问题的场景是比这种更为复杂的情况,就是产品下面带有子产品信息的情况。

场景2:Map中包含对象

使用Map封装产品信息,产品中包含Map对象的的情况下,测试代码如下:

    @Test
    public void putAllTest2() throws InterruptedException {
        String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
        Map<String, Object> prodInfo = new HashMap<>();//产品
        prodInfo.put("prodId", "1");//产品ID
        prodInfo.put("prodName", "AI产品");//产品名称
        prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
        prodInfo.put("isMain", "1");//是否是主产品,1是0不是
        prodInfo.put("crtTime", crtTime);//创建时间

        Map<String, Object> childProdInfo = new HashMap<>();
        childProdInfo.put("prodId", "2");//产品ID
        childProdInfo.put("prodName", "5G产品");//产品名称
        childProdInfo.put("prodDesc", "这是个5G产品");//产品描述
        childProdInfo.put("isMain", "0");//是否是主产品,1是0不是
        childProdInfo.put("crtTime", crtTime);//创建时间
        prodInfo.put("childProdInfo", childProdInfo);//子产品信息

        System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));

        Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
        TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
        String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        synProdInfo.putAll(prodInfo);
        synProdInfo.put("systemId", "order");//同步系统ID
        synProdInfo.put("systemName", "订单系统");//同步系统名称
        synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
        Map<String,Object> synChildProdInfo = (Map<String, Object>) synProdInfo.get("childProdInfo");//获取子产品信息
        synChildProdInfo.put("systemId", "order");//同步系统ID
        synChildProdInfo.put("systemName", "订单系统");//同步系统名称
        synChildProdInfo.put("crtTime", synCrtTime);//同步订单同步时间

        System.out.println("原同步信息内容:" + JSON.toJSONString(synProdInfo));
        System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
    }

日志输出如下:

原产品信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:07","prodId":"1","childProdInfo":{"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:07","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
原同步信息内容:{"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:10","prodId":"1","childProdInfo":{"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:10","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 21:57:07","prodId":"1","childProdInfo":{"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 21:57:10","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}

这里发现主产品的创建时间和入库的时候一致但是子产品的创建时间和其入库时间不一致了,但是和同步创建时间一致了。
日志解释2
这说明,同步信息封装的时候修改了子产品的信息,这并不是我们想要的结果。这里其实就涉及到对象的浅拷贝和深拷贝的问题。

什么是对象的浅拷贝深拷贝

简单来说就是:
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

如何实现深拷贝

序列化的方式可以实现对象的深拷贝,但是对象必须是实现了Serializable接口才可以,Map本身没有实现 Serializable 这个接口,不能实现深拷贝,但是HashMap实现了Serializable,可以进行深拷贝。
首先,附上深拷贝的方法

    /**
     * 使用对象的序列化进而实现深拷贝
     * @param obj
     * @param <T>
     * @return
     */
    private <T extends Serializable> T clone(T obj) {
        T cloneObj = null;
        try {
            ByteOutputStream bos = new ByteOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.close();
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }

其次,对我们的代码稍作修改

    @Test
    public void putAllTest3() throws InterruptedException {
        String crtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());//产品创建时间
        HashMap<String, Object> prodInfo = new HashMap<>();//产品,此处修改为HashMap
        prodInfo.put("prodId", "1");//产品ID
        prodInfo.put("prodName", "AI产品");//产品名称
        prodInfo.put("prodDesc", "这是个AI产品,智能连接未来");//产品描述
        prodInfo.put("isMain", "1");//是否是主产品,1是0不是
        prodInfo.put("crtTime", crtTime);//创建时间

        Map<String, Object> childProdInfo = new HashMap<>();
        childProdInfo.put("prodId", "2");//产品ID
        childProdInfo.put("prodName", "5G产品");//产品名称
        childProdInfo.put("prodDesc", "这是个5G产品");//产品描述
        childProdInfo.put("isMain", "0");//是否是主产品,1是0不是
        childProdInfo.put("crtTime", crtTime);//创建时间
        prodInfo.put("childProdInfo", childProdInfo);//子产品信息

        System.out.println("原产品信息:" + JSON.toJSONString(prodInfo));

        Map<String, Object> synProdInfo = new HashMap<>();//同步产品信息
        TimeUnit.SECONDS.sleep(3);//模拟同步产品信息耗时3秒
        String synCrtTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        synProdInfo = clone(prodInfo);//此处使用深拷贝赋值
        synProdInfo.put("systemId", "order");//同步系统ID
        synProdInfo.put("systemName", "订单系统");//同步系统名称
        synProdInfo.put("crtTime", synCrtTime);//同步订单同步时间
        Map<String, Object> synChildProdInfo = (Map<String, Object>) synProdInfo.get("childProdInfo");//获取子产品信息
        synChildProdInfo.put("systemId", "order");//同步系统ID
        synChildProdInfo.put("systemName", "订单系统");//同步系统名称
        synChildProdInfo.put("crtTime", synCrtTime);//同步订单同步时间

        System.out.println("原同步信息内容:" + JSON.toJSONString(synProdInfo));
        System.out.println("产品入库信息:" + JSON.toJSONString(prodInfo));
    }

测试结果如下:

原产品信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:26","prodId":"1","childProdInfo":{"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:26","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
原同步信息内容:{"systemId":"order","systemName":"订单系统","isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:29","prodId":"1","childProdInfo":{"systemId":"order","systemName":"订单系统","isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:29","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}
产品入库信息:{"isMain":"1","prodName":"AI产品","crtTime":"2020-04-18 22:53:26","prodId":"1","childProdInfo":{"isMain":"0","prodName":"5G产品","crtTime":"2020-04-18 22:53:26","prodId":"2","prodDesc":"这是个5G产品"},"prodDesc":"这是个AI产品,智能连接未来"}

这里的产品创建时间、子产品创建时间都和入库时间一致了
日志解释3
至此,解决了由于putAll引起的问题,这个在使用的时候需要注意。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72723.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!