【问题标题】:How to concatenate observable lists in JavaFX?如何在 JavaFX 中连接可观察列表?
【发布时间】:2016-09-28 05:23:04
【问题描述】:

连接是指获取一个新列表,它监听所有连接部分的变化。

方法FXCollections#concat(ObservableList<E>... lists)的目的是什么?如果它只是合并几个列表,那么我认为为此设置单独的方法是没有意义的。

如果按照我的意愿去做,那就行不通了:

import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

public class ConcatObservabeList {

   public static void main(String[] args) {

      ObservableList<Integer> list1 = FXCollections.observableArrayList();
      ObservableList<Integer> list2 = FXCollections.observableArrayList();


      ObservableList<Integer> concat = FXCollections.concat(list1, list2);
      concat.addListener(new ListChangeListener<Integer>() {
         public void onChanged(Change<? extends Integer> c) {
            System.out.println("changed");
         }
      });

      list1.add(12);


   }
}

【问题讨论】:

  • 可能只是为了方便 - 看看代码 ;-) 我怀疑它只是将所有列表的所有内容复制到自己的支持结构中(与 observableList(List) 相同
  • @kleopatra:看起来它复制了参考文献,如下所示。
  • @trashgod 是的,这是我的猜测,不是吗:-)
  • 您可能会尝试实现自定义 TransformList (这可能是答案之一中引用的库中所做的,但没有检查)好奇:您到底在追求什么?

标签: java list javafx concat observablelist


【解决方案1】:

如果你对使用库没问题,我推荐EasyBind

ObservableList<String> listA = ...;
ObservableList<String> listB = ...;
ObservableList<String> combinedList = EasyBind.concat(listA, listB);

如果您不喜欢添加新的依赖项,@Lukas Körfer 使用 ListBinding 的答案是您将获得的最佳答案。

