不使用受保护/受限制的集合的原因

时间:2015-07-31 14:09:22

标签: java collections constraints predicate

是否有任何理由/参数不实现基于谓词/约束限制其成员的Java集合?

鉴于经常需要这样的功能,我希望它已经在apache-commons或Guava等集合框架上实现了。但是,apache indeed had it,番石榴deprecated its version of it,并建议不要使用类似的方法。

Collection interface contract指出,只要记录正确,集合可能会对其元素施加任何限制,因此我无法理解为什么不鼓励保护集合。还有什么其他选择可以确保Integer集合永远不会包含负值而不隐藏整个集合?

3 个答案:

答案 0 :(得分:3)

这只是一个偏好的问题 - 看一下关于checking before vs checking after的主题 - 我认为这就是它归结为什么。另外只检查add()我只能用于不可变对象。

答案 1 :(得分:1)

几乎没有一个(“可接受的”)答案,所以我只想添加一些想法:

正如评论中所提到的,Collection#add(E)已经允许抛出IllegalArgumentException,原因是

  

如果元素的某些属性阻止将其添加到此集合

因此可以说在集合界面的设计中明确考虑了这个案例,并且没有明显的,深刻的,纯技术的(与接口契约相关的)理由不允许创建这样的集合。

但是,在考虑可能的应用模式时,很快就会发现这样一个集合的观察行为可能是......违反直觉的情况,至少可以说。

dcsohl在评论中已经提到了一个,并提到了这样一个集合只是另一个集合上的 view 的情况:

List<Integer> listWithIntegers = new ArrayList<Integer>();

List<Integer> listWithPositiveIntegers = 
    createView(listWithIntegers, e -> e > 0);

//listWithPositiveIntegers.add(-1); // Would throw IllegalArgumentException
listWithIntegers.add(-1); // Fine

// This would be true:
assert(listWithPositiveIntegers.contains(-1));

然而,人们可以争辩说

  • 这样的集合不一定只是视图。相反,可以强制执行只能创建具有此类约束的 new 集合
  • 行为类似于Collections.unmodifiableCollection(Collection)的行为,这是人们普遍预期的。 (虽然它提供了一个更广泛和无所不在的用例,即通过访问器方法返回一个可修改版本的集合来避免暴露类的内部状态)

但在这种情况下,“不一致”的可能性要高得多。

例如,考虑拨打Collection#addAll(Collection)。如果指定集合的​​元素的某些属性阻止将其添加到此集合“,它还允许抛出IllegalArgumentException ”。但是对于像原子性这样的东西没有任何保证。以这种方式表达它:未指定在抛出此类异常时集合的 state 是什么。想象一下这样的案例:

List<Integer> listWithPositiveIntegers = createList(e -> e > 0);

listWithPositiveIntegers.add(1); // Fine
listWithPositiveIntegers.add(2); // Fine
listWithPositiveIntegers.add(Arrays.asList(3,-4,5)); // Throws

assert(listWithPositiveIntegers.contains(3)); // True or false?
assert(listWithPositiveIntegers.contains(5)); // True or false?

(这可能很微妙,但可能是一个问题)。

在创建集合后条件发生更改时,所有这些都可能变得更加棘手(无论它是否只是一个视图)。例如,人们可以想象一系列这样的调用:

List<Integer> listWithPredicate = create(predicate);
listWithPredicate.add(-1); // Fine 
someMethod();
listWithPredicate.add(-1); // Throws

someMethod()中,有一条无辜的行,如

predicate.setForbiddingNegatives(true);

其中一条评论已经提到了可能的性能问题。这当然是正确的,但我认为这不是一个强大的技术论证:无论如何,Collection接口的任何方法的运行时都没有正式的复杂性保证。您不知道collection.add(e)来电需要多长时间。对于LinkedList,它是O(1),但是对于TreeSet,它可以是O(n log n)(并且谁知道此时{4}}是什么。

也许性能问题和可能的不一致性可以被视为更一般性陈述的特殊情况:

这样的集合允许在许多操作中基本上执行任意代码 - 具体取决于谓词的实现。

这可能会产生任意影响,并且无法对算法,性能和确切行为(就一致性而言)进行推理。

底线是:有许多可能的原因不使用此类集合。但我想不出强大而普遍的技术原因。因此,对于这样的集合可能存在应用案例,但应该记住这些警告,考虑到这样一个集合究竟是如何使用

答案 2 :(得分:0)

我会说这样的集合会承担太多责任并违反SRP。

我在这里看到的主要问题是使用该集合的代码的可读性和可维护性。假设您有一个集合,您只允许添加正整数(Collection<Integer>),并在整个代码中使用它。然后需求发生变化,只允许向它添加奇数正整数。因为没有编译时间检查,所以在代码中添加元素的所有实例都比在单独的封装类封装集合时要困难得多。

虽然当然没有接近这种极端,但它与使用Object引用对应用程序中的所有对象有一些相似之处。

更好的方法是利用编译时检查并遵循完善的OOP原则,如类型安全和封装。这意味着创建一个单独的包装类或为集合元素创建一个单独的类型。

例如,如果您确实希望确保只在上下文中使用正整数,则可以创建单独的类型PositiveInteger extends Number,然后将它们添加到Collection<PositiveInteger>。通过这种方式,您可以获得编译时安全性并将PositiveInteger转换为OddPositiveInteger所需的工作量更少。

枚举是一个很好的例子,它更喜欢专用类型和运行时约束值(常量字符串或整数)。