从另一个 ObservableList 中将映射的不同值派生到一个 ObservableList 中?
Deriving Mapped Distinct Values into an ObservableList off another ObservableList?
我有一个有趣的问题,我是 JavaFX 的新手,我需要创建一个有点小众的 ObservableList
实现。
本质上,我需要一个 ObservableList
来维护另一个 ObservableList
的映射派生值列表。我需要创建一个 ObservableDistinctList<P,V>
接受另一个 ObservableList<P>
和一个 Function<P,V>
lambda 作为其构造函数参数。 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
如果我根据每个 Flight 对象的承运人值创建一个新的 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
?我对 List 可变性感到非常不舒服,所以我想 post 在我考虑在我的项目中使用它之前。另外,我是否需要担心使用 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();
}
}
下面是一个简单示例(加上驱动程序 - 提示:这就是您应该在问题中提供的内容:-) 自定义 ObservableList,它在源列表中保留 属性 元素的不同值。它使自己与 adding/removing 项上的源保持同步。同步通过以下方式实现:
- 正在监听源列表变化
- 收到删除时:如果删除是最后一个具有不同 属性 的,则从自己中删除 属性 并通知其自己的侦听器有关删除的信息。不然没啥可做的。
- 收到添加时:如果添加的是第一个具有不同 属性 的,则将 属性 添加到自己(最后)并通知其自己的听众有关添加的信息。不然没啥可做的。
通知是通过向实用方法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);
}
}
我有一个有趣的问题,我是 JavaFX 的新手,我需要创建一个有点小众的 ObservableList
实现。
本质上,我需要一个 ObservableList
来维护另一个 ObservableList
的映射派生值列表。我需要创建一个 ObservableDistinctList<P,V>
接受另一个 ObservableList<P>
和一个 Function<P,V>
lambda 作为其构造函数参数。 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
如果我根据每个 Flight 对象的承运人值创建一个新的 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
?我对 List 可变性感到非常不舒服,所以我想 post 在我考虑在我的项目中使用它之前。另外,我是否需要担心使用 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();
}
}
下面是一个简单示例(加上驱动程序 - 提示:这就是您应该在问题中提供的内容:-) 自定义 ObservableList,它在源列表中保留 属性 元素的不同值。它使自己与 adding/removing 项上的源保持同步。同步通过以下方式实现:
- 正在监听源列表变化
- 收到删除时:如果删除是最后一个具有不同 属性 的,则从自己中删除 属性 并通知其自己的侦听器有关删除的信息。不然没啥可做的。
- 收到添加时:如果添加的是第一个具有不同 属性 的,则将 属性 添加到自己(最后)并通知其自己的听众有关添加的信息。不然没啥可做的。
通知是通过向实用方法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);
}
}