如何根据谓词创建一个实时子集合?

3

背景

我有一个界面

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

问题

我的注册表中的getAllThings()返回的Set会反映出对注册表所做的任何更改

然而,getAllThingsWithProperty()返回的Set不会反映这些更改

问题

是否有任何使用标准Java库或一些非常常见的第三方库的方法,可以使getAllThingsWithProperty()的返回值成为一个"活动"的子Set?也就是说,它是由原始Set支持的,但每次访问时都会重新应用Predicate?最好是适用于任何Collection,因为我还有另一个使用List的注册表接口。

我知道我可以编写自己的Set实现,但我更希望避免这样做。


编写自己的Set实现听起来似乎是一件大事,但实际上你可以简单地在一个类中包装标准的HashSet,并且还利用观察者模式。这样,当主集合发生变化时,你就可以“自动”更新你的子集。但显然,如果需要的话,你还必须使用一些同步手段来确保线程安全。 - undefined
@geanakuch 我意识到实施会从小规模开始,但我怀疑这是一个常见问题,所以想知道是否有现成的解决方案。如果我在未来的项目中不得不一遍又一遍地重写相同的东西,那会很烦人。特别是因为这里有很多复杂性的空间,比如你提到的急切缓存或延迟缓存。 - undefined
是的,我明白你的意思;-) 这些事情很快就会失控。但是,也许更好的做法是转换为不可变类,这样你就不必在工作时处理数据的变化了。但这只是我的个人观点,我担心我已经偏离了原始问题的范围... - undefined
2个回答

4
不要使用返回Set<Thing>的方法。你可以编写一个返回Supplier<Set<Thing>>的方法。每次你想要获取当前的Set时,调用该Supplierget()方法。
public Supplier<Set<Thing>> getAllThingsWithProperty(final String property) {
    return () -> this.things.stream().filter((thing) -> thing.hasProperty(property)).collect(Collectors.toSet());
}

3
使用AbstractSet来实现一个Set的实现非常简单。你只需要实现sizeiterator方法即可。你已经在使用流了,所以你可以直接使用流来实现这些方法:
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。它提供了对后备集合的“实时视图”,因为它的元素在每次操作时都会进行流式处理和过滤。
对于元素数量较少的集合,这是可行的,但随着元素数量的增长,它可能会明显减慢。例如,contains方法可能会迭代整个集合,因此时间复杂度为O(N)。您可以重写contains方法直接委托给后备集合。这将将时间复杂度降低到底层集合提供的复杂度,对于HashSet来说,这是O(1),但涉及一些细微之处。
要使集合可读写,您需要实现add方法并重新实现迭代器以支持remove方法。但是,您一开始就返回了不可修改的集合,所以也许您不需要这样做。
如果您需要对List进行类似的操作,请查看AbstractList。它非常简单。或者使用AbstractCollection以与AbstractSet类似的方式包装任何集合,就像在这里使用AbstractSet一样。

2
如果您添加一个像@Override @SuppressWarnings("unchecked") public boolean contains(Object o) { return set.contains(o) && pred.test((E)o); }这样的方法,过滤集的性能将显著提高(由于前面的set.contains(o)测试,未检查的转换是合理的)。如果您对List进行相同的操作,它应该扩展AbstractSequentialList,但太多的开发人员假设随机访问而没有进行检查,因此我不建议使用过滤的实时列表视图... - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接