从抽象集合中生成抽象集合

时间:2011-11-30 12:31:26

标签: c# java oop collections

这个问题一直困扰着我。抽象地说,无论语言如何,通常都会出现这样的方法:

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

由于某些原因,这似乎并不是非常优雅的#34;对我来说。我非常感谢对此事的一些见解。

7 个答案:

答案 0 :(得分:4)

(说到java)。您返回Collection(接口)而不是具体类型(例如ArrayList)的原因是您告诉用户他们不应该关心所使用的实际具体类型是什么。这样您就可以自由选择适合您的库/ api的类型。

如果您正在执行特定的具体类,那么您应该返回该具体类,而不是接口。

因此,他们不应该将您的返回类型转换为除Collection之外的任何其他内容。看到 When should I return the Interface and when the concrete class?

答案 1 :(得分:3)

在Java中,实际上有一些很好的例子说明如何在java.util.Collections类中执行此操作。关键方法不是采用Collection并返回Collection,而是采用两个集合,“src”和“dest”。例如,查看copy方法的签名:

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

这使得在调用者上实例化目的地列表的责任。

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

我同意Matthew Farwell的回答,你可能只想返回界面并利用它,但是当你确实需要使用特定的实现类时,你可以像{{1}一样使用它。这样做。

答案 2 :(得分: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);
  }
}

答案 3 :(得分:0)

调用者应该假定返回给定类型的Collection。

相反,它应该复制到所需的类型或传递所需的类型。

e.g。

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

答案 4 :(得分:0)

据我所知,您想知道如何创建一个接受通用列表的方法并返回另一个修改过的通用列表。

所以,我的建议是使用一个抽象类型来实现修改其状态的方法。

IList<object> list = new List<object>();

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

或如上所示,实例化一个实现IList(或Java等价物)工作的列表,并将结果作为IList

返回

修改

如果要将列表中的某些项目过滤为新项目, generics 可以提供帮助(我不知道Java中是否存在此功能)。

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

答案 5 :(得分: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))

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

答案 6 :(得分: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收藏集Collections2.filter(Collection<E>, Predicate<E>),它会像您的问题一样返回Collection<E>。类似地,Iterables类将执行相同的操作,但创建惰性迭代,其中仅在实际执行迭代时应用过滤器。