【发布时间】:2014-08-06 01:34:01
【问题描述】:
在 Java 中,我们有这样的函数:
public Collection<String> removeNulls(Collection<String> input) {
List<String> output = new ArrayList<>();
// ...
return output;
}
请注意,我不会以任何方式修改输入,因为很多时候我们碰巧使用ImmutableList 作为列表。更常见的是,我们尽量确保函数的不变性。然而,我在这里看到的一个陷阱是,我所有的方法都经常使用它们自己的Collection 实现。如果有人使用我的库正在使用LinkedList 或Set 或SortedSet,那么每当在他们的列表上调用我的函数时,诸如“查找是O(1)”或“列表总是排序”之类的假设就会中断:
Set<String> mySet = new SortedSet<>();
mySet.add(10);
mySet.add(9); // I know that my collection is now sorted
Collection<String> myFilteredSet = removeNulls(mySet); // It is no longer sorted
Set<String> mySet = Sets.newSortedSet(myFilteredSet); // Have to sort it again
如果是LinkedList,这会变得更加微妙,如果我的函数要返回List,那么我将继续使用该列表。它不会有任何编译时错误,我们也不会看到问题。在前一种情况下,问题至少是可见的,因为我仍然必须将返回的 Collection(或 List)转换为 Set,并且我知道发生了什么。这个问题只会在以后出现,如果有一些繁重的处理依赖于 LinkedList 正在使用。一种理想的方式(但这并不意味着“推荐”)是:
public List<String> removeNulls(List<String> input) {
List<String> output = (List<String>)input.getClass().newInstance();
// ...
return output;
}
这样,我确保至少使用相同的类型实现。您认为这个问题的解决方案是什么?
编辑
澄清一下,我实际上没有 removeNulls 方法,这不是我想要的。这个问题与现实生活中的问题无关,而更像是一个“需要思考的问题”。换句话说,我的问题是,鉴于您给了我一个集合,我必须返回另一个以某种方式派生的集合并且不修改输入,我如何确保我的输出集合符合合同(或具体来说,输入集合的实现)?
【问题讨论】:
-
如果您不能开发更统一的 API,最好将关键假设(尤其是那些您无法以编程方式强制执行的假设)作为文档中的先决条件,因此程序员当他们不正确地使用您的库函数时,没有人可以责怪他们,而是他们自己打破了这一先决条件。在我看来确实存在一些 API 设计问题......
-
我认为关键是输入/原始集合可能不会更改并且调用者得到一个新列表,所以他最终得到了原始列表和不带空值的新列表,可能是原始列表列表可能和其他地方一样需要。
-
您可以通过将签名更改为
<E, C extends Collection<E>> C removeNulls(C input)来确保结果集合在语法上与输入集合的类型相同,但您仍然必须通过反射来实例化结果集合。无论如何,真正的问题是您不知道如何正确处理元素。即使在这个简单的例子中,也可以想象一个集合,它在内部将nulls 存储在一个单独的集合中。然后,removeNulls将以不同的方式实现。集合实现应该决定如何去做。通用解决方案无济于事。
标签: java oop generics collections