将一个 observableList 镜像到另一个

mirror one observableList to another

  1. 在 JavaFX 中,我有一个 ObservableList 个对象,并且想要另一个 ObservableList 来镜像第一个列表但包含每个对象的字符串表示形式。有什么比编写自定义 ListChangeListener 来进行转换更简单的吗?我有一个 StringConverter 可以提供镜像值。

  2. 同样,给定一个 ObservableList<String>,我如何创建第二个 ObservableList<String>,它在索引 0 处有一个常量条目,并镜像从索引 1 开始的第一个列表?

对于第一个问题,最简单的方法是使用EasyBind框架。那么就这么简单

ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);

这是一个使用 EasyBind 的 SSCCE:

import org.fxmisc.easybind.EasyBind;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;


public class MappedAndTransformedListExample {

    public static void main(String[] ags) {
        ObservableList<Person> baseList = FXCollections.observableArrayList(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com")
        );

        StringConverter<Person> converter = new StringConverter<Person>() {

            @Override
            public String toString(Person person) {
                return person.getFirstName() + " " + person.getLastName();
            }

            @Override
            public Person fromString(String string) {
                int indexOfDelimiter = string.indexOf(' ');
                return new Person(string.substring(0, indexOfDelimiter), 
                        string.substring(indexOfDelimiter+1), 
                        "");
            }

        };

        ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

        namesList.forEach(System.out::println);

        namesList.addListener((Change<? extends String> c) -> {
            while (c.next()) {
                if (c.wasAdded()) {
                    System.out.println("Added "+c.getAddedSubList());
                }
            }
        });

        System.out.println("\nAdding Michael to base list...\n");
        baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));

        namesList.forEach(System.out::println);
    }



    public static class Person {
        private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
        private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
        private final StringProperty email = new SimpleStringProperty(this, "email");

        public Person(String firstName, String lastName, String email) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.email.set(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty emailProperty() {
            return this.email;
        }

        public final java.lang.String getEmail() {
            return this.emailProperty().get();
        }

        public final void setEmail(final java.lang.String email) {
            this.emailProperty().set(email);
        }

    }
}

如果您出于某种原因不想使用第三方框架,您可以使用 TransformationList(这是 EasyBind 在幕后所做的:我从那里的源代码中复制了下面的代码并且修改了它)。

在上面,您将替换

ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

    ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {

        @Override
        public int getSourceIndex(int index) {
            return index ;
        }

        @Override
        public String get(int index) {
            return converter.toString(getSource().get(index));
        }

        @Override
        public int size() {
            return getSource().size();
        }

        @Override
        protected void sourceChanged(Change<? extends Person> c) {
            fireChange(new Change<String>(this) {
                @Override
                public boolean wasAdded() {
                    return c.wasAdded();
                }

                @Override
                public boolean wasRemoved() {
                    return c.wasRemoved();
                }

                @Override
                public boolean wasReplaced() {
                    return c.wasReplaced();
                }

                @Override
                public boolean wasUpdated() {
                    return c.wasUpdated();
                }

                @Override
                public boolean wasPermutated() {
                    return c.wasPermutated();
                }

                @Override
                public int getPermutation(int i) {
                    return c.getPermutation(i);
                }

                @Override
                protected int[] getPermutation() {
                    // This method is only called by the superclass methods
                    // wasPermutated() and getPermutation(int), which are
                    // both overriden by this class. There is no other way
                    // this method can be called.
                    throw new AssertionError("Unreachable code");
                }

                @Override
                public List<String> getRemoved() {
                    ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
                    for(Person removedPerson: c.getRemoved()) {
                        res.add(converter.toString(removedPerson));
                    }
                    return res;
                }

                @Override
                public int getFrom() {
                    return c.getFrom();
                }

                @Override
                public int getTo() {
                    return c.getTo();
                }

                @Override
                public boolean next() {
                    return c.next();
                }

                @Override
                public void reset() {
                    c.reset();
                }
            });
        }


    };

对于第二个问题,你必须使用转换列表。这是一个更新的 main(...) 方法,展示了如何执行此操作。 (它与第 1 部分的第二个版本一样有效。)

