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。
附测试代码:

  1. package com.google.common.base;
  2. import com.google.common.collect.Lists;
  3. import org.junit.Test;
  4. import java.util.List;
  5. /**
  6. * @author mnmlist@163.com
  7. * @date 2016/12/23
  8. * @time 19:31
  9. */
  10. public class ListsTransformTest {
  11. public PersonVo personDbToVo(PersonDb personDb) {
  12. Preconditions.checkNotNull(personDb, "[PersonDbToVo]personDb为null");
  13. PersonVo personVo = new PersonVo();
  14. personVo.setName(personDb.getName() + ",from Db");
  15. personVo.setAge(personDb.getAge());
  16. personVo.setMsg(personDb.getMsg());
  17. return personVo;
  18. }
  19. @Test
  20. public void testListsTransform() {
  21. List<PersonDb> personDbs = Lists.newArrayList(new PersonDb("zhangsan", 20),
  22. new PersonDb("lisi", 24), new PersonDb("wangwu", 30));
  23. List<PersonVo> personVos = Lists.transform(personDbs, new Function<PersonDb, PersonVo>() {
  24. @Override
  25. public PersonVo apply(PersonDb personDb) {
  26. return personDbToVo(personDb);
  27. }
  28. });
  29. for(PersonDb personDb : personDbs) {
  30. personDb.setMsg("hello world!");
  31. }
  32. //Collections.shuffle(personVos);
  33. //personVos = ImmutableList.copyOf(personVos);
  34. //personVos = Lists.newArrayList(personVos);
  35. for(PersonVo personVo : personVos) {
  36. personVo.setMsg("Merry Christmas!");
  37. }
  38. personVos.add(personDbToVo(new PersonDb("sting", 30)));
  39. System.out.println(personVos);
  40. }
  41. }
  42. class PersonDb {
  43. private String name;
  44. private int age;
  45. private String msg;
  46. public PersonDb(String name, int age){
  47. this.name = name;
  48. this.age = age;
  49. }
  50. public String getName() {
  51. return name;
  52. }
  53. public void setName(String name) {
  54. this.name = name;
  55. }
  56. public int getAge() {
  57. return age;
  58. }
  59. public void setAge(int age) {
  60. this.age = age;
  61. }
  62. public String getMsg() {
  63. return msg;
  64. }
  65. public void setMsg(String msg) {
  66. this.msg = msg;
  67. }
  68. @Override
  69. public String toString() {
  70. return MoreObjects.toStringHelper(this)
  71. .add("name", name)
  72. .add("age", age)
  73. .add("msg", msg).toString();
  74. }
  75. }
  76. class PersonVo {
  77. private String name;
  78. private int age;
  79. private String msg;
  80. public String getName() {
  81. return name;
  82. }
  83. public void setName(String name) {
  84. this.name = name;
  85. }
  86. public int getAge() {
  87. return age;
  88. }
  89. public void setAge(int age) {
  90. this.age = age;
  91. }
  92. public String getMsg() {
  93. return msg;
  94. }
  95. public void setMsg(String msg) {
  96. this.msg = msg;
  97. }
  98. @Override
  99. public String toString() {
  100. return MoreObjects.toStringHelper(this)
  101. .add("name", name)
  102. .add("age", age)
  103. .add("msg", msg).toString();
  104. }
  105. }

2.源码解读和异常分析

