关于List.addAll(Collection<E>)方法遇到的问题

来源:互联网 发布:数据采集网络兼职 编辑:程序博客网 时间:2024/04/26 13:04

涉及的软件环境:SpringMVC,fastjson

有一个需求,需要将热词信息跟自定义的热词信息(另外一个实体的信息)合并到一个列表里,

获取hotKeyList的代码

List<HotKeys> hotKeyList = hotKeysDao.getAllHotKeys();

获取自定义热词的代码:

 List<AdInfoExt> comprehensivePageAds = adInfoDao.getAdInfoListByRuleCode("comprehensive_search_page", "ad");        List<HotKeys> cpaKeys = new ArrayList<HotKeys>();        for(AdInfoExt ad : comprehensivePageAds){            HotKeys key = new HotKeys();            key.setHotKey(ad.getAdTittle());            key.setStats(1);            key.setUrl(ad.getUrl());            cpaKeys.add(key);        }

显然最终都是封装成List<HotKeys>了,然后将hotKeyListcpaKeys合并到一起用了:

cpaKeys.addAll(hotKeyList);

或者:

        for(HotKeys k : hotKeyList){            cpaKeys.add(k);        }

都产生了下面的问题:输出到客户端的时候,cpaKeys是正常的,

"cpaKey": [    {      "k": "杨振宁遗产分配",      "sta": 0,      "u": "https://wap.sogou.com/web/searchList.jsp?keyword=%E6%9D%A8%E6%8C%AF%E5%AE%81%E9%81%97%E4%BA%A7%E5%88%86%E9%85%8D&pid=sogou-mobp-eeea8c180c5dff16&v=5"    },    {      "k": "泉州6车追尾",      "sta": 0,      "u": "https://wap.sogou.com/web/searchList.jsp?keyword=%E6%B3%89%E5%B7%9E6%E8%BD%A6%E8%BF%BD%E5%B0%BE&pid=sogou-mobp-eeea8c180c5dff16&v=5"    },    {      "k": "翻车致19人遇难",      "sta": 1,      "u": "https://wap.sogou.com/web/searchList.jsp?keyword=%E7%BF%BB%E8%BD%A6%E8%87%B419%E4%BA%BA%E9%81%87%E9%9A%BE&pid=sogou-mobp-eeea8c180c5dff16&v=5"    }]

而原来的hotKeyList的数据就变成了以下的模样(它正确的样子应该跟上面的cpaKey一样):

 "hotKey": [    {      "$ref": "$.cpaKey[0]"    },    {      "$ref": "$.cpaKey[1]"    },    {      "$ref": "$.cpaKey[2]"    },    {      "$ref": "$.cpaKey[3]"    },    {      "$ref": "$.cpaKey[4]"    },    {      "$ref": "$.cpaKey[5]"    },    {      "$ref": "$.cpaKey[6]"    },    {      "$ref": "$.cpaKey[7]"    } ]

后来将两个list的合并方式改成对象新对象拷贝旧对象信息,将新对象存入列表的方式,就两个都正常了:

        for(HotKeys key : hotKeyList){            HotKeys k = new HotKeys();            BeanUtils.copyProperties(key, k);            cpaKeys.add(k);        }

这是什么问题?对象引用的问题?

从上面的运行结果可以看出是hotKeyList里面的对象引用发生变化,转而指向cpaKeys中对应的数据,经过addAll()方法处理后hotKeyList只保存了每个对象的引用信息(或者说指针)。hotKeyList并没有另外生成一份数据,看看源码:

public boolean addAll(Collection<? extends E> c) {        Object[] a = c.toArray();        int numNew = a.length;        ensureCapacityInternal(size + numNew);  // Increments modCount        System.arraycopy(a, 0, elementData, size, numNew);        size += numNew;        return numNew != 0;    }

其中方法System.arraycopy(a, 0, elementData, size, numNew);的描述如下:

Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array.

If the src and dest arguments refer to the same array object, then the copying is performed as if the components at positions srcPos through srcPos+length-1 were first copied to a temporary array with length components and then the contents of the temporary array were copied into positions destPos through destPos+length-1 of the destination array.

第一段话说到”数组组件的子序列从src引用的源数组复制到dest引用的目标数组。”注意“引用的源数组”、“引用的目标数组”的意思。

而通过SpringMVC的@ResponseBody返回JSON的时候,FastJSON并没有将引用转成数据,为了避免日后还会产生这样的问题,最终的解决方案是将FastJSON换为Jackson。配置文件修改如下:

pom.xml 新增jackson的依赖:

<dependency>    <groupId>com.fasterxml.jackson.core</groupId>    <artifactId>jackson-databind</artifactId>    <version>2.4.2</version></dependency>

spring-mvc.xml修改如下:

<!--避免IE执行AJAX时,返回JSON出现下载文件.原来的(fastjson)class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"-->    <bean id="mappingJacksonHttpMessageConverter"        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">        <property name="supportedMediaTypes">            <list>                <value>text/html;charset=UTF-8</value>                <value>application/json</value>            </list>        </property>        <!-- 使用jackson不需要这个        <property name="features">            <list>                <!// <value>WriteMapNullValue</value> //>                <value>QuoteFieldNames</value>            </list>        </property> -->    </bean>    <!-- 修改新增 -->    <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter">        <property name="supportedMediaTypes">            <list>                <value>text/plain;charset=UTF-8</value>            </list>        </property>    </bean>    <!-- 修改新增 -->    <bean id="formHttpMessageConverter" class="org.springframework.http.converter.FormHttpMessageConverter" />    <!-- 启动SpringMVC的注解功能,完成请求和注解POJO的映射 -->    <bean        class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">        <property name="messageConverters">            <list>                <ref bean="formHttpMessageConverter" />     <!-- 修改新增 -->                <ref bean="stringHttpMessageConverter" />   <!-- 修改新增 -->                <ref bean="mappingJacksonHttpMessageConverter" />   <!-- JSON转换器 -->            </list>        </property>    </bean>

还是使用最简单addAll()方法,结果正常。

这次疏忽导致的影响非常大,虽然问题藏得有点深,但关键还是没有去验证对原来数据影响导致,以此作为一次深刻经验教训。

另外,经过集思广益,总结出以上出现$ref的问题的根本是因为对象中存在重复的对象导致,如果仍然使用fastjson方法的话可以这么处理:

FastJson提供了SerializerFeature.DisableCircularReferenceDetect这个序列化选项,用来关闭引用检测。关闭引用检测后,重复引用对象时就不会被$ref代替,但是在循环引用时也会导致StackOverflowError异常。

用法:

 JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);

得到的是object的json字符串。

原创粉丝点击