将流收集回到相同的集合类型中

假设我有一个未知类型的集合。 我想要做的是流式传输,在流上做一些事情,并将其收回到与我的原始集合相同的集合类型。 例如:

Collection getBigger(Collection col, int value) { return col.stream().filter(v -> v > value).collect(????); } 

这个不完整的代码示例的想法是返回一个List如果colList类(或它的任何子类), Set如果colSet类,等等……这里的流的方法名称和实际操作是不重要,我已经指定它们只是为了说明我的问题。 那么,有可能吗?

如果不违反构建Java流框架的原则,就不可能实现。 它完全违反了从物理表示中抽取流的想法。

批量数据操作的顺序进入管道 ,请参见下图: 管道:一系列批量数据操作

这条小流在某种程度上类似于薛定谔的猫 – 在你调用终端操作之前它没有实现。 流处理完全是抽象的,并与原始流源分离。

管道作为黑匣子

如果您希望在原始数据存储中使用如此低级别的工作,请不要因为简单地避开流而感到羞耻。 它们只是一种工具,而不是任何神圣的东西。 通过引入流,Good Old Collections仍然像它们一样好,具有内部迭代的附加值 – 新的Iterable.forEach()方法。


添加以满足您的好奇心:)

可能的解决方案如下。 我自己并不喜欢它,而且我无法解决那里的所有generics问题,但它有局限性

这个想法是创建一个收集器返回与输入集合相同的类型。 但是,并非所有集合都提供了一个无效的构造函数(没有参数),没有它, Class.newInstance()方法就不起作用。 还有lambda表达式中检查exception的尴尬问题。 (在这个很好的答案中提到: https : //stackoverflow.com/a/22919112/2886891 )

 public Collection getBiggerThan(Collection col, int value) { // Collection below is an example of one of the rare appropriate // uses of raw types. getClass returns the runtime type of col, and // at runtime all type parameters have been erased. @SuppressWarnings("rawtypes") final Class clazz = col.getClass(); System.out.println("Input collection type: " + clazz); final Supplier> supplier = () -> { try { return clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException( "A checked exception caught inside lambda", e); } }; // After all the ugly preparatory code, enjoy the clean pipeline: return col.stream() .filter(v -> v > value) .collect(supplier, Collection::add, Collection::addAll); } 

正如您所看到的,它通常起作用,假设您的原始集合提供了一个无效的构造函数。

 public void test() { final Collection numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); final Collection arrayList = new ArrayList<>(numbers); final Collection arrayList2 = getBiggerThan(arrayList, 6); System.out.println(arrayList2); System.out.println(arrayList2.getClass()); System.out.println(); final Collection set = new HashSet<>(arrayList); final Collection set2 = getBiggerThan(set, 6); System.out.println(set2); System.out.println(set2.getClass()); System.out.println(); // This does not work as Arrays.asList() is of a type // java.util.Arrays$ArrayList which does not provide a nullary constructor final Collection numbers2 = getBiggerThan(numbers, 6); } 

这里有两个问题:(1)输入的运行时类型(类)及其结果,以及(2)输入的编译时类型及其结果。

对于(1),它可能看起来很奇怪,但一般来说,在Java中不可能创建任意类的实例的副本。 如果类没有可访问的no-arg构造函数或者它是不可变的,则使用getClass().newInstance()可能不起作用。 该对象也可能不是Cloneable 。 因此,调用者需要传递一个供应商,该供应商负责创建正确结果类的实例。

对于(2),适当剂量的仿制药可以在编译时使这种类型安全。

 , C extends Collection> C getBigger( C col, T value, Supplier supplier) { return col.stream() .filter(v -> v.compareTo(value) > 0) .collect(Collectors.toCollection(supplier::get)); } 

请注意,类型参数T上存在Comparable的边界,因此调用者仅限于传递可比较的东西的集合。 这让我们可以使用compareTo来比较这些值。 我们还使用Collectors.toCollection方法并将供应商的get方法传递给它。

使用示例:

 List input1 = Arrays.asList(1, 4, 9, 13, 14, 22); List filtered1 = getBigger(input1, 10, ArrayList::new); Set input2 = new HashSet<>(); input2.add("foo"); input2.add("bar"); input2.add("baz"); input2.add("qux"); Set filtered2 = getBigger(input2, "c", HashSet::new); 

由于实际的底层类型只知道您的方法的被调用者,因此他们应该将其collect到他们想要的任何类型的Collection (例如,使用Collectors.toCollection(CustomCollectionType::new); )。 所以你的方法应该返回Stream 。 它可以采取CollectionStream取决于方便。