3 回答
TA贡献1798条经验 获得超3个赞
您的方法恰好有效,因为流管道仅包含无状态操作。在这种情况下,顺序流评估可能一次处理一个元素,因此对包装器实例的访问不会重叠,如此处所示。但请注意,这不是保证的行为。
它绝对不适用于像sorted
和这样的有状态操作distinct
。它也不能用于归约操作,因为它们总是必须至少保存两个元素进行处理,其中包括reduce
、min
和max
。在 的情况下collect
,这取决于特定的Collector
。forEachOrdered
由于需要缓冲,因此不适用于并行流。
请注意,即使您使用TheadLocal
创建线程受限包装器,并行处理也会出现问题,因为无法保证在一个工作线程中创建的对象保持在该线程的本地。工作线程可能会在获取另一个不相关的工作负载之前将部分结果移交给另一个线程。
所以这个共享的可变包装器在特定实现的顺序执行中与一组特定的无状态操作一起工作,比如map
, filter
, forEach
, findFirst/Any
, 。all/any/noneMatch
您无法获得 API 的灵活性,因为您必须限制自己,不能将流传递给期望 a 的任意代码,Stream
也不能使用任意Collector
实现。您也没有接口的封装,因为您假设了特定的实现行为。
换句话说,如果你想使用这样一个可变的包装器,你最好使用一个实现特定操作的循环。您确实已经有了这种手动实施的缺点,那么为什么不实施它来获得优势呢。
另一个要考虑的方面是,你从重用这样一个可变包装器中得到了什么。它仅适用于类似循环的用法,在这种情况下,临时对象在应用逃逸分析后可能会被优化掉。在这种情况下,重用对象、延长它们的生命周期实际上可能会降低性能。
当然,对象标量化不是一种有保证的行为。可能存在一些场景,例如超过 JVM 内联限制的长流管道,其中对象不会被删除。但是,临时对象并不一定很昂贵。
这已在此答案中进行了解释。临时对象的分配成本很低。垃圾收集的主要成本是由仍然存在的对象引起的。这些需要遍历,并且在为新分配腾出空间时需要移动这些。临时对象的负面影响是它们可能会缩短垃圾收集轮次之间的时间。但这是分配率和可用分配空间的函数,所以这确实是一个可以通过投入更多 RAM 来解决的问题。更多的 RAM 意味着 GC 周期之间的时间更长,GC 发生时死对象更多,这使得 GC 的净成本更小。
尽管如此,避免过度分配临时对象是一个有效的问题。IntStream
、LongStream
和的存在DoubleStream
表明。但这些是特殊的,因为使用原始类型是使用包装器对象的可行替代方案,而且没有重用可变包装器的缺点。它的不同之处还在于它适用于原始类型和包装类型在语义上等同的问题。相反,您想解决操作需要包装器类型的问题。对于原始流也适用,当您需要对象来解决问题时,没有办法绕过装箱,这将为不同的值创建不同的对象,而不是共享可变对象。
因此,如果您同样遇到一个问题,即存在语义等效的包装对象避免替代方案而没有实质性问题,例如在可行Comparator.comparingInt
的情况下使用而不是Comparator.comparing
,您可能仍然会喜欢它。但只有那时。
简而言之,大多数时候,这种对象重用的节省(如果有的话)并不能证明其缺点。在特殊情况下,在有益且重要的情况下,使用循环或完全控制的任何其他构造可能会更好,而不是使用Stream
.
TA贡献1836条经验 获得超3个赞
你可以有一些方便的功能,也可以有线程安全的版本来并行工作。
Function<T,U> threadSafeReusableWrapper(Supplier<U> newWrapperInstanceFn, BiConsumer<U,T> wrapFn) {
final ThreadLocal<T> wrapperStorage = ThreadLocal.withInitial(newWrapperInstanceFn);
return item -> {
T wrapper = wrapperStorage.get();
wrapFn.consume(wrapper, item);
return wrapper;
}
}
Function<T,U> reusableWrapper(U wrapper, BiConsumer<U,T> wrapFn) {
return item -> {
wrapFn.consume(wrapper, item);
return wrapper;
};
}
list.stream()
.map(reusableWrapper(new Wrapper(), Wrapper::setSource))
.forEach( w -> processWrapper(w) );
list.stream()
.map(threadSafeReusableWrapper(Wrapper::new, Wrapper::setSource))
.parallel()
.forEach( w -> processWrapper(w) );
但是,我认为不值得。这些包装器是短暂的,所以不太可能离开年轻一代,所以会很快被垃圾收集。不过,我认为这个想法值得用微基准库 JMH检查
TA贡献1936条经验 获得超6个赞
尽管这是可能的,但引用流外的对象会使代码在风格上功能性降低。可以使用辅助函数简单地实现一个封装得更好的非常接近的等价物:
public class Context {
private static final Wrapper WRAPPER = new Wrapper();
private static void helper(Source source) {
WRAPPER.setSource(source);
processWrapper(WRAPPER);
}
public static void main(String[] args) {
List<Source> list = Arrays.asList(new Source("Foo"), new Source("Baz"), new Source("Bar"));
list.stream().forEach(Context::helper);
}
添加回答
举报