I'm looking for a way to create a collection, list, set, or map which contains the transformed elements of an original collection and reflects every modification in that collection.
For example if I have a List<Integer>
from a third party API and another API is expecting a List<String>
. I know I can transform the list like this:
List<Integer> intList = thirdPartyBean.getIntListProperty();
List<String> stringList = intList.stream().map(Integer::toString)
.collect(Collectors.toList());
secondBean.setStringListProperty(stringList);
The problem is, if anything is changed in one of the lists the other one will still reflect the previous state. Let's assume that intList
contains [1, 2, 3]
:
intList.add(4);
stringList.remove(0);
System.out.println(intList.toString()); // will print: [1, 2, 3, 4]
System.out.println(stringList.toString()); // will print: [2, 3]
// Expected result of both toString(): [2, 3, 4]
So I'm searching for something like List.sublist(from, to)
where the result is "backed" by the original list.
I'm thinking of implementing my own list wrapper which is used like this:
List<String> stringList = new MappedList<>(intList, Integer::toString, Integer::valueOf);
The second lambda is for inverting the conversion, to support calls like stringList.add(String)
.
But before I implement it myself I would like to know if I try to reinvent the wheel - maybe there is already a common solution for this problem?
The elements in a collection can be accessed by index (starts at 0). The length Property returns the number of elements in the collection.
The Collection interface contains methods that perform basic operations, such as int size() , boolean isEmpty() , boolean contains(Object element) , boolean add(E element) , boolean remove(Object element) , and Iterator<E> iterator() .
I would wrap the list in another List
with transformers attached.
public class MappedList<S, T> extends AbstractList<T> {
private final List<S> source;
private final Function<S, T> fromTransformer;
private final Function<T, S> toTransformer;
public MappedList(List<S> source, Function<S, T> fromTransformer, Function<T, S> toTransformer) {
this.source = source;
this.fromTransformer = fromTransformer;
this.toTransformer = toTransformer;
}
public T get(int index) {
return fromTransformer.apply(source.get(index));
}
public T set(int index, T element) {
return fromTransformer.apply(source.set(index, toTransformer.apply(element)));
}
public int size() {
return source.size();
}
public void add(int index, T element) {
source.add(index, toTransformer.apply(element));
}
public T remove(int index) {
return fromTransformer.apply(source.remove(index));
}
}
private void test() {
List<Integer> intList = new ArrayList<>(Arrays.asList(1, 2, 3));
List<String> stringList = new MappedList<>(intList, String::valueOf, Integer::valueOf);
intList.add(4);
stringList.remove(0);
System.out.println(intList); // Prints [2, 3, 4]
System.out.println(stringList); // Prints [2, 3, 4]
}
Note that the fromTransformer
needs null
checking for the input value, if source
may contain null
.
Now you are not transforming the original list into another one and losing contact with the original, you are adding a transformation to the original list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With