zoukankan      html  css  js  c++  java
  • 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。
    附测试代码:

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

    2.源码解读和异常分析

    带着上面的三个问题去查看源码
    [java] view plain copy
    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.   
    42.   
    43. private static class TransformingRandomAccessList<F, T> extends AbstractList<T>  
    44.     implements RandomAccess, Serializable {  
    45.   final List<F> fromList;  
    46.   final Function<? super F, ? extends T> function;  
    47.   
    48.   TransformingRandomAccessList(List<F> fromList, Function<? super F, ? extends T> function) {  
    49.     this.fromList = checkNotNull(fromList);  
    50.     this.function = checkNotNull(function);  
    51.   }  
    52.   
    53.   @Override  
    54.   public void clear() {  
    55.     fromList.clear();  
    56.   }  
    57.   
    58.   @Override  
    59.   public T get(int index) {  
    60.     return function.apply(fromList.get(index));  
    61.   }  
    62.   
    63.   @Override  
    64.   public Iterator<T> iterator() {  
    65.     return listIterator();  
    66.   }  
    67.   
    68.   @Override  
    69.   public ListIterator<T> listIterator(int index) {  
    70.     return new TransformedListIterator<F, T>(fromList.listIterator(index)) {  
    71.       @Override  
    72.       T transform(F from) {  
    73.         return function.apply(from);  
    74.       }  
    75.     };  
    76.   }  
    77.   
    78.   @Override  
    79.   public boolean isEmpty() {  
    80.     return fromList.isEmpty();  
    81.   }  
    82.   
    83.   @Override  
    84.   public T remove(int index) {  
    85.     return function.apply(fromList.remove(index));  
    86.   }  
    87.   
    88.   @Override  
    89.   public int size() {  
    90.     return fromList.size();  
    91.   }  
    92.   
    93.   private static final long serialVersionUID = 0;  
    94. }  

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

    [java] view plain copy
    1. final List<F> fromList;  
    2. final Function<? super F, ? extends T> function;  
    也就是Lists.transform保存的只是原有的列表和向新列表转化的Function,每次遍历就重新计算一次。
    [java] view plain copy
    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。
    [java] view plain copy
    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是不是更好呢。
    [java] view plain copy
    1. //personVos = ImmutableList.copyOf(personVos);  
    2. //personVos = Lists.newArrayList(personVos);//我认为直接循环遍历、转换生成resultList在时间和空间复杂度上会更好。  
     
  • 相关阅读:
    git常用命令
    代码实现-栈、队列、双端队列
    websocket实现简单的单聊
    websocket实现简单的群聊
    成员
    反射
    类与类之间的关系
    常用模块-02
    模块
    微信小程序表单多页面步骤提交
  • 原文地址:https://www.cnblogs.com/winkey4986/p/7498769.html
Copyright © 2011-2022 走看看