Java 是否允许/建议重复使用收集器?

Java 是否允许/建议重复使用收集器?,java,collections,java-8,java-stream,Java,Collections,Java 8,Java Stream,我的代码中有很多地方是这样的: someStream.collect(Collectors.toList()) 其中Collectors.toList()在每次使用时创建一个新收集器 这就引出了一个问题,即是否允许并建议采取以下措施: private final static Collector<…> TO_LIST = Collectors.toList() private final static Collector<Object, ?, List<Object&

我的代码中有很多地方是这样的:

someStream.collect(Collectors.toList())
其中
Collectors.toList()
在每次使用时创建一个新收集器

这就引出了一个问题,即是否允许并建议采取以下措施:

private final static Collector<…> TO_LIST = Collectors.toList()
private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList();


public void test() {
    // Any method could do this (no idea why but it should be possible).
    TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH);
}
当需要收集器时

由于收集器是无状态的,只是函数和特性的集合,我认为它应该可以工作,但是oth,
collectors.toList()
会在每次调用时创建一个新的
CollectorImpl

重复使用收集器的缺点是什么?

我认为这更像是一个风格问题,但让我们思考一下:

  • 不使用这样的常量收集器对象似乎是常见的实践。从这个意义上说:这样做可能会让一些读者感到惊讶,而让读者感到惊讶很少是一件好事
  • 然后:很少有代码可以“复制”到周围(并且可能不应该这样做以避免代码重复);但是:指向一个不同的收集器对象可能会使您更难重新考虑或重新使用流结构
  • 除此之外:你自己说;收集器的重复使用取决于无状态实现。因此,您使自己依赖于任何无状态的实现。可能不是问题;但也许要记住一个风险
  • 可能更重要的是:表面上看,你的想法似乎是一个很好的优化手段。但是很好;当您担心使用流的“性能影响”时,那么最终收集器的单个对象创建将“无法削减”
我的意思是:如果你担心“浪费”性能;您更愿意查看使用流的每一行代码,以确定该流是否与“足够”的对象一起工作,从而首先证明流的使用是合理的。这些流的开销相当大


长话短说:java社区尚未找到流的“标准最佳实践”;因此,我(个人)现在有两分钱:喜欢“每个人”都在使用的模式——避免做自己的事情。特别是当它与性能相关时。

使用单个静态对象代替动态创建的对象的经典问题是可变性。快速扫描Java 8源代码会突出显示
Set
字段,这可能是一个问题

显然,某些代码可能在某个地方执行以下操作:

private final static Collector<…> TO_LIST = Collectors.toList()
private final static Collector<Object, ?, List<Object>> TO_LIST = Collectors.toList();


public void test() {
    // Any method could do this (no idea why but it should be possible).
    TO_LIST.characteristics().add(Collector.Characteristics.IDENTITY_FINISH);
}
private final static Collector TO_LIST=Collectors.toList();
公开无效测试(){
//任何方法都可以做到这一点(不知道为什么,但这应该是可能的)。
添加(Collector.characteristics.IDENTITY\u FINISH);
}
这可能会全局性地将每次使用
的功能更改为\u LIST
,这可能会产生非常模糊的bug


所以,不要

由于
收集器
基本上是四个函数和特征标志的容器,因此重用它没有问题,但也很少有任何优势,因为这样一个轻量级对象对内存管理的影响可以忽略不计,如果优化器不完全消除的话

不重用
收集器
s的主要原因是,您不能以类型安全的方式进行重用,这与内置的
收集器
相同。为任意键入的
列表
提供收集器时,您将需要取消选中的操作来始终分发相同的
收集器
实例。如果将
收集器
存储在正确类型的变量中,以便在不进行未检查操作的情况下使用,则只能将其用于一种类型的
列表
s,以继续该示例


Collections.emptyList()
等方面,JRE开发人员采取了不同的方式,但是常量
EMPTY\u LIST
EMPTY\u MAP
EMPTY\u SET
在引入泛型之前就已经存在了,而且我认为它们比少数可缓存的
收集器更通用,这只是其他三十多个内置收集器中的四个特殊情况,这些收集器由于其函数参数而无法缓存。由于函数参数通常通过lambda表达式实现,而lambda表达式生成未指定标识/相等的对象,因此将它们映射到收集器实例的缓存将具有不可预测的效率,但很可能远低于内存管理器处理临时实例的效率。

对于库来说,提供一种工厂方法来获取有用对象是一种很好的做法。由于库提供了这样一种方法:
Collectors.toList()
,因此最好还是让库在每次请求对象时决定是否创建新实例,而不是篡改库,从而降低可读性,并在实现更改时冒着未来问题的风险


这将作为一个支持性的论据添加到GhostCat和Holger的答案中:)

只是一个小小的旁注,@Holger在他的答案中所说的优化器是智能的,完全可以替换该结构,这被称为
标量替换。当方法中使用的对象被解构,并且其字段像普通局部变量一样被
堆栈分配时。因此,生成的
收集器
可能不会在JVM级别被视为对象本身。这将在JIT时间发生

这将是过早优化的情况。对象创建非常便宜。在普通笔记本电脑上,我希望每秒能创建10-50万个对象。考虑到这些数字,整个练习变得毫无意义

很好的尝试,但这将抛出一个
java.lang.UnsupportedOperationException
。通常,这种JRE方法返回
集合