带着上面的三个问题去查看源码

  1. /**
  2. * Returns a list that applies {@code function} to each element of {@code
  3. * fromList}. The returned list is a transformed view of {@code fromList};
  4. * changes to {@code fromList} will be reflected in the returned list and vice
  5. * versa.
  6. *
  7. * <p>Since functions are not reversible, the transform is one-way and new
  8. * items cannot be stored in the returned list. The {@code add},
  9. * {@code addAll} and {@code set} methods are unsupported in the returned
  10. * list.
  11. *
  12. * <p>The function is applied lazily, invoked when needed. This is necessary
  13. * for the returned list to be a view, but it means that the function will be
  14. * applied many times for bulk operations like {@link List#contains} and
  15. * {@link List#hashCode}. For this to perform well, {@code function} should be
  16. * fast. To avoid lazy evaluation when the returned list doesn't need to be a
  17. * view, copy the returned list into a new list of your choosing.
  18. *
  19. * <p>If {@code fromList} implements {@link RandomAccess}, so will the
  20. * returned list. The returned list is threadsafe if the supplied list and
  21. * function are.
  22. *
  23. * <p>If only a {@code Collection} or {@code Iterable} input is available, use
  24. * {@link Collections2#transform} or {@link Iterables#transform}.
  25. *
  26. * <p><b>Note:</b> serializing the returned list is implemented by serializing
  27. * {@code fromList}, its contents, and {@code function} -- <i>not</i> by
  28. * serializing the transformed values. This can lead to surprising behavior,
  29. * so serializing the returned list is <b>not recommended</b>. Instead,
  30. * copy the list using {@link ImmutableList#copyOf(Collection)} (for example),
  31. * then serialize the copy. Other methods similar to this do not implement
  32. * serialization at all for this reason.
  33. */
  34. @CheckReturnValue
  35. public static <F, T> List<T> transform(
  36. List<F> fromList, Function<? super F, ? extends T> function) {
  37. return (fromList instanceof RandomAccess)
  38. ? new TransformingRandomAccessList<F, T>(fromList, function)
  39. : new TransformingSequentialList<F, T>(fromList, function);
  40. }
  41. private static class TransformingRandomAccessList<F, T> extends AbstractList<T>
  42. implements RandomAccess, Serializable {
  43. final List<F> fromList;
  44. final Function<? super F, ? extends T> function;
  45. TransformingRandomAccessList(List<F> fromList, Function<? super F, ? extends T> function) {
  46. this.fromList = checkNotNull(fromList);
  47. this.function = checkNotNull(function);
  48. }
  49. @Override
  50. public void clear() {
  51. fromList.clear();
  52. }
  53. @Override
  54. public T get(int index) {
  55. return function.apply(fromList.get(index));
  56. }
  57. @Override
  58. public Iterator<T> iterator() {
  59. return listIterator();
  60. }
  61. @Override
  62. public ListIterator<T> listIterator(int index) {
  63. return new TransformedListIterator<F, T>(fromList.listIterator(index)) {
  64. @Override
  65. T transform(F from) {
  66. return function.apply(from);
  67. }
  68. };
  69. }
  70. @Override
  71. public boolean isEmpty() {
  72. return fromList.isEmpty();
  73. }
  74. @Override
  75. public T remove(int index) {
  76. return function.apply(fromList.remove(index));
  77. }
  78. @Override
  79. public int size() {
  80. return fromList.size();
  81. }
  82. private static final long serialVersionUID = 0;
  83. }

源码的解释很清楚,Lists.transform返回的是一个新的类TransformingRandomAccessList,该类有两个变量

  1. final List<F> fromList;
  2. final Function<? super F, ? extends T> function;

也就是Lists.transform保存的只是原有的列表和向新列表转化的Function,每次遍历就重新计算一次。

  1. @Override
  2. public T get(int index) {
  3. return function.apply(fromList.get(index));
  4. }

返回的列表是原有列表的一个转换视图,对原有集合的修改当然会反映到新集合中,这可以解释上述异常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。

  1. public PersonVo personDbToVo(PersonDb personDb) {
  2. Preconditions.checkNotNull(personDb, "[PersonDbToVo]personDb为null");
  3. PersonVo personVo = new PersonVo();
  4. personVo.setName(personDb.getName() + ",from Db");
  5. personVo.setAge(personDb.getAge());
  6. personVo.setMsg(personDb.getMsg());
  7. return personVo;
  8. }

3.问题避免

a.刚开始看Guava代码觉着Lists.transform是个好方法,很强大,但在使用的过程中发现其坑也是挺多的,不注意的话可能会
出现很严重的bug。所以考虑在只有在很必要的情况下才考虑用Lists.transform,即使用Lists.transform可以极大地减少代码量并
使得程序更清晰易懂。在使用复杂的开源类库前还是很有必要仔细阅读下源码的,在不清楚知道自己在干什么的时候最好还是
用成熟的解决方案去解决遇到的问题。
b.如果非要使用Lists.transform方法来实现集合转换,最好对returnList进行下后处理,如使用ImmutableList.copyOf和Lists.newArrayList
对返回结果进行下加工,这样就不用担心不可以对returnList结果进行必要修改了。但如果真的对returnList做上述处理,是否还真的有必要
调用Lists.transform?直接循环遍历过程中生成新的resultList是不是更好呢。

  1. //personVos = ImmutableList.copyOf(personVos);
  2. //personVos = Lists.newArrayList(personVos);//我认为直接循环遍历、转换生成resultList在时间和空间复杂度上会更好。
 

