从另一个可观察列表派生映射的不同值到一个可观察列表中?



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

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

例如,假设我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

如果我从每个飞行对象的载体值创建一个新的 ObservableDistinctList,这就是我在客户端的做法。

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

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

WN
DL
UA

如果一个航班被添加到flights,它将首先检查一个新的非重复值是否实际存在,然后再添加它。因此,新的WN航班不会导致distinctCarriers名单增加,但AA航班会。 相反,如果一个航班从flights中删除,它需要在删除它之前检查其他实例是否会保留该值。从flights中删除 WN 航班不会导致从distinctCarriers列表中删除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();
}
}

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

  • 监听源列表更改
  • 收到已删除时:如果被移除的是最后一个具有不同属性的属性,请从自己中删除该属性,并通知其自己的侦听器有关删除的信息。否则,无事可做。
  • 收到 Add 时:如果添加的是第一个具有 distinct 属性的,请将该属性添加到自己(在末尾),并通知其自己的侦听器有关添加的信息。否则,无事可做。

通知通过向实用程序方法"下一步删除/下一步添加"(视情况而定)的消息来处理。

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

最新更新