在Java中开发通用修饰符的正确方法

时间:2014-06-16 09:07:07

标签: java oop generics collections

在Java中,我们有以下类型的函数:

public Collection<String> removeNulls(Collection<String> input) {
    List<String> output = new ArrayList<>();
    // ...
    return output;
}

请注意,我没有以任何方式修改输入,因为很多时候我们碰巧使用ImmutableList作为List。更常见的是,我们尽可能地确保函数的不变性。但是,我在这里看到的一个陷阱是,我的所有方法都经常使用自己的Collection实现。如果使用我的库的人使用的是LinkedListSetSortedSet,则只要“查找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方法,这不是我想要的。这个问题与现实生活中的问题无关,但更多的是“需要考虑的事情”。换句话说,我的问题是,鉴于你给我一个集合,我必须返回另一个集合,它以某种方式从它派生而不修改输入,我如何确保我的输出集合遵守合同(或具体来说,输入集合的实现?

5 个答案:

答案 0 :(得分:2)

我认为你的方法需要改变。

如果您需要稍后调整它们,创建不可变集合的目的很少。收集应该在暴露于外部世界之前变得不可变(如果需要)。因此,您应该仅对可修改的集合进行操作,并允许您的方法调整参数。

如果您从其他地方收到不可变的集合,请将其转换为可修改的集合,然后按照上述方法进行。

答案 1 :(得分:2)

removeNulls方法是一个非常“简单”的例子,很难想象你可能会使用哪种“修饰符”。

从您的描述中可以看出,新的Streams API in Java 8可能会很好地支持您的实际意图。例如,可以使用

轻松地从null中删除Stream元素
// Filter out all null elements
collection.stream().filter((e) -> e != null).... // Further operations

这里的好处是你的实际问题是在内部处理的:在操作过程中(尽可能)保留了流的characteristics。例如,之前排序的流将保持排序。


处理Java-8之前版本的这种情况的另一种方法是指定输出集合(或者至少提供这样做的选项)。例如:您可以将方法编写为

public <T> Collection<T> removeNulls(Collection<? extends T> input, Collection<T> output)
{
    if (output == null) 
    {
        // Create some default collection or
        // report an error...
    }
    for (T t : input)
    {
        if (t != null)
        {
            output.add(t);
        }
    }
    return output;
}

// Convenience method
public <T> Collection<T> removeNulls(Collection<? extends T> input)
{
    return removeNulls(input, new ArrayList<T>());
}

被称为

Set<String> mySet = new SortedSet<>();
mySet.add(10);
mySet.add(9); // I know that my collection is now sorted
Collection<String> myFilteredSet = removeNulls(mySet, new SortedSet<>());

(注意:人们甚至可以在这里进一步利用类型推理和泛型的力量 - 这只是为了展示基本思想)


在任何情况下,我认为非常重要的是明确指定任何假设作为 JavaDoc 中的前置条件和后置条件,例如

/** 
 * Creates a new collection with the same contents as the given collection,
 * but without any <code>null</code> entries
 *
 * The order of output elements will be the same as the input elements
 * or alternatively:
 * No assumptions may be made about the order of the output collection
 *
 * @return The filtered collection
 */

答案 2 :(得分:1)

我想,你想要的是用操作withoutNulls修改Collection接口,并相应地实现它,但是既然你不能“添加”一个新的方法到现有的集合类,你就不走运了。但是,由于您经常使用自己的集合实现,也许您可​​以扩展您的接口并提供一些方法将常规集合类转换为您自己的集合类。

这样的事情:

public interface BetterCollection<E> extends Collection<E> {
    BetterCollection<E> withoutNulls();
}

public class BetterLinkedList<E> extends LinkedList<E> implements BetterCollection<E> {
    public BetterLinkedList() {}

    public BetterLinkedList(LinkedList<E> input) {
        super(input);
    }

    @Override
    public BetterCollection<E> withoutNulls() {
        BetterCollection<E> result = new BetterLinkedList<E>();
        ...
        return result;
    }
}

我认为在C#中可以使用扩展方法,但是你在实现方面有些限制(例如你无法访问私有字段,我不确定它们是否可以是虚拟的)。

Java中的另一个解决方案是重载方法并使它们接受不同的集合类型,如removeNulls(LinkedList<String>)removeNulls(SortedSet<String>),但它显然也有其缺点。

答案 3 :(得分:0)

至于属性&#34;已排序&#34;,根据

过滤
public Collection<String> someFilter(Collection<String> input)

可能会以允许您保证财产排序&#34;的方式编写。没有违反,即过滤器是稳定的&#34;。

例如,迭代输入集合并存储过滤器在ArrayList中传递的任何内容并返回它将是&#34; stable&#34;。

(当然,可能会有转变,你无法保证。)

另一个属性是保存对象身份&#34;而不是复制,但这有点深奥;-)

答案 4 :(得分:0)

简短回答使用Scala。

更严肃的回答:看看Scala集合库,尤其是BuildFrom

基本思想是,如果可能,您提供的工厂能够创建正确的回收集合。您根据输入参数的类创建集合的想法是朝着正确方向迈出的一大步。

完整的Scala版本会相当复杂,因为用户必须自己提供正确的Bui​​ldFrom,并且不能依靠Scalas隐式魔法在98%的情况下提供正确的版本。但是研究它会给你很多想法,你可能会担心什么以及如何解决它。如果你真的想最终做到这一点,那就取决于你。