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

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

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

,它接受另一个ObservableList

和一个Function

lambda作为它的构造函数参数。 ObservableDistinctList

维护ObservableList

每个元素的应用Function

的不同值列表。

例如,假设我有以下实例的ObservableList 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 distinctCarriers = new ObservableDistinctList(flights, f -> f.getCarrier()); 

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

 WN DL UA 

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

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

 public final class ObservableDistinctList

extends ObservableListBase { private final ObservableList

parentList; private final Function

valueExtractor; private final List values; public ObservableDistinctList(ObservableList

parentList, Function

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 c) -> { while (c.next()) { if (c.wasRemoved()) { final Stream candidatesForRemoval = c.getRemoved().stream().map(p -> valueExtractor.apply(p)); final List persistingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList()); final Stream valuesToRemove = candidatesForRemoval.filter(v -> ! persistingValues.contains(v)); valuesToRemove.forEach(v -> values.remove(v)); } if (c.wasAdded()) { final Stream candidatesForAdd = c.getAddedSubList().stream().map(p -> valueExtractor.apply(p)); final List existingValues = parentList.stream().map(p -> valueExtractor.apply(p)).distinct().collect(Collectors.toList()); final Stream 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,它保留源列表中元素属性的不同值。 在添加/删除项目时,它会自动与源同步。 同步通过以下方式实现:

  • 听取源列表的变化
  • 当接收到删除时:如果删除的是具有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 extends ObservableListBase { private List mapped; private Function mapper; public DistinctMappingList(ObservableList source, Function mapper) { this.mapper = mapper; mapped = applyMapper(source); ListChangeListener l = c -> sourceChanged(c); source.addListener(l); } private void sourceChanged(Change c) { beginChange(); List 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 c, List backing) { List 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 c, List backing) { List addedCategories = applyMapper(c.getAddedSubList()); for (E e : addedCategories) { if (!contains(e)) { int last = size(); mapped.add(e); nextAdd(last, last +1); } } } private List applyMapper(List list) { List 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 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 table = new TableView<>(data); TableColumn name = new TableColumn<>("Name"); name.setCellValueFactory(new PropertyValueFactory<>("name")); TableColumn cat = new TableColumn<>("Category"); cat.setCellValueFactory(new PropertyValueFactory<>("category")); table.getColumns().addAll(name, cat); Function mapper = c -> c.categoryProperty().get(); ObservableList mapped = new DistinctMappingList<>(data, mapper); ListView 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); } }