问题描述
我有一个有趣的问题,我对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中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!