如何基于谓词创建实时子集合?

How can I create a live sub-collection based on a predicate?

提问人:Scroobius 提问时间:11/13/2023 最后编辑:Scroobius 更新时间:11/14/2023 访问量:64

问:

背景

我有一个界面

public interface ThingRegistry {

    public Set<Thing> getAllThings();
    public Set<Thing> getAllThingsWithProperty(String property);
}

和实现

public class MemoryThingRegistry {
    private final Set<Thing> things = new HashSet<>();

    public Set<Thing> getAllThings() {
        return Collections.unmodifiableSet(this.things);
    }

    public Set<Thing> getAllThingsWithProperty(final String property) {
        return this.things.stream().filter((thing) -> thing.hasProperty(property)).collect(Collectors.toUnmodifiableSet());
    }
}

问题

  • 返回的 Set 将反映在我的注册表中所做的任何更改getAllThings()
  • 但是,返回的 Set 不会反映这些更改getAllThingsWithProperty()

问题

有没有办法使用标准的 java 库,或一些非常常见的第三方库,使返回值成为“实时”子?即它由原始文件“支持”,但每次访问时都会重新应用?最好是可以应用于任何的东西,因为我有另一个使用 .getAllThingsWithProperty()SetSetPredicateCollectionList

我知道我可以编写自己的实现,但宁愿避免这种情况。Set

Java 集合 函数式编程

评论

0赞 geanakuch 11/13/2023
编写自己的实现听起来像是一件大事,但您可以简单地将标准 HashSet 包装在一个也利用 Observer 模式的类中。这样,当主集发生变化时,您始终可以“自动”更新您的子集。但显然,您还必须使用一些同步方式来使其线程安全(如果需要)。Set
0赞 Scroobius 11/13/2023
@geanakuch我意识到实现会从小规模开始,但主要是我怀疑这是一个常见问题,所以想知道是否有开箱即用的解决方案。如果我不得不在未来的项目中不断重写同样的东西,那就太烦人了。特别是因为有很多复杂性的空间,例如急切缓存(如您所建议的)或延迟缓存
0赞 geanakuch 11/13/2023
是的,我听到了;-)这些事情很快就会失控。但是,切换到不可变类可能会更好,这样您就不必处理在处理时更改的数据。但这只是我,恐怕我已经偏离了原来的问题足够远......

答:

4赞 Eran 11/13/2023 #1

而不是返回 .您可以编写一个返回 .每次你想得到电流时,你调用那个 的方法。Set<Thing>Supplier<Set<Thing>>SetSupplierget()

public Supplier<Set<Thing>> getAllThingsWithProperty(final String property) {
    return () -> this.things.stream().filter((thing) -> thing.hasProperty(property)).collect(Collectors.toSet());
}
3赞 Stuart Marks 11/14/2023 #2

用它来提出一个实现是相当简单的。您需要做的就是实现 and 方法。您已经在使用流,因此您可以使用流来实现以下方法:AbstractSetSetsizeiterator

public static <E> Set<E> filteredSet(Set<E> set, Predicate<? super E> pred) {
    return new AbstractSet<>() {
        public int size() {
            return (int) set.stream().filter(pred).count();
        }

        public Iterator<E> iterator() {
            return set.stream().filter(pred).iterator();
        }
    };
}

这是一个功能齐全的只读 .它提供了后备集的“实时视图”,因为它的元素在每次操作时都会被流式处理和过滤。Set

这对于元素数量较少的集合是可行的,但随着元素数量的增加,它可能会明显减慢。例如,该方法可能会迭代整个集合,因此为 O(N)。可以重写该方法以直接委托给后备集。这将把时间复杂度降低到基础集合提供的任何内容 -- 因为 这是 O(1) -- 但其中涉及一些微妙之处。containscontainsHashSet

若要使集合读写,需要实现该方法并重新实现迭代器,以便它支持该方法。但是你首先返回的是不可修改的集合,所以也许你不需要它。addremove

如果你需要用 做类似的事情,请看一下。这相当简单。或者用于包装任何集合,方式与此处所示的相同。ListAbstractListAbstractCollectionAbstractSet

评论

2赞 Holger 11/14/2023
如果添加类似 (由于前面的测试,未检查的强制转换是合理的) 方法,则过滤集的性能将显著提高。如果你对 做同样的事情,它应该扩展,但太多的开发人员假设随机访问而不进行检查,所以我不建议过滤实时列表视图......@Override @SuppressWarnings("unchecked") public boolean contains(Object o) { return set.contains(o) && pred.test((E)o); }set.contains(o)ListAbstractSequentialList