从抽象集合生成抽象集合的摘要收集

4
这个问题困扰了我一段时间。抽象地说,无论是什么语言,经常有这样的情况:你希望有一个像这样的方法:
Collection method(Collection c) {
    // select some elements from c based on some filter 
    // and return a new collection
}

现在,在这种情况下,Collection 是一些抽象类(例如 C# 中的 IList 或 Java 中的 List),有多个实现。我一直在想,到底什么是生成抽象集合的正确过程?
在方法内部创建具体集合并返回它,这样做可以吗?例如:
Collection method(Collection c) {
   Collection cc = new ConcreteCollection();
   // select some elements from c based on some filter 
   return cc;
}

当然,这会对结果集产生限制,并且如果出现某些原因需要将方法的结果转换为与方法内部使用的不同具体集合,则会产生问题。

或者,使用反射来确定c的实际具体类型并创建该类的实例:

Collection method(Collection c) {
   Collection cc = c.getClass().newInstance();
   // select some elements from c based on some filter 
   return cc;
}

由于某些原因,这对我来说并不是非常“优雅”。我非常感谢您在此事上提供一些见解。

7个回答

4
(关于Java),你返回Collection(一个接口)而不是具体类型(如ArrayList)的原因是告诉用户他们不必在意实际使用的具体类型。这样就可以自由选择适合你的库/ API的类型。如果你要强制使用特定的具体类,则应返回该具体类,而不是接口。因此,他们不应将你的返回类型转换为除Collection之外的任何其他类型。请参见“何时应返回接口,何时应返回具体类?”

事实上,我一直在问自己“为什么你要将它转换为其他东西呢?”在这种情况下,第一个版本似乎应该没问题。 - Tudor

3

在Java中,实际上有一些很好的例子可以说明如何在java.util.Collections类中实现此操作。与其获取一个Collection并返回一个Collection,关键方法需要获取两个集合,即“src”和“dest”。例如,看一下copy方法的签名:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

这将实例化目标列表的责任放在调用方身上。

当您想创建一个作用于src集合并将结果放入目标集合(而不是列表)的方法时,我认为您可以做同样的事情。

我同意Matthew Farwell的答案,您可能只需要返回接口并利用它,但在您确实需要使用特定实现类的情况下,您可以像Collections类一样执行。


嗯...我喜欢这个建议。 - Tudor

1

您可以采取的一种方法是创建一个实现了Collection接口的类,通过委托调用原始的Collection。这样可以将对大型Collection的过滤操作推迟到需要显式读取元素的时候,从而避免了潜在的高昂开销,并节省了内存。

示例

public interface Filter<T> {
  boolean include(T t);
}

public class FilterCollection<T> implements Collection<T> {
  private final Collection<T> orig;
  private final Filter<T> filter;

  public FilterCollection(Collection<T> orig, Filter<T> filter) {
    this.orig = orig;
    this.filter = filter;
  }

  public int size() {
    int sz = 0;

    for (T t : orig) {
      if (filter.include(t)) {
        ++sz;
      }
    }

    return sz;
  }

  public boolean contains(Object o) {
    return o instanceof T && filter.include((T) o) && orig.contains(o);
  }

  public boolean add(T t) {
    if (!filter.include(t)) {
      throw new IllegalArgumentException("Element lies outside filter bounds.");
    }

    orig.add(t);
  }
}

0

调用者应该假设返回的是指定类型的集合。

而不应该直接将其复制到所需的类型,或者传递所需的类型。

例如:

Set<T> set2 = new HashSet<T>(filter(set));
List<T> list2 = new ArrayList<T>(filter(list));

或者

filter(set2, set); // the target collection is passed.
filter(list2, list);

0
据我所理解,您想知道如何创建一个接受泛型列表并返回另一个修改后的泛型列表的方法。
因此,我的建议是使用一个抽象类型来实现修改其状态的方法。
IList<object> list = new List<object>();

list.Add(new object());
list.Remove(obj);

或者如上所示,实例化一个实现了 IList(或其 Java 等效物)的列表,使用此实例并将结果作为 IList 返回。

编辑

如果您想要从列表中过滤出一些项目到新列表中,泛型 可以帮助实现(我不知道 Java 中是否存在此功能)。

    public IList<T> Filter<T>(IList<T> list)
    {
        var result = new List<T>();
        result.Add(list[0]); // Or whatever filtering method
        return result;
    }

0
关于 ConcreteCollection 的问题,它是绝对允许的。
对于预期具有不同具体集合的问题,有几种解决方法:

更改方法的返回类型。例如:

ConcreteCollection method(Collection c){
    ConcreteCollection cc=new ConcreteCollection
    for(Object x: c){
        //do something
    }
    return cc
}

利用多态性。例如:

Collection x=method(c)
x.add(new Object) //add is a method defined within the abstract Collection

使用一些工具来转换类型。例如:
LinkedList h=Collections.toLinkedList(method(c))

希望我的回答有所帮助。^^

0
如果您希望您的方法能够接受尽可能多种不同的集合类型,并且您想确保结果与输入的实现类型相同,那么您可能希望使用一个void方法,直接修改提供的集合。例如:
import com.google.common.base.Predicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class Testy {

    private static <T> void filter(Iterable<T> collection, Predicate<T> filter) {
        Iterator<T> iterator = collection.iterator();
        while (iterator.hasNext()) {
            if (!filter.apply(iterator.next())) { // Condition goes here
                iterator.remove();
            }
        }
    }

    public static void main(String... args) {
        List<String> list = new ArrayList<String>();
        list.addAll(Arrays.asList("A", "B", "C", "D"));

        filter(list, new Predicate<String>() { // Anonymous filter (predicate)
            @Override public boolean apply(String input) {
                return input.equals("B");
            }
        });

        System.out.println(list); // Prints ["B"]
    }

}

辅助方法filter接受一个Iterable,这是迭代某些东西所需的最简单类型。将过滤器应用于每个元素,如果谓词(过滤器)返回false,则使用Iterator.remove()从底层集合中删除该元素。

这里的Predicate<T>接口来自Google。如果您不想导入它,可以轻松编写自己的接口。唯一需要的方法是apply(T),它返回一个布尔值。或者,直接在循环内部编写条件并摆脱第二个参数。

如果您的原始集合是可变的,并且不希望保留任何中间结果,则此方法是最有效的。

另一个选择是使用Google Collections Collections2.filter(Collection<E>, Predicate<E>),它返回与您的问题中相同的Collection<E>。同样,Iterables类将执行相同的操作,但创建惰性可迭代对象,其中仅在实际进行迭代时才应用过滤器。


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