Thomas N. Thomas N. - 3 years ago 166
Java Question

Deriving Mapped Distinct Values into an ObservableList off another ObservableList?

I have an interesting problem, and I am relatively new to JavaFX and I need to create a somewhat niche


Essentially, I need an
that maintains a list of mapped derived values off another
. I need to create an
that accepts another
and a
lambda as its constructor arguments. The
maintains a list of distinct values off the applied
for each element in

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

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());

These would be the only values in that


If a flight got added to
, it would first check if a new distinct value is actually present before adding it. So a new
flight would not cause an addition to the
list, but an
flight will. Conversely, if a flight gets removed from
, it needs to check if other instances would persist the value before removing it. Removing a WN flight from
would not cause a removal of
from the
list, but removing the
flight will cause its removal.

Here is my implementation. Did I implement the
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
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 = -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());

this.parentList.addListener((ListChangeListener.Change<? extends P> c) -> {
while ( {
if (c.wasRemoved()) {
final Stream<V> candidatesForRemoval = c.getRemoved().stream().map(p -> valueExtractor.apply(p));
final List<V> persistingValues = -> 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 = -> valueExtractor.apply(p)).distinct().collect(Collectors.toList());

final Stream<V> valuesToAdd = candidatesForAdd.filter(v -> ! values.contains(v));

valuesToAdd.forEach(v -> values.add(v));
public V get(int index) {
return values.get(index);

public int size() {
return values.size();

Answer Source

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:

  • listening to list changes of source
  • when receiving a removed: if the removed had been the last with the distinct property, remove that property from oneself and notify its own listener about the removal. Otherwise, there's nothing to do.
  • when receiving an added: if the added is the first with the distinct property, add the property to oneself (at the end) and notify its own listeners about the addition. Otherwise, there's nothing to do.

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);

        private void sourceChanged(Change<? extends V> c) {
            List<E> backing = applyMapper(c.getList());
            while( {
                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);

        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);
                    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();
                    nextAdd(last, last +1);

        private List<E> applyMapper(List<? extends V> list) {
            List<E> backing = -> mapper.apply(p)).distinct()
            return backing;

        public E get(int index) {
            return mapped.get(index);

        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;

        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));
        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) {

        public StringProperty nameProperty() {
            return name;

        public StringProperty categoryProperty() {
            return category;
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getContent()));;

    public static void main(String[] args) {

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download