public static void main(String[] ags) {
    ObservableList<Person> baseList = FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com")
    );

    StringConverter<Person> converter = new StringConverter<Person>() {

        @Override
        public String toString(Person person) {
            return person.getFirstName() + " " + person.getLastName();
        }

        @Override
        public Person fromString(String string) {
            int indexOfDelimiter = string.indexOf(' ');
            return new Person(string.substring(0, indexOfDelimiter), 
                    string.substring(indexOfDelimiter+1), 
                    "");
        }

    };

    ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

    ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {

        @Override
        public int getSourceIndex(int index) {
            return index - 1 ;
        }

        @Override
        public String get(int index) {
            if (index == 0) {
                return "Contacts";
            } else {
                return getSource().get(index - 1);
            }
        }

        @Override
        public int size() {
            return getSource().size() + 1 ;
        }

        @Override
        protected void sourceChanged(Change<? extends String> c) {
            fireChange(new Change<String>(this) {
                @Override
                public boolean wasAdded() {
                    return c.wasAdded();
                }

                @Override
                public boolean wasRemoved() {
                    return c.wasRemoved();
                }

                @Override
                public boolean wasReplaced() {
                    return c.wasReplaced();
                }

                @Override
                public boolean wasUpdated() {
                    return c.wasUpdated();
                }

                @Override
                public boolean wasPermutated() {
                    return c.wasPermutated();
                }

                @Override
                public int getPermutation(int i) {
                    return c.getPermutation(i - 1) + 1;
                }

                @Override
                protected int[] getPermutation() {
                    // This method is only called by the superclass methods
                    // wasPermutated() and getPermutation(int), which are
                    // both overriden by this class. There is no other way
                    // this method can be called.
                    throw new AssertionError("Unreachable code");
                }

                @Override
                public List<String> getRemoved() {
                    ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
                    for(String removed: c.getRemoved()) {
                        res.add(removed);
                    }
                    return res;
                }

                @Override
                public int getFrom() {
                    return c.getFrom() + 1;
                }

                @Override
                public int getTo() {
                    return c.getTo() + 1;
                }

                @Override
                public boolean next() {
                    return c.next();
                }

                @Override
                public void reset() {
                    c.reset();
                }
            });
        }

    };

    namesListWithHeader.forEach(System.out::println);

    namesListWithHeader.addListener((Change<? extends String> c) -> {
        while (c.next()) {
            if (c.wasAdded()) {
                System.out.println("Added "+c.getAddedSubList());
                System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
            }
        }
    });

    System.out.println("\nAdding Michael to base list...\n");
    baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));

    namesListWithHeader.forEach(System.out::println);
}

这里是 James_D 使用 Lombok 的 @Delegate 的答案的一个稍微简短的版本,以避免必须编写所有委托方法

/**
 * A List that mirrors a base List by applying a converter on all items.
 * @param <E> item type of the target List
 * @param <F> item type of the base list
 */
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {

    /** mapping function from base list item type to target list item type */
    private final Function<F, E> converter;

    public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
        super(list);
        this.converter = converter;
    }

    @Override
    public int getSourceIndex(int index) {
        return index;
    }

    @Override
    public E get(int index) {
        return converter.apply(getSource().get(index));
    }

    @Override
    public int size() {
        return getSource().size();
    }

    @Override
    protected void sourceChanged(Change<? extends F> change) {
        fireChange(new DelegatingChange(change, this));
    }


    /**
     * An implementation of {@link Change} that delegates all methods to a specified change except {@link #getRemoved()} 
     */
    private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {

        @Delegate(excludes = DelegatingChangeExcluded.class)
        private final Change<? extends F> change;

        public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
            super(list);
            this.change = change;
        }

        @Override
        protected int[] getPermutation() {
            return new int[0];
        }

        @Override
        public List<E> getRemoved() {
            return change.getRemoved().stream()
                    .map(converter)
                    .collect(Collectors.toList());
        }
    }

    /**
     * This interface is only used to exclude some methods from delegated methods via Lombok's @{@link Delegate}
     * so that the compiler doesn't complain.
     */
    @SuppressWarnings("unused")
    private interface DelegatingChangeExcluded<E> {
        List<E> getRemoved();
        ObservableList<? extends E> getList();
        List<E> getAddedSubList();
    }
}