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