假设我有一个未知类型的集合。我想要做的是流式传输,在流上做一些事情,并将其收回到与我的原始集合相同的集合类型。例如:
Collection<? extends Integer> getBigger(Collection<? extends Integer> col, int value) {
return col.stream().filter(v -> v > value).collect(????);
}
这个不完整的代码示例的想法是,如果List
属于col
类(或其任何子类),则返回List
,Set
if {{ 1}}属于col
类等...这里的流的方法名称和实际操作并不重要,我只是为了说明我的问题而指定它们。
那么,有可能吗?
答案 0 :(得分:16)
如果不违反构建Java流框架的原则,就不可能实现。它完全违反了从物理表示中抽象出流的想法。
批量数据操作的顺序在管道中,见下图:
该流与Schrödinger's cat类似 - 在您调用终端操作之前,它不会实现。流处理完全是抽象的,并与原始流源分离。
如果您希望在原始数据存储中使用如此低级别的工作,请不要因为简单地避开流而感到羞耻。它们只是一种工具,而不是任何神圣的东西。通过引入流,Good Good Collections仍然像它们一样好,具有内部迭代的附加值 - 新的Iterable.forEach()方法。
添加以满足您的好奇心:))
接下来是可能的解决方案。我自己并不喜欢它,而且我无法解决那里的所有泛型问题,但它有有限制。
这个想法是创建一个收集器返回与输入集合相同的类型。但是,并非所有集合都提供了一个无效的构造函数(没有参数),如果没有它,Class.newInstance()方法就不起作用。还有lambda表达式中检查异常的尴尬问题。 (在这个很好的答案中提到:https://stackoverflow.com/a/22919112/2886891)
public Collection<Integer> getBiggerThan(Collection<Integer> 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<? extends Collection> clazz = col.getClass();
System.out.println("Input collection type: " + clazz);
final Supplier<Collection<Integer>> 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<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
final Collection<Integer> arrayList = new ArrayList<>(numbers);
final Collection<Integer> arrayList2 = getBiggerThan(arrayList, 6);
System.out.println(arrayList2);
System.out.println(arrayList2.getClass());
System.out.println();
final Collection<Integer> set = new HashSet<>(arrayList);
final Collection<Integer> 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<Integer> numbers2 = getBiggerThan(numbers, 6);
}
答案 1 :(得分:3)
这里有两个问题:(1)输入的运行时类型(类)及其结果,以及(2)输入的编译时类型及其结果。
对于(1),它可能看起来很奇怪,但一般来说,在Java中不可能创建任意类的实例的副本。如果类没有可访问的no-arg构造函数或者它是不可变的,那么使用getClass().newInstance()
可能不起作用。对象可能也不是Cloneable
。因此,调用者需要传递一个供应商,该供应商负责创建正确结果类的实例。
对于(2),适当剂量的仿制药可以在编译时使这种类型安全。
<T extends Comparable<T>, C extends Collection<T>> C getBigger(
C col, T value, Supplier<C> supplier) {
return col.stream()
.filter(v -> v.compareTo(value) > 0)
.collect(Collectors.toCollection(supplier::get));
}
请注意,类型参数Comparable<T>
上有T
的限制,因此调用者只能传递一组可比较的东西。这允许我们使用compareTo
来比较值。我们还使用Collectors.toCollection
方法并将供应商的get
方法传递给它。
使用示例:
List<Integer> input1 = Arrays.asList(1, 4, 9, 13, 14, 22);
List<Integer> filtered1 = getBigger(input1, 10, ArrayList::new);
Set<String> input2 = new HashSet<>();
input2.add("foo");
input2.add("bar");
input2.add("baz");
input2.add("qux");
Set<String> filtered2 = getBigger(input2, "c", HashSet::new);
答案 2 :(得分:0)
由于已知实际的基础类型只是您方法的被调用者,因此他们应该collect
对Collection
他们想要的任何类型(例如使用Collectors.toCollection(CustomCollectionType::new);
)负责。所以你的方法应该返回Stream
。可能需要Collection
或Stream
,具体取决于方便性。