Java 从另一个ObservableList导出映射到ObservableList的不同值?

Java 从另一个ObservableList导出映射到ObservableList的不同值?,java,javafx,javafx-8,Java,Javafx,Javafx 8,我有一个有趣的问题,我对JavaFX比较陌生,我需要创建一个有点小的observelist实现 本质上,我需要一个observebleList,它维护另一个observebleList的映射派生值列表。我需要创建一个接受另一个ObservableList和一个函数lambda作为其构造函数参数的observableStincList。observatedistinclist为observatedList中的每个元素维护应用的函数的不同值列表 例如,假设我有observeListFlights,有

我有一个有趣的问题,我对JavaFX比较陌生,我需要创建一个有点小的
observelist
实现

本质上,我需要一个
observebleList
,它维护另一个
observebleList
的映射派生值列表。我需要创建一个接受另一个
ObservableList

和一个
函数
lambda作为其构造函数参数的
observableStincList
observatedistinclist
observatedList

中的每个元素维护应用的
函数的不同值列表

例如,假设我有
observeListFlights
,有以下实例

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
如果我创建了一个新的ObservabledStinctList,列出每个飞行对象的载体值,我将在客户端这样做

ObservableDistinctList<Flight,String> distinctCarriers = new 
    ObservableDistinctList(flights, f -> f.getCarrier());
如果航班被添加到
航班
,则在添加之前,它将首先检查新的不同值是否实际存在。因此,新的
WN
航班不会导致添加到
distinctCarriers
列表中,但
AA
航班会。相反,如果从
航班
中删除航班,则需要检查其他实例是否会在删除之前保留该值。从
flights
中删除WN航班不会导致从
distinctcarries
列表中删除
WN
,但删除
DL
航班将导致其删除

这是我的实现。我是否正确实现了
ListChangeListener
?我对列表可变性感到非常不舒服,所以我想在我把它应用到我的项目之前张贴。另外,我是否需要担心使用
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();
    }
}
public final类ObservabledStincList扩展了ObservableListBase{
私人最终观察者

parentList; 私有最终函数值提取程序; 私有最终列表值; 公共ObservableStinctList(ObservableList

parentList,函数值提取器){ this.parentList=父列表; this.valueExtractor=valueExtractor; this.values=parentList.stream().map(p->valueExtractor.apply(p)).distinct().collect(Collectors.toList()); this.parentList.addListener((下面的ListChangeListener.Change是一个简单的示例(加上驱动程序-提示:这是您应该在问题中提供的:-)自定义ObservableList,它在源列表中保留元素属性的不同值。它在添加/删除项时保持自身与源同步。同步通过以下方式实现:

  • 侦听源的列表更改
  • 当收到一个被删除的消息时:如果被删除的消息是最后一个具有不同属性的消息,请从自己的消息中删除该属性,并通知其自己的侦听器该消息已被删除。否则,就没有什么可做的了
  • 当收到一个added时:如果added是第一个具有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);
    }

}
/**
*如何实现自定义ObservableList的示例。
* 
*这里:一个不可变且不可修改(本身)的列表,包含不同的
*支持列表中元素的属性值,提取这些值
*通过函数
*/
公共类DistinctMapperDemo扩展了应用程序{
公共静态类DistinctMappingList扩展了ObservableListBase{
私有列表映射;
私有函数映射器;
public DistinctMappingList(可观察列表源、函数映射器){
this.mapper=mapper;
映射=applyMapper(源);
ListChangeListener l=c->sourceChanged(c);
source.addListener(l);
}

private void sourceChanged(更改您的ObservatedStincList是否需要是一个列表,还是可以是提供的映射事件流?(只是尝试看看是否有一种替代方法比您现在尝试的更适合您)还有,顺序重要吗?如果不重要,使用帮助吗?顺序可能很重要,但我需要将其绑定到TableView和ListView,这样我就不能使用集合。虽然我完全支持第三方库,但我正在尝试将其用于ControlsFX项目中的一个组件,我不想遇到引入de的障碍悬而未决。不过,我需要查看ReactFX。阅读描述听起来很有趣。为了清楚起见,这个问题要求对问题中包含的实现进行审查和检查?用于..代码审查:-)再说一次,为什么要重新发明轮子?这里的八角形是生成通知所缺少的。TransformList看起来是一个合适的起点:它将为您将侦听器连接到源。您仍然需要实现sourceChanged,更新beginChange/endChange块中的内部状态,并将适当的更改发布到changeBuilder、 您是一位艺术家,谢谢!我想我现在看到了这一切的联系。
ListChangeListener
订阅了
source
,它触发
sourcedChanged()
处理程序。在
beginChange()
endChange()之间
您处理所有添加和删除,这些添加和删除会传递给幕后的某个更改生成器。
wasAdded()
wasaremove()
非常有趣。它们非常重要,但它似乎可以处理所有索引参数的细微差别。这不仅对我眼前的问题有很大帮助,而且可以帮助我理解
ObservaleBaselist
。谢谢!您可能对@kleopatra代码的更精简和完整版本感兴趣:请注意l到“dis”
/**
 * 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);
    }

}