Guava Lists.transform踩坑小记<转>
1.问题提出
1.前段时间在项目中用到Lists.transform返回的List,在对该list修改后发现修改并没有反映在结果里,研究源码后发现问题还挺大。
下面通过单步调试的结果来查看Guava Lists.transform使用过程中需要注意的地方。
a.对原有的list列表修改会影响Lists.transform已经生成列表
由上图可以看出,对原数据集personDbs的修改会直接影响到Lists.transform方法返回的结果personVos,
这是很危险的,如果在使用的过程中不注意的话会造成很严重的问题,而这种问题又是很隐蔽的,在项目中
无疑是个不定时的炸弹。
b.对Lists.transform生成的列表的元素进行修改可能无法生效
由上面的调试结果可以看出对Lists.transform返回的List列表中的元素的修改不会"生效",即修改不会反映在list列表中。
c.对returnList调用add、addAll和shuffle等修改returnList的方法会抛异常
对personVos调用Collections.shuffle(personVos);或personVos.add(personDbToVo(new PersonDb("sting", 30)));
都会抛出java.lang.UnsupportedOperationException。
附测试代码:
- package com.google.common.base;
- import com.google.common.collect.Lists;
- import org.junit.Test;
- import java.util.List;
- /**
- * @author mnmlist@163.com
- * @date 2016/12/23
- * @time 19:31
- */
- public class ListsTransformTest {
- public PersonVo personDbToVo(PersonDb personDb) {
- Preconditions.checkNotNull(personDb, "[PersonDbToVo]personDb为null");
- PersonVo personVo = new PersonVo();
- personVo.setName(personDb.getName() + ",from Db");
- personVo.setAge(personDb.getAge());
- personVo.setMsg(personDb.getMsg());
- return personVo;
- }
- @Test
- public void testListsTransform() {
- List<PersonDb> personDbs = Lists.newArrayList(new PersonDb("zhangsan", 20),
- new PersonDb("lisi", 24), new PersonDb("wangwu", 30));
- List<PersonVo> personVos = Lists.transform(personDbs, new Function<PersonDb, PersonVo>() {
- @Override
- public PersonVo apply(PersonDb personDb) {
- return personDbToVo(personDb);
- }
- });
- for(PersonDb personDb : personDbs) {
- personDb.setMsg("hello world!");
- }
- //Collections.shuffle(personVos);
- //personVos = ImmutableList.copyOf(personVos);
- //personVos = Lists.newArrayList(personVos);
- for(PersonVo personVo : personVos) {
- personVo.setMsg("Merry Christmas!");
- }
- personVos.add(personDbToVo(new PersonDb("sting", 30)));
- System.out.println(personVos);
- }
- }
- class PersonDb {
- private String name;
- private int age;
- private String msg;
- public PersonDb(String name, int age){
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getMsg() {
- return msg;
- }
- public void setMsg(String msg) {
- this.msg = msg;
- }
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("name", name)
- .add("age", age)
- .add("msg", msg).toString();
- }
- }
- class PersonVo {
- private String name;
- private int age;
- private String msg;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getMsg() {
- return msg;
- }
- public void setMsg(String msg) {
- this.msg = msg;
- }
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("name", name)
- .add("age", age)
- .add("msg", msg).toString();
- }
- }
2.源码解读和异常分析
带着上面的三个问题去查看源码
- /**
- * Returns a list that applies {@code function} to each element of {@code
- * fromList}. The returned list is a transformed view of {@code fromList};
- * changes to {@code fromList} will be reflected in the returned list and vice
- * versa.
- *
- * <p>Since functions are not reversible, the transform is one-way and new
- * items cannot be stored in the returned list. The {@code add},
- * {@code addAll} and {@code set} methods are unsupported in the returned
- * list.
- *
- * <p>The function is applied lazily, invoked when needed. This is necessary
- * for the returned list to be a view, but it means that the function will be
- * applied many times for bulk operations like {@link List#contains} and
- * {@link List#hashCode}. For this to perform well, {@code function} should be
- * fast. To avoid lazy evaluation when the returned list doesn't need to be a
- * view, copy the returned list into a new list of your choosing.
- *
- * <p>If {@code fromList} implements {@link RandomAccess}, so will the
- * returned list. The returned list is threadsafe if the supplied list and
- * function are.
- *
- * <p>If only a {@code Collection} or {@code Iterable} input is available, use
- * {@link Collections2#transform} or {@link Iterables#transform}.
- *
- * <p><b>Note:</b> serializing the returned list is implemented by serializing
- * {@code fromList}, its contents, and {@code function} -- <i>not</i> by
- * serializing the transformed values. This can lead to surprising behavior,
- * so serializing the returned list is <b>not recommended</b>. Instead,
- * copy the list using {@link ImmutableList#copyOf(Collection)} (for example),
- * then serialize the copy. Other methods similar to this do not implement
- * serialization at all for this reason.
- */
- @CheckReturnValue
- public static <F, T> List<T> transform(
- List<F> fromList, Function<? super F, ? extends T> function) {
- return (fromList instanceof RandomAccess)
- ? new TransformingRandomAccessList<F, T>(fromList, function)
- : new TransformingSequentialList<F, T>(fromList, function);
- }
- private static class TransformingRandomAccessList<F, T> extends AbstractList<T>
- implements RandomAccess, Serializable {
- final List<F> fromList;
- final Function<? super F, ? extends T> function;
- TransformingRandomAccessList(List<F> fromList, Function<? super F, ? extends T> function) {
- this.fromList = checkNotNull(fromList);
- this.function = checkNotNull(function);
- }
- @Override
- public void clear() {
- fromList.clear();
- }
- @Override
- public T get(int index) {
- return function.apply(fromList.get(index));
- }
- @Override
- public Iterator<T> iterator() {
- return listIterator();
- }
- @Override
- public ListIterator<T> listIterator(int index) {
- return new TransformedListIterator<F, T>(fromList.listIterator(index)) {
- @Override
- T transform(F from) {
- return function.apply(from);
- }
- };
- }
- @Override
- public boolean isEmpty() {
- return fromList.isEmpty();
- }
- @Override
- public T remove(int index) {
- return function.apply(fromList.remove(index));
- }
- @Override
- public int size() {
- return fromList.size();
- }
- private static final long serialVersionUID = 0;
- }
源码的解释很清楚,Lists.transform返回的是一个新的类TransformingRandomAccessList,该类有两个变量
- final List<F> fromList;
- final Function<? super F, ? extends T> function;
也就是Lists.transform保存的只是原有的列表和向新列表转化的Function,每次遍历就重新计算一次。
- @Override
- public T get(int index) {
- return function.apply(fromList.get(index));
- }
返回的列表是原有列表的一个转换视图,对原有集合的修改当然会反映到新集合中,这可以解释上述异常a。
由于functions不具有可逆性,transform是单向的,无法向结果列表中添加新元素,因此Lists.transform返回的l
ist不支持add和addAll方法。这可以解释异常c。
The returned list is a transformed view of fromList; changes to fromList will be reflected in the returned list
and vice versa.源码的注释表明对fromList的修改会反映到returnList上,对returnList的修改也会同样影响fromList,
这是不正确的,对returnList的修改不一定样影响fromList,没有必然的联系,这取决于Function对象中的转换方法,如
本测试方法用到的PersonDb向PersonVo转换方法personDbToVo,遍历returnList时每次都会调用personDbToVo,然后每次都会调用
PersonVo personVo = new PersonVo();生成新的对象,所以对结果列表returnList修改只会影响该局部变量personVo,而不会
影响到原来的fromList,这可以解释异常b。
- public PersonVo personDbToVo(PersonDb personDb) {
- Preconditions.checkNotNull(personDb, "[PersonDbToVo]personDb为null");
- PersonVo personVo = new PersonVo();
- personVo.setName(personDb.getName() + ",from Db");
- personVo.setAge(personDb.getAge());
- personVo.setMsg(personDb.getMsg());
- return personVo;
- }
3.问题避免
a.刚开始看Guava代码觉着Lists.transform是个好方法,很强大,但在使用的过程中发现其坑也是挺多的,不注意的话可能会
出现很严重的bug。所以考虑在只有在很必要的情况下才考虑用Lists.transform,即使用Lists.transform可以极大地减少代码量并
使得程序更清晰易懂。在使用复杂的开源类库前还是很有必要仔细阅读下源码的,在不清楚知道自己在干什么的时候最好还是
用成熟的解决方案去解决遇到的问题。
b.如果非要使用Lists.transform方法来实现集合转换,最好对returnList进行下后处理,如使用ImmutableList.copyOf和Lists.newArrayList
对返回结果进行下加工,这样就不用担心不可以对returnList结果进行必要修改了。但如果真的对returnList做上述处理,是否还真的有必要
调用Lists.transform?直接循环遍历过程中生成新的resultList是不是更好呢。
- //personVos = ImmutableList.copyOf(personVos);
- //personVos = Lists.newArrayList(personVos);//我认为直接循环遍历、转换生成resultList在时间和空间复杂度上会更好。
Guava Lists.transform踩坑小记<转>的更多相关文章
- Ubuntu 16.04 安装Mysql 5.7 踩坑小记
title:Ubuntu 16.04 安装Mysql 5.7 踩坑小记 date: 2018.02.03 安装mysql sudo apt-get install mysql-server mysql ...
- guava Lists.transform使用
作用:将一个List中的实体类转化为另一个List中的实体类. 稍微方便一点.例如:将List<Student>转化为List<StudentVo> Student: pack ...
- MySql 踩坑小记
MySql 踩坑一时爽,一直踩啊一直爽... 以下记录刚踩的三个坑,emmm... 首先是远程机子上创建表错误(踩第一个坑),于是将本地机器 MySql 版本回退至和远程一致(踩第二个坑),最后在 ...
- dubbo 2.7应用级服务发现踩坑小记
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 背景 本文记录最近一位读者反馈的dubbo 2.7.x中应用级服务发现的问题,关于dubbo应 ...
- async语法升级踩坑小记
从今年过完年回来,三月份开始,就一直在做重构相关的事情. 就在今天刚刚上线了最新一次的重构代码,希望高峰期安好,接近半年的Node.js代码重构. 包含从callback+async.waterfal ...
- 支付宝使用流程和踩坑小记(附Demo)
# 支付宝使用整理 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym, ...
- HTTP访问控制(CORS)踩坑小记
前几天在帮后端排查一个cors的问题的时候发现的一些小坑特此记录 ** cors的本质是出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求. 例如,XMLHttpRequest和FetchAPI遵 ...
- 修改ranger ui的admin用户登录密码踩坑小记
修改的ranger ui的admin用户登录密码时,需要在ranger的配置里把admin_password改成一样的,否则hdfs的namenode在使用admin时启动不起来,异常如下: Trac ...
- Jquery AJAX使用踩坑小记
在使用jquery ajax时,如果其参数是一个json对象,将此参数使用$('#dd').data(param)绑定到一个元素上, 在使用$('#dd').bind('click',function ...
随机推荐
- window版chrome 57.0.2970.0 (64-bit)滚动条的BUG
有一个元素div,它有一个子元素ul,给div添加一个:hover样式{color: red},挡鼠标hover div时,字体变红. 当鼠标hover div时,ul字体变红,鼠标hover ul的 ...
- C# 组件模组引用第三方组件问题
对接上一文章由于是动态加载指定程序集,会把当前目录下所有dll都加载进来.如果像sqlite这种第三组件调用了由C.C++非.net语言所以生成的Dll.因为自动生成的原因.会把非C#生成的dll都加 ...
- python魔法方法-比较相关
在python2.x的版本中,支持不同类型的比较,其比较原则如下: 内置类型: 1.同一类型: 1.1)数字类型:包含int.float.long.complex.bool(bool类型是int的 ...
- C# 使用PrintDocument 绘制表格 完成 打印预览 DataTable
经过不断的Google与baidu,最终整理出来的打印类 主要是根据两个参考的类组合而成,稍微修改了一下,参考代码及来源见最后(其中一份是VB语言的) 其中遇到的一些问题也已经得到了解决(分页,打印预 ...
- node+express上传图片
注意: 别用multer 上传文件了,太坑了,普通文本获取不到,折腾了半天没有解决,最后采用 multiparty 解决了: <!DOCTYPE html><html> < ...
- 配置Spring的用于初始化容器对象的监听器
<!-- 配置Spring的用于初始化容器对象的监听器 --> <listener> <listener-class>org.springframework.web ...
- windows下配置 GNU的gdb调试功能
1.配置 修改环境变量(前提电脑中存在gdb.exe) 1. 我的电脑->属性->环境......在path那一项后面添加你DEV-C++ Bin目录的路径(gdb.exe所在目录),如: ...
- React系列文章:无状态组件生成真实DOM结点
在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...
- Microsoft.mshtml.dll 添加引用及类型选择错误问题解决办法
在比较早的文章中,提到使用 Microsoft.mshtml.dll 进行模拟浏览器点击的例子. 1.添加引用的问题 一般在开发环境下会在三个地方存有microsoft.mshtml.dll文件.所以 ...
- Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件
一.问题描述 今天使用SDK Manager将Android SDK的版本更新到了Android 5.1的版本,eclipse创建android项目时,预览activity_main.xml文件时提示 ...