将映射的不同值导出到另一个ObservableList的ObservableList中?

时间:2015-04-15 17:23:48

标签: java javafx javafx-8

我有一个有趣的问题,我对JavaFX相对较新,我需要创建一个小小的ObservableList实现。

基本上,我需要一个ObservableList来维护另一个ObservableList的映射派生值列表。我需要创建一个ObservableDistinctList<P,V>,它接受​​另一个ObservableList<P>和一个Function<P,V> lambda作为其构造函数参数。 ObservableDistinctList<P,V>Function<P,V>中的每个元素维护了已应用ObservableList<P>的不同值列表。

例如,假设我ObservableList<Flight> flights包含以下实例。

Flt #   Carrier Orig    Dest    Dep Date
174     WN      ABQ     DAL     5/6/2015
4673    WN      DAL     HOU     5/6/2015
485     DL      DAL     PHX     5/7/2015
6758    UA      JFK     HOU     5/7/2015

如果我从每个Flight对象的载波值创建一个新的ObservableDistinctList,这就是我在客户端这样做的方法。

ObservableDistinctList<Flight,String> distinctCarriers = new 
    ObservableDistinctList(flights, f -> f.getCarrier());

这些是distinctCarriers列表中唯一的值。

WN
DL
UA

如果航班已添加到flights,则在添加之前,首先会检查是否确实存在新的不同值。因此,新的WN航班不会导致添加distinctCarriers列表,但会AA航班。相反,如果航班从flights中删除,则需要检查其他实例是否会在删除之前保留该值。从flights移除WN航班不会导致从WN列表中删除distinctCarriers,但删除DL航班将导致其被删除。

这是我的实施。我是否正确实施了ListChangeListener?我对List可变性感到非常不舒服,所以我想在我考虑在我的项目中使用它之前发布它。另外,我是否需要使用ArrayList来担心线程安全?

public final class ObservableDistinctList<P,V> extends ObservableListBase<V> {

    private final ObservableList<P> parentList;
    private final Function<P,V> valueExtractor;
    private final List<V> values;

    public ObservableDistinctList(ObservableList<P> parentList, Function<P,V> valueExtractor) {
        this.parentList = parentList;
        this.valueExtractor = valueExtractor;
        this.values = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());

        this.parentList.addListener((ListChangeListener.Change<? extends P> c) -> { 
            while (c.next()) { 
                if (c.wasRemoved()) { 
                    final Stream<V> candidatesForRemoval = c.getRemoved().stream().map(p -> valueExtractor.apply(p));
                    final List<V> persistingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());

                    final Stream<V> valuesToRemove = candidatesForRemoval.filter(v -> ! persistingValues.contains(v));

                    valuesToRemove.forEach(v -> values.remove(v));
                }

                if (c.wasAdded()) { 
                    final Stream<V> candidatesForAdd = c.getAddedSubList().stream().map(p -> valueExtractor.apply(p));
                    final List<V> existingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());

                    final Stream<V> valuesToAdd = candidatesForAdd.filter(v -> ! values.contains(v));

                    valuesToAdd.forEach(v -> values.add(v));
                }
            }
        });
    }
    @Override
    public V get(int index) {
        return values.get(index);
    }

    @Override
    public int size() {
        return values.size();
    }
}

1 个答案:

答案 0 :(得分:2)

下面是一个简单的例子(加上驱动程序 - 提示:这就是你应该在问题中提供的内容:-)自定义ObservableList,它保留源列表中元素属性的不同值。在添加/删除项目时,它会自动与源同步。同步通过以下方式实现:

  • 收听源列表更改
  • 当收到删除时:如果删除的是具有distinct属性的最后一个,则从自己删除该属性并通知其自己的侦听器有关删除。否则,无事可做。
  • 当收到添加的内容时:如果添加的是第一个具有distinct属性的属性,则将该属性添加到自己(最后)并通知自己的侦听器有关添加的内容。否则,无事可做。

通过根据需要发送实用程序方法nextRemove / nextAdd来处理通知。

