Guava Lists.transform的一个小坑

    最近在修改项目中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<Integer> src = new ArrayList<>();
List<String> to = src.stream().map(e -> e.toString()).collect(Collectors.toList());

如果非要用Guava,需要把transform的结果copy一次,转换成普通的List:

List<String> list = Lists.transform(...)
ArrayList<String> target = new ArrayList<>(Arrays.asList(new String[list.size()]));
Collections.copy(target,list);

猜你喜欢

转载自my.oschina.net/u/1013857/blog/1559525