映射集合元素并保持对源集合的引用

Tob*_*fke 13 java collections

我正在寻找一种创建集合,列表,集合或地图的方法,其中包含原始集合的转换元素并反映该集合中的每个修改。

例如,如果我有一个List<Integer>来自第三方API的API,而另一个API需要一个List<String>。我知道我可以像这样转换列表:

List<Integer> intList = thirdPartyBean.getIntListProperty();
List<String> stringList = intList.stream().map(Integer::toString)
    .collect(Collectors.toList());
secondBean.setStringListProperty(stringList);
Run Code Online (Sandbox Code Playgroud)

问题是,如果其中一个列表中的任何内容发生更改,则另一个列表仍将反映以前的状态。假设intList包含[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]
Run Code Online (Sandbox Code Playgroud)

因此,我正在搜索类似List.sublist(from, to)原始列表“支持”结果的内容。

我正在考虑实现自己的列表包装器,其用法如下:

List<String> stringList = new MappedList<>(intList, Integer::toString, Integer::valueOf);
Run Code Online (Sandbox Code Playgroud)

第二个lambda用于反转转换,以支持stringList.add(String)

但是在我自己实施之前,我想知道是否尝试重新发明轮子-也许已经有解决此问题的通用解决方案?

Old*_*eon 8

我会将清单包装在另一个List附有变压器的清单中。

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]
}
Run Code Online (Sandbox Code Playgroud)

请注意,fromTransformer需要null检查输入值(如果source可能包含)null

现在,您没有将原始列表转换为另一个列表并失去与原始列表的联系,而是向原始列表添加了转换。


Sha*_*awn 8

我不知道您使用的是哪个版本的JDK,但如果可以使用JavaFX库,则可以使用ObservableList。您无需像ObservableList的包装一样修改现有列表java.util.List查看FXCollection中的提取器,了解复杂的对象。该文章有它的一个例子。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)