/**
 * Example of how to implement a custom ObservableList.
 * 
 * Here: an immutable and unmodifiable (in itself) list containing distinct
 * values of properties of elements in a backing list, the values are extracted
 * via a function 
 */
public class DistinctMapperDemo extends Application {

    public static class DistinctMappingList<V, E> extends ObservableListBase<E> {

        private List<E> mapped;
        private Function<V, E> mapper;

        public DistinctMappingList(ObservableList<V> source, Function<V, E> mapper) {
            this.mapper = mapper;
            mapped = applyMapper(source); 
            ListChangeListener l = c -> sourceChanged(c);
            source.addListener(l);
        }

        private void sourceChanged(Change<? extends V> c) {
            beginChange();
            List<E> backing = applyMapper(c.getList());
            while(c.next()) {
                if (c.wasAdded()) {
                    wasAdded(c, backing);
                } else if (c.wasRemoved()) {
                    wasRemoved(c, backing);
                } else {
                    // throw just for the example
                    throw new IllegalStateException("unexpected change " + c);
                }
            }
            endChange();
        }

        private void wasRemoved(Change<? extends V> c, List<E> backing) {
            List<E> removedCategories = applyMapper(c.getRemoved());
            for (E e : removedCategories) {
                if (!backing.contains(e)) {
                    int index = indexOf(e);
                    mapped.remove(index);
                    nextRemove(index, e);
                }
            }
        }

        private void wasAdded(Change<? extends V> c, List<E> backing) {
            List<E> addedCategories = applyMapper(c.getAddedSubList());
            for (E e : addedCategories) {
                if (!contains(e)) {
                    int last = size();
                    mapped.add(e);
                    nextAdd(last, last +1);
                }
            }
        }

        private List<E> applyMapper(List<? extends V> list) {
            List<E> backing = list.stream().map(p -> mapper.apply(p)).distinct()
                    .collect(Collectors.toList());
            return backing;
        }

        @Override
        public E get(int index) {
            return mapped.get(index);
        }

        @Override
        public int size() {
            return mapped.size();
        }

    }

    int categoryCount;
    private Parent getContent() {
        ObservableList<DemoData> data = FXCollections.observableArrayList(
                new DemoData("first", "some"),
                new DemoData("second", "some"),
                new DemoData("first", "other"),
                new DemoData("dup", "other"),
                new DemoData("dodo", "next"),
                new DemoData("getting", "last")

                );
        TableView<DemoData> table = new TableView<>(data);
        TableColumn<DemoData, String> name = new TableColumn<>("Name");
        name.setCellValueFactory(new PropertyValueFactory<>("name"));
        TableColumn<DemoData, String> cat = new TableColumn<>("Category");
        cat.setCellValueFactory(new PropertyValueFactory<>("category"));
        table.getColumns().addAll(name, cat);

        Function<DemoData, String> mapper = c -> c.categoryProperty().get();
        ObservableList<String> mapped = new DistinctMappingList<>(data, mapper);
        ListView<String> cats = new ListView<>(mapped);

        Button remove = new Button("RemoveSelected DemoData");
        remove.setOnAction(e -> {
            int selected = table.getSelectionModel().getSelectedIndex(); 
            if (selected <0) return;
            data.remove(selected);
        });

        Button createNewCategory = new Button("Create DemoData with new Category");
        createNewCategory.setOnAction(e -> {
            String newCategory = data.size() == 0 ? "some" + categoryCount : 
                data.get(0).categoryProperty().get() + categoryCount;
            data.add(new DemoData("name" + categoryCount, newCategory));
            categoryCount++;
        });
        VBox buttons = new VBox(remove, createNewCategory);
        HBox box = new HBox(table, cats, buttons);
        return box;
    }

    public static class DemoData {
        StringProperty name = new SimpleStringProperty(this, "name");
        StringProperty category = new SimpleStringProperty(this, "category");

        public DemoData(String name, String category) {
            this.name.set(name);
            this.category.set(category);
        }

        public StringProperty nameProperty() {
            return name;
        }

        public StringProperty categoryProperty() {
            return category;
        }
    }
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent()));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}