A small pit of Guava Lists.transform

    Recently, I encountered a problem when modifying the bug in the project. I need to modify the value in a list, but no matter how the value is set, the final serialized result is the original value. I was puzzled, and finally clicked on the code that returned the list, and saw that Guava's Lists.transform was used for type conversion, and then I suddenly realized. Because I heard that Guava's Lists.transform method has a pit before, so I took the opportunity to study the source code.

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);
}

The transform method in the source code creates TransformingRandomAccessList or TransformingSequentialList, and passes in the original list and transform function. Both of these classes are subclasses of AbstractList and override several key methods:

@Override
 public T get( int index) {
   return function .apply( fromList .get(index));//Get the element corresponding to the original list and call function conversion
}

@Override
 public Iterator< T > iterator() {
   return listIterator();//Calling the listIterator of the parent class AbstractList is equivalent to calling 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);    
    }
  };
}

It can be seen that the get method first takes elements from the original list, and then calls the incoming function conversion to return the result. The iterator method returns the TransformedListIterator, which implements the Iterator interface, the most important of which is the next method:

@Override
public final T next() {
  return transform(backingIterator.next());
}

First get the next value of the iterator of the original list, then call transform, and use the incoming function to convert the type.

Therefore, the List created by Lists.transform will perform the transformation only when the element is actually acquired, that is, lazy loading, and the transformation is re-performed each time it is fetched, that is, a new object is acquired. That's why changes to the transform result won't take effect.

To solve this problem, just use lambda

List<Integer> src = new ArrayList<>();
List<String> to = src.stream().map(e -> e.toString()).collect(Collectors.toList());

If you have to use Guava, you need to copy the result of the transform once and convert it into a normal List:

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325569057&siteId=291194637