Guava Lists.transform踩坑小記<轉>

1.問題提出

1.前段時間在項目中用到Lists.transform返回的List,在對該list修改後發現修改並無反映在結果裏,研究源碼後發現問題還挺大。
下面經過單步調試的結果來查看Guava Lists.transform使用過程當中須要注意的地方。

a.對原有的list列表修改會影響Lists.transform已經生成列表java

由上圖能夠看出,對原數據集personDbs的修改會直接影響到Lists.transform方法返回的結果personVos,
這是很危險的,若是在使用的過程當中不注意的話會形成很嚴重的問題,而這種問題又是很隱蔽的,在項目中
無疑是個不定時的炸彈。
b.對Lists.transform生成的列表的元素進行修改可能沒法生效app

由上面的調試結果能夠看出對Lists.transform返回的List列表中的元素的修改不會"生效",即修改不會反映在list列表中。
c.對returnList調用add、addAll和shuffle等修改returnList的方法會拋異常
對personVos調用Collections.shuffle(personVos);或personVos.add(personDbToVo(new PersonDb("sting", 30)));
都會拋出java.lang.UnsupportedOperationException。
附測試代碼:dom

 

[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,該類有兩個變量ide

 

[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在時間和空間複雜度上會更好。  

 

相關文章
相關標籤/搜索