Guava Lists.transform踩坑小记<转>的更多相关文章

  1. 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 ...

  2. guava Lists.transform使用

    作用:将一个List中的实体类转化为另一个List中的实体类. 稍微方便一点.例如:将List<Student>转化为List<StudentVo> Student: pack ...

  3. MySql 踩坑小记

    MySql 踩坑一时爽,一直踩啊一直爽...   以下记录刚踩的三个坑,emmm... 首先是远程机子上创建表错误(踩第一个坑),于是将本地机器 MySql 版本回退至和远程一致(踩第二个坑),最后在 ...

  4. dubbo 2.7应用级服务发现踩坑小记

    本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 背景 本文记录最近一位读者反馈的dubbo 2.7.x中应用级服务发现的问题,关于dubbo应 ...

  5. async语法升级踩坑小记

    从今年过完年回来,三月份开始,就一直在做重构相关的事情. 就在今天刚刚上线了最新一次的重构代码,希望高峰期安好,接近半年的Node.js代码重构. 包含从callback+async.waterfal ...

  6. 支付宝使用流程和踩坑小记(附Demo)

    # 支付宝使用整理 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym, ...

  7. HTTP访问控制(CORS)踩坑小记

    前几天在帮后端排查一个cors的问题的时候发现的一些小坑特此记录 ** cors的本质是出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求. 例如,XMLHttpRequest和FetchAPI遵 ...

  8. 修改ranger ui的admin用户登录密码踩坑小记

    修改的ranger ui的admin用户登录密码时,需要在ranger的配置里把admin_password改成一样的,否则hdfs的namenode在使用admin时启动不起来,异常如下: Trac ...

  9. Jquery AJAX使用踩坑小记

    在使用jquery ajax时,如果其参数是一个json对象,将此参数使用$('#dd').data(param)绑定到一个元素上, 在使用$('#dd').bind('click',function ...

随机推荐

  1. STL中实现 iterator trail 的编程技巧

    STL中实现 iterator trail 的编程技巧 <泛型编程和 STL>笔记及思考. 这篇文章主要记录在 STL 中迭代器设计过程中出现的编程技巧,围绕的 STL 主题为 (迭代器特 ...

  2. iOS 11开发教程(二十)iOS11应用视图美化按钮之设置按钮的状态

    iOS 11开发教程(二十)iOS11应用视图美化按钮之设置按钮的状态 在示例2-2中,设置按钮的标题和颜色时,需要对按钮的状态进行设置,表示按钮在某一状态下的标题和标题颜色是什么样子.例如,UICo ...

  3. iOS 11开发教程(十九)iOS11应用视图美化按钮之设置按钮的外观

    iOS 11开发教程(十九)iOS11应用视图美化按钮之设置按钮的外观 美化按钮说白了就是对按钮的属性进行设置,设置按钮的属性有两种方法:一种是使用编辑界面中的属性检查器:另一种是使用代码进行设置.以 ...

  4. JS-排序详解-快速排序

    说明 时间复杂度指的是一个算法执行所耗费的时间 空间复杂度指运行完一个程序所需内存的大小 稳定指,如果a=b,a在b的前面,排序后a仍然在b的前面 不稳定指,如果a=b,a在b的前面,排序后可能会交换 ...

  5. AngularJS checkbox/复选框 根据缓存数据实时显示

    想缓存选择按钮时,可以使用这样的方法. index.html <!DOCTYPE html> <html data-ng-app="App"> <he ...

  6. u3d 鼠标点击位置,物体移动过去。 U3d mouse clicks position, objects move past.

    u3d 鼠标点击位置,物体移动过去. U3d mouse clicks position, objects move past. 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱: ...

  7. JDBC(6)—BeanUtils

    1.简介: BeanUtils工具是一种方便我们对JavaBean进行操作的工具,是Apache组织下的产品 2.Java 类的属性: 1>.在JavaEE中java类的属性通过getter和s ...

  8. Revit API创建房间

    start [Transaction(TransactionMode.Manual)] [Regeneration(RegenerationOption.Manual)] );             ...

  9. android:四种基本布局

    一个丰富的界面总是要由很多个控件组成的,那我们如何才能让各个控件都有条不紊地 摆放在界面上,而不是乱糟糟的呢?这就需要借助布局来实现了.布局是一种可用于放置很 多控件的容器,它可以按照一定的规律调整内 ...

  10. Java调用使用SSL/HTTPS协议来传输的axis webservice服务

    使用SSL/HTTPS协议来传输 Web服务也可以使用SSL作为传输协议.虽然JAX-RPC并没有强制规定是否使用SSL协议,但在tomcat 下使用HTTPS协议. 1.使用JDK自带的工具创建密匙 ...