【讨论】:

    【解决方案2】:

    我偶然发现了同样的问题,因为 FXCollections.concat(...) 方法不会随着源列表的更改而改变,这是我的用例的要求。

    由于这个问题的其他答案对我来说似乎有点矫枉过正,我将添加我自己的解决方案,它有两个主要限制:

    • 生成的ObservableList 将对源列表的内容只读。
    • 生成的 ObservableList 将在每次更改时成为一个新实例,因此基于它们的可能 UI 元素将在每次更改时重新创建。

    由于任何ListBinding 计算一个ObservableList 作为它的值并同时实现(因此是一个)ObservableList,它可以很容易地用于增强默认的concat 方法以更新来源列表:

    @SafeVarargs
    public static <T> ObservableList<T> concat(ObservableList<T>... sources) {
        return new ListBinding<T>() {
    
            {
                bind(sources);
            }
    
            @Override
            protected ObservableList<T> computeValue() {
                return FXCollections.concat(sources);
            }
        };
    }
    

    【讨论】:

    • 这是我真正需要的!谢谢
    【解决方案3】:

    我也遇到了同样的问题。最后我做了一个 ObservableList 的列表:

    import java.util.AbstractList;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.List;
    import java.util.NoSuchElementException;
    
    import javafx.beans.InvalidationListener;
    import javafx.beans.Observable;
    import javafx.beans.WeakInvalidationListener;
    import javafx.collections.ListChangeListener;
    import javafx.collections.ObservableList;
    import javafx.collections.WeakListChangeListener;
    
    /**
     * Read only view on a list of {@link ObservableList}
     *
     * @author Marcel Heckel
     */
    public class CompositeObservableList<T> extends AbstractList<T> implements ObservableList<T>
    {
        protected List<ObservableList<T>>             lists;
    
        protected List<ListChangeListener<? super T>> listChangeListeners          = new ArrayList<>();
    
        protected List<InvalidationListener>          invalidationListeners        = new ArrayList<>();
    
        protected InvalidationListener                internalInvalidationListener = this::invalidated;
    
        protected ListChangeListener<T>               internalListChangeListener   = this::onChanged;
    
        protected WeakInvalidationListener            weakInvalidationListener     = new WeakInvalidationListener(
            internalInvalidationListener);
    
        protected WeakListChangeListener<T>           weakListChangeListener       = new WeakListChangeListener<>(
            internalListChangeListener);
    
        public CompositeObservableList()
        {
            this.lists = new ArrayList<>();
        }
    
        public CompositeObservableList(List<ObservableList<T>> lists)
        {
            this.lists = lists;
    
            for (ObservableList<T> l : lists)
            {
                l.addListener(weakInvalidationListener);
                l.addListener(weakListChangeListener);
            }
        }
    
        public void addObservableList(ObservableList<T> l)
        {
            lists.add(l);
            l.addListener(weakInvalidationListener);
            l.addListener(weakListChangeListener);
        }
    
        /** remove listeners and clears the internal list */
        public void clearLists()
        {
            for (ObservableList<T> l : lists)
            {
                l.removeListener(weakInvalidationListener);
                l.removeListener(weakListChangeListener);
            }
            lists.clear();
        }
    
        ///////////////////////////////////////
        // listeners
    
        private void invalidated(Observable observable)
        {
            for (InvalidationListener l : invalidationListeners)
                l.invalidated(CompositeObservableList.this);
        }
    
        private void onChanged(ListChangeListener.Change<? extends T> c)
        {
            int idx = getStartIndexOfListReference(c.getList());
            assert (idx >= 0);
            if (idx < 0)
                return;
    
            c = new IndexOffsetChange<>(CompositeObservableList.this, idx, c);
    
            for (ListChangeListener<? super T> l : listChangeListeners)
            {
                l.onChanged(c);
            }
        }
    
        private int getStartIndexOfListReference(ObservableList<?> l)
        {
            int startIndex = 0;
            for (int i = 0; i < lists.size(); i++ )
            {
                if (l == lists.get(i))
                    return startIndex;
                startIndex += l.size();
            }
            return -1;
        }
    
        ////////////////////////////////////////
    
        @Override
        public int size()
        {
            int size = 0;
            for (Collection<T> c : lists)
                size += c.size();
            return size;
        }
    
        @Override
        public boolean isEmpty()
        {
            for (Collection<T> c : lists)
                if ( !c.isEmpty())
                    return false;
            return true;
        }
    
        @Override
        public boolean contains(Object obj)
        {
            for (Collection<T> c : lists)
                if (c.contains(obj))
                    return true;
            return false;
        }
    
        @Override
        public boolean containsAll(Collection<?> c)
        {
            for (Object ele : c)
            {
                if ( !this.contains(ele))
                    return false;
            }
            return true;
        }
    
        @Override
        public int indexOf(Object o)
        {
            int index = 0;
            for (List<T> l : lists)
            {
                int i = l.indexOf(o);
                if (i >= 0)
                    return index + i;
                index += l.size();
            }
            return -1;
        }
    
        @Override
        public T get(int index)
        {
            if (index < 0)
                throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());
    
            for (List<T> l : lists)
            {
                if (l.size() > index)
                    return l.get(index);
                index -= l.size();
            }
            throw new IndexOutOfBoundsException("index: " + index + " - size: " + size());
        }
    
        @Override
        public Iterator<T> iterator()
        {
            return new Iterator<T>()
                {
                    Iterator<T>                 currentIterator = null;
    
                    Iterator<ObservableList<T>> listsIterator   = lists.iterator();
    
                    @Override
                    public boolean hasNext()
                    {
                        while (true)
                        {
                            if (currentIterator != null && currentIterator.hasNext())
                                return true;
                            if ( !listsIterator.hasNext())
                                return false;
                            currentIterator = listsIterator.next().iterator();
                        }
                    }
    
                    @Override
                    public T next()
                    {
                        if ( !hasNext())
                            throw new NoSuchElementException();
                        return currentIterator.next();
                    }
    
                    @Override
                    public void remove()
                    {
                        throw new UnsupportedOperationException();
                    }
    
                };
        }
    
        // editing methods
    
        @Override
        public boolean add(T obj)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean remove(Object obj)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean addAll(Collection<? extends T> c)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean removeAll(Collection<?> c)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean retainAll(final Collection<?> c)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void clear()
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean addAll(int index, Collection<? extends T> c)
        {
    
            throw new UnsupportedOperationException();
        }
    
        @Override
        public T set(int index, T element)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void add(int index, T element)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public T remove(int index)
        {
            throw new UnsupportedOperationException();
        }
    
        // editing methods of ObservableList list
    
        @Override
        public boolean addAll(T... elements)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean setAll(T... elements)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean setAll(Collection<? extends T> c)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean removeAll(T... elements)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public boolean retainAll(T... elements)
        {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public void remove(int from, int to)
        {
            throw new UnsupportedOperationException();
        }
    
        /////////////////////////////
    
        @Override
        public void addListener(InvalidationListener listener)
        {
            invalidationListeners.add(listener);
        }
    
        @Override
        public void removeListener(InvalidationListener listener)
        {
            invalidationListeners.remove(listener);
        }
    
        @Override
        public void addListener(ListChangeListener<? super T> listener)
        {
            listChangeListeners.add(listener);
    
        }
    
        @Override
        public void removeListener(ListChangeListener<? super T> listener)
        {
            listChangeListeners.remove(listener);
        }
    
        ////////////////
    
        private static class IndexOffsetChange<T> extends ListChangeListener.Change<T>
        {
            private final int                                    indexOffset;
    
            private final ListChangeListener.Change<? extends T> delegate;
    
            public IndexOffsetChange(ObservableList<T> list, final int indexOffset,
                ListChangeListener.Change<? extends T> c)
            {
                super(list);
                this.indexOffset = indexOffset;
                this.delegate = c;
            }
    
            @Override
            public boolean next()
            {
                return delegate.next();
            }
    
            @Override
            public void reset()
            {
                delegate.reset();
            }
    
            @Override
            public int getFrom()
            {
                return delegate.getFrom() + indexOffset;
            }
    
            @Override
            public int getTo()
            {
                return delegate.getTo() + indexOffset;
            }
    
            @Override
            public boolean wasPermutated()
            {
                return delegate.wasPermutated();
            }
    
            @Override
            public int getPermutation(int i)
            {
                return indexOffset + super.getPermutation(i - indexOffset);
            }
    
            @Override
            protected int[] getPermutation()
            {
                return null;
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public List<T> getAddedSubList()
            {
                return (List<T>) delegate.getAddedSubList();
            }
    
            @Override
            public int getAddedSize()
            {
                return delegate.getAddedSize();
            }
    
            @Override
            public boolean wasAdded()
            {
                return delegate.wasAdded();
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public List<T> getRemoved()
            {
                return (List<T>) delegate.getRemoved();
            }
    
            @Override
            public int getRemovedSize()
            {
                return delegate.getRemovedSize();
            }
    
            @Override
            public boolean wasRemoved()
            {
                return delegate.wasRemoved();
            }
    
            @Override
            public boolean wasReplaced()
            {
                return delegate.wasReplaced();
            }
    
            @Override
            public boolean wasUpdated()
            {
                return delegate.wasUpdated();
            }
    
        }
    
    }
    

    【讨论】:

      【解决方案4】:

      添加到ObservableListListChangeListener 会将列表中的某些特定changes 作为一个整体。正如引用的 API here 所见,监听任何祖先列表的开销也是相当大的。因为FXCollections.concat() 只是将引用从源列表复制到目标的后备列表,添加到concat 的侦听器会看到对concat 所做的更改;它不会看到list1list2的变化。

      如果您出于其他原因不需要创建新的ObservableList,则聚合列表以允许您向每个列表添加相同的侦听器。

      控制台:

      changed { [42] added at 0 }
      

      代码:

      import java.util.ArrayList;
      import java.util.List;
      import javafx.collections.FXCollections;
      import javafx.collections.ListChangeListener;
      import javafx.collections.ObservableList;
      
      /**
       * @see https://stackoverflow.com/a/37527245/230513
       */
      public class ObservableListAggregate {
      
          public static void main(String[] args) {
              ObservableList<Integer> list1 = FXCollections.observableArrayList();
              ObservableList<Integer> list2 = FXCollections.observableArrayList();
              Aggregate<ObservableList<Integer>> aggregate = new Aggregate(list1, list2);
              aggregate.addListener(new ListChangeListener<ObservableList<Integer>>() {
                  @Override
                  public void onChanged(ListChangeListener.Change<? extends ObservableList<Integer>> c) {
                      System.out.println("changed " + c);
                  }
              });
              list1.add(42);
          }
      
          private static class Aggregate<T> {
      
              List<ObservableList<T>> lists = new ArrayList<>();
      
              public Aggregate(ObservableList<T>... lists) {
                  for (ObservableList<T> list : lists) {
                      this.lists.add(list);
                  }
              }
      
              public final void addListener(ListChangeListener<? super T> listener) {
                  for (ObservableList<T> list : lists) {
                      list.addListener(listener);
                  }
              }
      
              public final void removeListener(ListChangeListener<? super T> listener) {
                  for (ObservableList<T> list : lists) {
                      list.removeListener(listener);
                  }
              }
          }
      }
      

      要查看单个列表元素的更改,请使用ObservableList&lt;Observable&gt;,例如ObservableList&lt;IntegerProperty&gt;。在下面的示例中,请注意,添加到 list1 中的 ip 与后来在 concat 中修改的IntegerProperty相同

      控制台:

      IntegerProperty [value: 42]
      concat changed { [IntegerProperty [value: 2147483647]] added at 1 }
      

      代码:

      import javafx.beans.property.IntegerProperty;
      import javafx.beans.property.SimpleIntegerProperty;
      import javafx.collections.FXCollections;
      import javafx.collections.ListChangeListener;
      import javafx.collections.ObservableList;
      
      public class ConcatObservabeList {
      
          public static void main(String[] args) {
      
              ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
              IntegerProperty ip = new SimpleIntegerProperty(0);
              ip.addListener(System.out::println);
              list1.add(ip);
              ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
              ObservableList<IntegerProperty> concat = FXCollections.concat(list1, list2);
              concat.get(0).setValue(42);
              concat.addListener(new ListChangeListener<IntegerProperty>() {
                  @Override
                  public void onChanged(ListChangeListener.Change<? extends IntegerProperty> c) {
                      System.out.println("concat changed " + c);
                  }
              });
              concat.add(new SimpleIntegerProperty(Integer.MAX_VALUE));
          }
      }
      

      【讨论】:

      • hmm .. 您可能会考虑澄清将新项目添加到源列表之一将不会触发串联列表上的 changeListener(我认为这是问题- 可能是错的,但是,大脑在假期中的工作不是最理想的 :-)
      • @kleopatra:你的观点很好;我正在等待 Dims 的回复。
      • 你注意到的有趣的方面。实际上,连接列表似乎包含与成员列表完全相同的实例。这是强化印象,FXCollection#concat 方法设计错误。
      • Dims:我必须反对;观察者应该观察他们被添加为听众的列表;正如引用的 API here 所见,聆听祖先的开销也会组合增加;我要重申@kleopatra 的问题——你想解决什么问题?
      • @Dims 实际上,我同意该方法是一个 api 设计意外:意外的方面是它需要一个 ObservableList 类型的列表,而它的实现只使用一个简单的列表(复制所有内容进入 Arraylist,然后调用 observableList(copiedAll)。这种类型的要求本身可能会导致(否则不支持)期望这些列表被观察到。lAlso javadoc 显然是由 编写的。但同样:这不是火箭科学:只需实现您自己的(在没有合适 IDE 的假期中,将向您展示其他方法:-)
      【解决方案5】:

      我同样需要将多个 ObservableList 连接/聚合到一个 JavaFX LineChart 列表中。我在此处或在另一个答案中发布的 github 上找到的示例总是在每次更改时将子列表中的所有条目复制到聚合列表中。对于包含许多条目的列表,这似乎不是很优雅。

      我决定实现我的 on 版本,它跟踪子列表在聚合列表中的位置,当子列表中的元素发生更改时,在聚合列表中应用相同的更改。仍有改进的空间(不使用委托列表,而是直接扩展 ObservableList,或者将子列表中的事件触发到聚合列表并覆盖 getter 和迭代器 - 将不胜感激),但我想我发布了我的版本在这里,也许它可以帮助某人。

      代码:

      /**
       * This class aggregates several other Observed Lists (sublists), observes changes on those sublists and applies those same changes to the
       * aggregated list.
       * Inspired by:
       * - http://stackoverflow.com/questions/25705847/listchangelistener-waspermutated-block
       * - http://stackoverflow.com/questions/37524662/how-to-concatenate-observable-lists-in-javafx
       * - https://github.com/lestard/advanced-bindings/blob/master/src/main/java/eu/lestard/advanced_bindings/api/CollectionBindings.java
       * Posted result on: http://stackoverflow.com/questions/37524662/how-to-concatenate-observable-lists-in-javafx
       */
      public class AggregatedObservableArrayList<T> {
      
          protected final List<ObservableList<T>> lists = new ArrayList<>();
          final private List<Integer> sizes = new ArrayList<>();
          final private List<InternalListModificationListener> listeners = new ArrayList<>();
          final protected ObservableList<T> aggregatedList = FXCollections.observableArrayList();
      
          public AggregatedObservableArrayList() {
      
          }
      
          /**
           * The Aggregated Observable List. This list is unmodifiable, because sorting this list would mess up the entire bookkeeping we do here.
           *
           * @return an unmodifiable view of the aggregatedList
           */
          public ObservableList<T> getAggregatedList() {
              return FXCollections.unmodifiableObservableList(aggregatedList);
          }
      
          public void appendList(@NotNull ObservableList<T> list) {
              assert !lists.contains(list) : "List is already contained: " + list;
              lists.add(list);
              final InternalListModificationListener listener = new InternalListModificationListener(list);
              list.addListener(listener);
              //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
              sizes.add(list.size());
              aggregatedList.addAll(list);
              listeners.add(listener);
              assert lists.size() == sizes.size() && lists.size() == listeners.size() :
                    "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
          }
      
          public void prependList(@NotNull ObservableList<T> list) {
              assert !lists.contains(list) : "List is already contained: " + list;
              lists.add(0, list);
              final InternalListModificationListener listener = new InternalListModificationListener(list);
              list.addListener(listener);
              //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
              sizes.add(0, list.size());
              aggregatedList.addAll(0, list);
              listeners.add(0, listener);
              assert lists.size() == sizes.size() && lists.size() == listeners.size() :
                    "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
          }
      
          public void removeList(@NotNull ObservableList<T> list) {
              assert lists.size() == sizes.size() && lists.size() == listeners.size() :
                    "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
              final int index = lists.indexOf(list);
              if (index < 0) {
                  throw new IllegalArgumentException("Cannot remove a list that is not contained: " + list + " lists=" + lists);
              }
              final int startIndex = getStartIndex(list);
              final int endIndex = getEndIndex(list, startIndex);
              // we want to find the start index of this list inside the aggregated List. End index will be start + size - 1.
              lists.remove(list);
              sizes.remove(index);
              final InternalListModificationListener listener = listeners.remove(index);
              list.removeListener(listener);
              aggregatedList.remove(startIndex, endIndex + 1); // end + 1 because end is exclusive
              assert lists.size() == sizes.size() && lists.size() == listeners.size() :
                    "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size() + " or not equal to listeners.size=" + listeners.size();
          }
      
          /**
           * Get the start index of this list inside the aggregated List.
           * This is a private function. we can safely asume, that the list is in the map.
           *
           * @param list the list in question
           * @return the start index of this list in the aggregated List
           */
          private int getStartIndex(@NotNull ObservableList<T> list) {
              int startIndex = 0;
              //System.out.println("=== searching startIndex of " + list);
              assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
              final int listIndex = lists.indexOf(list);
              for (int i = 0; i < listIndex; i++) {
                  final Integer size = sizes.get(i);
                  startIndex += size;
                  //System.out.println(" startIndex = " + startIndex + " added=" + size);
              }
              //System.out.println("startIndex = " + startIndex);
              return startIndex;
          }
      
          /**
           * Get the end index of this list inside the aggregated List.
           * This is a private function. we can safely asume, that the list is in the map.
           *
           * @param list       the list in question
           * @param startIndex the start of the list (retrieve with {@link #getStartIndex(ObservableList)}
           * @return the end index of this list in the aggregated List
           */
          private int getEndIndex(@NotNull ObservableList<T> list, int startIndex) {
              assert lists.size() == sizes.size() : "lists.size=" + lists.size() + " not equal to sizes.size=" + sizes.size();
              final int index = lists.indexOf(list);
              return startIndex + sizes.get(index) - 1;
          }
      
          private class InternalListModificationListener implements ListChangeListener<T> {
      
              @NotNull
              private final ObservableList<T> list;
      
              public InternalListModificationListener(@NotNull ObservableList<T> list) {
                  this.list = list;
              }
      
              /**
               * Called after a change has been made to an ObservableList.
               *
               * @param change an object representing the change that was done
               * @see Change
               */
              @Override
              public void onChanged(Change<? extends T> change) {
                  final ObservableList<? extends T> changedList = change.getList();
                  final int startIndex = getStartIndex(list);
                  final int index = lists.indexOf(list);
                  final int newSize = changedList.size();
                  //System.out.println("onChanged for list=" + list + " aggregate=" + aggregatedList);
                  while (change.next()) {
                      final int from = change.getFrom();
                      final int to = change.getTo();
                      //System.out.println(" startIndex=" + startIndex + " from=" + from + " to=" + to);
                      if (change.wasPermutated()) {
                          final ArrayList<T> copy = new ArrayList<>(aggregatedList.subList(startIndex + from, startIndex + to));
                          //System.out.println("  permutating sublist=" + copy);
                          for (int oldIndex = from; oldIndex < to; oldIndex++) {
                              int newIndex = change.getPermutation(oldIndex);
                              copy.set(newIndex - from, aggregatedList.get(startIndex + oldIndex));
                          }
                          //System.out.println("  permutating done sublist=" + copy);
                          aggregatedList.subList(startIndex + from, startIndex + to).clear();
                          aggregatedList.addAll(startIndex + from, copy);
                      } else if (change.wasUpdated()) {
                          // do nothing
                      } else {
                          if (change.wasRemoved()) {
                              List<? extends T> removed = change.getRemoved();
                              //System.out.println("  removed= " + removed);
                              // IMPORTANT! FROM == TO when removing items.
                              aggregatedList.remove(startIndex + from, startIndex + from + removed.size());
                          }
                          if (change.wasAdded()) {
                              List<? extends T> added = change.getAddedSubList();
                              //System.out.println("  added= " + added);
                              //add those elements to your data
                              aggregatedList.addAll(startIndex + from, added);
                          }
                      }
                  }
                  // update the size of the list in the map
                  //System.out.println("list = " + list + " puttingInMap=" + list.hashCode());
                  sizes.set(index, newSize);
                  //System.out.println("listSizesMap = " + sizes);
              }
      
          }
      
          public String dump(Function<T, Object> function) {
              StringBuilder sb = new StringBuilder();
              sb.append("[");
              aggregatedList.forEach(el -> sb.append(function.apply(el)).append(","));
              final int length = sb.length();
              sb.replace(length - 1, length, "");
              sb.append("]");
              return sb.toString();
          }
      }
      

      jUnit 测试:

      /**
       * Testing the AggregatedObservableArrayList
       */
      public class AggregatedObservableArrayListTest {
      
      
          @Test
          public void testObservableValue() {
              final AggregatedObservableArrayList<IntegerProperty> aggregatedWrapper = new AggregatedObservableArrayList<>();
              final ObservableList<IntegerProperty> aggregatedList = aggregatedWrapper.getAggregatedList();
              aggregatedList.addListener((Observable observable) -> {
                  System.out.println("observable = " + observable);
              });
      
              final ObservableList<IntegerProperty> list1 = FXCollections.observableArrayList();
              final ObservableList<IntegerProperty> list2 = FXCollections.observableArrayList();
              final ObservableList<IntegerProperty> list3 = FXCollections.observableArrayList();
      
              list1.addAll(new SimpleIntegerProperty(1), new SimpleIntegerProperty(2), new SimpleIntegerProperty(3), new SimpleIntegerProperty(4),
                           new SimpleIntegerProperty(5));
              list2.addAll(new SimpleIntegerProperty(10), new SimpleIntegerProperty(11), new SimpleIntegerProperty(12), new SimpleIntegerProperty(13),
                           new SimpleIntegerProperty(14), new SimpleIntegerProperty(15));
              list3.addAll(new SimpleIntegerProperty(100), new SimpleIntegerProperty(110), new SimpleIntegerProperty(120), new SimpleIntegerProperty(130),
                           new SimpleIntegerProperty(140), new SimpleIntegerProperty(150));
      
              // adding list 1 to aggregate
              aggregatedWrapper.appendList(list1);
              assertEquals("wrong content", "[1,2,3,4,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // removing elems from list1
              list1.remove(2, 4);
              assertEquals("wrong content", "[1,2,5]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // adding second List
              aggregatedWrapper.appendList(list2);
              assertEquals("wrong content", "[1,2,5,10,11,12,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // removing elems from second List
              list2.remove(1, 3);
              assertEquals("wrong content", "[1,2,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // replacing element in first list
              list1.set(1, new SimpleIntegerProperty(3));
              assertEquals("wrong content", "[1,3,5,10,13,14,15]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // adding third List
              aggregatedWrapper.appendList(list3);
              assertEquals("wrong content", "[1,3,5,10,13,14,15,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // emptying second list
              list2.clear();
              assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // adding new elements to second list
              list2.addAll(new SimpleIntegerProperty(203), new SimpleIntegerProperty(202), new SimpleIntegerProperty(201));
              assertEquals("wrong content", "[1,3,5,203,202,201,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // sorting list2. this results in permutation
              list2.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue()));
              assertEquals("wrong content", "[1,3,5,201,202,203,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // removing list2 completely
              aggregatedWrapper.removeList(list2);
              assertEquals("wrong content", "[1,3,5,100,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // updating one integer value in list 3
              SimpleIntegerProperty integer = (SimpleIntegerProperty) list3.get(0);
              integer.set(1);
              assertEquals("wrong content", "[1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
              // prepending list 2 again
              aggregatedWrapper.prependList(list2);
              assertEquals("wrong content", "[201,202,203,1,3,5,1,110,120,130,140,150]", aggregatedWrapper.dump(ObservableIntegerValue::get));
      
          }
      
      }
      

      【讨论】:

        【解决方案6】:

        我遇到了同样的问题,并在库 advanced-bindings (javadoc of the helper method) 中为此用例创建了一个助手。

        【讨论】:

        • 请注意,不鼓励link-only answers,因此答案应该是搜索解决方案的终点(与另一个参考文献的中途停留相比,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。
        • 我必须同意@kleopatra;引用的方法的开销是相当大的;提纲会很有用。
        • 我不确定CollectionBindings.concat() 的结果是否可以称为ObservableList,因为每次更改lists 都会清除它。
        • @trashgod 我认为它已包含在合同中,尽管它的行为非常恶劣,因为它没有以最狭隘的方式报告更改,而是将(哈哈)每种类型的更改都合并为明确/多重添加
        • 你是对的。实现不是很好,更改事件的行为可能是一个问题。我在这里发布它是因为它可能对某些人仍然有用。至少在某些用例中它对我很有用。但也许我会修复它并将其替换为在所有用例中都按预期运行的“真实”实现。
        猜你喜欢
        • 2021-12-07
        • 1970-01-01
        • 2016-10-20
        • 1970-01-01
        • 1970-01-01
        • 2018-07-09
        • 1970-01-01
        • 1970-01-01
        • 2016-08-06
        相关资源
        最近更新 更多