将映射的不同值导出到另一个ObservableList的ObservableList中? [英] Deriving Mapped Distinct Values into an ObservableList off another ObservableList?

查看:104
本文介绍了将映射的不同值导出到另一个ObservableList的ObservableList中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an interesting problem, and I am relatively new to JavaFX and I need to create a somewhat niche ObservableList implementation.

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

Essentially, I need an ObservableList that maintains a list of mapped derived values off another ObservableList. I need to create an ObservableDistinctList<P,V> that accepts another ObservableList<P> and a Function<P,V> lambda as its constructor arguments. The ObservableDistinctList<P,V> maintains a list of distinct values off the applied Function<P,V> for each element in ObservableList<P>.

例如,假设我有 ObservableList< Flight> ;航班,包含以下实例。

For example, say I have ObservableList<Flight> flights with the following instances.

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,这就是我在客户端这样做的方法。

If I created a new ObservableDistinctList off the carrier values of each Flight object, this is how I would do it on the client side.

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

这些是 distinctCarriers 列表。

WN
DL
UA

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

If a flight got added to flights, it would first check if a new distinct value is actually present before adding it. So a new WN flight would not cause an addition to the distinctCarriers list, but an AA flight will. Conversely, if a flight gets removed from flights, it needs to check if other instances would persist the value before removing it. Removing a WN flight from flights would not cause a removal of WN from the distinctCarriers list, but removing the DL flight will cause its removal.

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

Here is my implementation. Did I implement the ListChangeListener correctly? I get really uncomfortable with List mutability so I wanted to post this before I consider using it in my project. Also, do I need to worry about threadsafety using an ArrayList to back this?

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


推荐答案

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

Below is a simple example (plus driver - hint: that's what you should have provided in the question :-) custom ObservableList that keeps distinct values of a property of the elements in a source list. It keeps itself sync'ed to the source on adding/removing items. The sync is implemented by:


  • 收听源列表更改

  • 收到删除时:如果删除的是具有distinct属性的最后一个,则从自己删除该属性并通知其自己的侦听器有关删除。否则,无事可做。

  • 收到添加的内容:如果添加的是第一个带有distinct属性的属性,请将属性添加到自己(最后)并通知自己的侦听器另外。否则,无事可做。

通过实用程序方法nextRemove / nextAdd发送消息来处理通知。

The notification is handled by messaging the utility methods nextRemove/nextAdd as appropriate.

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

}

这篇关于将映射的不同值导出到另一个ObservableList的ObservableList中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