最近在修改项目中bug的时候遇到一个问题,需要修改一个list里的值,但是不管怎么set值,最后序列化的结果都是原来的值。百思不得其解,最后点开返回list的代码,看到里面用了Guava的Lists.transform做了类型转换,才恍然大悟。因为之前听说过Guava的Lists.transform方法有个坑,于是趁机研究下源码。
public static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function) {
return (fromList instanceof RandomAccess) ? new TransformingRandomAccessList<>(fromList, function) : new TransformingSequentialList<>(fromList, function);
}
源码中transform方法创建了TransformingRandomAccessList或者TransformingSequentialList,并将原始list和转换function传入,这两个类都是AbstractList的子类,并重写了几个关键方法:
@Override public T get(int index) { return function.apply(fromList.get(index));//获取原始list对应的元素,调用function转换 }
@Override public Iterator<T> iterator() { return listIterator();//调用父类AbstractList的listIterator,相当于调用listIterator(0) }
@Override public ListIterator<T> listIterator(int index) { return new TransformedListIterator<F, T>(fromList.listIterator(index)) { @Override T transform(F from) { return function.apply(from); } }; }
可以看到get方法是先从原始list里取出元素,然后调用传入的function转换,返回结果。而iterator方法,则是返回了TransformedListIterator,这个类实现了Iterator接口,其中最主要的是next方法:
@Override public final T next() { return transform(backingIterator.next()); }
先获取原始list的iterator的下个值,然后调用transform,使用传入function转换类型。
所以Lists.transform创建的List只有在真正获取元素的时候才会执行转换,也就是延迟加载,而且是每次取都重新执行一次转换也就是获取新的对象。这就是为什么对transform结果的修改并不会起效。
解决这个问题,只需要使用lambda即可
List
如果非要用Guava,需要把transform的结果copy一次,转换成普通的List:
List