从抽象集合生成抽象集合

本文关键字:抽象 集合 | 更新日期: 2023-09-27 18:16:07

这个问题已经困扰我一段时间了。抽象地说,无论使用哪种语言,通常情况下都需要这样的方法:

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

出于某种原因,这对我来说似乎不是很"优雅"。如果你能对这件事有所了解,我将不胜感激。

从抽象集合生成抽象集合

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

如果你要强制一个特定的具体类,那么你应该返回那个具体类,而不是接口。

所以,它们不应该将你的返回类型强制转换为Collection以外的任何类型。看到什么时候返回接口,什么时候返回具体类?

在Java中,实际上有一些很好的例子说明如何在java.util.Collections类中做到这一点。关键方法不是接受一个集合并返回一个集合,而是接受两个集合,"src"answers"dest"。例如,看看copy方法的签名:

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

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

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

我同意Matthew Farwell的回答,你可能只是想返回接口并利用它,但是当你真的需要使用特定的实现类时,你可以像Collections类那样做。

您可以采用的一种方法是创建一个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);
  }
}

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

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

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

对于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))

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

据我所知,您想知道如何制作一个接受泛型列表并返回另一个修改后的泛型列表的方法。

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

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

如果你想让你的方法接受尽可能多的不同的集合类型,并且你想确保结果与你输入的是相同的实现类型,你可能想使用一个直接修改提供的集合的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),它返回一个布尔值。或者直接在循环中写入条件,并去掉第二个参数。

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

另一个选择是使用谷歌集合Collections2.filter(Collection<E>, Predicate<E>),它返回一个Collection<E>就像在你的问题。类似地,Iterables类将做同样的事情,但创建延迟可迭代对象,其中过滤器仅在实际执行迭代时应用。