Map collection elements and keep reference to source collection
I don't know what version of the JDK you are using, but if you are okay with using the JavaFX library you can use ObservableList
. You do not need to modify an existing list as ObservableList
is a wrapper for java.util.List
. Look at extractor in FXCollection for complex Objects. This article has an example of it.
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ListChangeListener.Change;
public class ObservableBiList{
//prevent stackoverflow
private static final AtomicBoolean wasChanged = new AtomicBoolean( false);
public static <T, R> void change( Change< ? extends T> c, ObservableList< R> list, Function< T, R> convert) {
if( wasChanged.get()){
wasChanged.set( false);
return;
}
wasChanged.set( true);
while( c.next()){
if( c.wasAdded() && !c.wasReplaced()){
for( T str : c.getRemoved())
list.add( convert.apply( str));
}else if( c.wasReplaced()){
for( int i=c.getFrom();i<c.getTo();i++)
list.set( i,convert.apply( c.getList().get( i)));
}else if( c.wasRemoved()){
for( T str : c.getRemoved())
list.remove( convert.apply( str));
}
}
System.out.printf( "Added: %s, Replaced: %s, Removed: %s, Updated: %s, Permutated: %s%n",
c.wasAdded(), c.wasReplaced(), c.wasRemoved(), c.wasUpdated(), c.wasPermutated());
}
public static void main( String[] args){
ObservableList< Integer> intList = FXCollections.observableArrayList();
intList.addAll( 1, 2, 3, 4, 5, 6, 7);
ObservableList< String> stringList = FXCollections.observableArrayList();
stringList.addAll( "1", "2", "3", "4", "5", "6", "7");
intList.addListener( ( Change< ? extends Integer> c) -> change( c, stringList, num->Integer.toString( num)));
stringList.addListener( ( Change< ? extends String> c) -> change( c, intList, str->Integer.valueOf( str)));
intList.set( 1, 22);
stringList.set( 3, "33");
System.out.println( intList);
System.out.println( stringList);
}
}
This is exactly the kind of problems that the Observer Pattern solves.
You can create two wrappers, around List<String>
and List<Integer>
and let first wrapper observe the state of the other one.
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.