Java 9集合工厂的用法

在List.of()或Collections.emptyList()和List.of(…)或Collections.unmodifiableList()中给出的注释和答案的上下文中,我提出了以下两条经验法则(也适用于SetMap工厂)。

  1. 不要替换所有出现的事件

继续使用Collections.emptyList()以提高可读性,例如初始化惰性字段成员时:

 class Bean { private List beans = Collection.emptyList(); public List getBeans() { if (beans == Collections.EMPTY_LIST) { beans = new ArrayList(); } return beans; } } 
  1. 使用新工厂作为方法参数构建器

当使用List参数调用可执行文件时,使用新工厂List.of()和变体作为快速和较少类型的版本。 以下是我目前的替代模式:

 Collections.emptyList() --> List.of() Collections.singletonList(a) --> List.of(a) Arrays.asList(a, ..., z) --> List.of(a, ..., z) 

Collections.indexOfSubList的虚构用法中,以下行

 Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.emptyList()); Collections.indexOfSubList(Arrays.asList(1, 2, 3), Collections.singletonList(1)); Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1)); Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(2, 3)); Collections.indexOfSubList(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3)); 

会读

 Collections.indexOfSubList(List.of(1, 2, 3), List.of()); Collections.indexOfSubList(List.of(1, 2, 3), List.of(1)); Collections.indexOfSubList(List.of(1, 2, 3), List.of(1)); Collections.indexOfSubList(List.of(1, 2, 3), List.of(2, 3)); Collections.indexOfSubList(List.of(1, 2, 3), List.of(1, 2, 3)); 

你(dis-)同意吗?

通常,使用新工厂对于新代码是安全的,其中没有现有代码依赖于现有集合的行为。

有几个原因,新的集合工厂不是用于使用现有API初始化集合的代码的替代品。 显然,不变性是最突出的原因之一; 如果你以后需要修改集合,那显然不可能是不可变的! 但也有其他原因,其中一些非常微妙。

有关使用新API替换现有API的示例,请参阅JDK-8134373 。 审核主题在这里: Part1 Part2 。

这是问题的概述。

数组包装与复制。 有时您有一个数组,例如varargs参数,并且您希望将其作为列表进行处理。 有时Arrays.asList是最合适的东西,因为它只是一个包装器。 相比之下, List.of创建了一个副本,这可能是浪费。 另一方面,调用者仍然具有包装数组的句柄并且可以修改它,这可能是一个问题,因此有时您需要支付复制它的费用,例如,如果您想保留对它的引用在实例变量中列出。

哈希集合迭代命令。 新的Set.ofMap.of结构随机化它们的迭代顺序。 HashSetHashMap的迭代顺序是未定义的,但实际上它相对稳定。 代码可能会对迭代顺序产生无意的依赖。 切换到新的集合工厂可能会将旧代码暴露给迭代顺序依赖项,从而暴露出潜在的错误。

禁止无效。 新集合完全禁止空值,而常见的非并发集合( ArrayListHashMap )允许它们。

序列化格式。 新集合具有与旧集合不同的序列化格式。 如果集合是序列化的,或者它存储在序列化的其他类中,则序列化输出将有所不同。 这可能是也可能不是问题。 但是,如果您希望与其他系统进行互操作,则可能会出现问题。 特别是,如果将新集合的序列化forms传输到Java 8 JVM,它将无法反序列化,因为Java 8上不存在新类。

严格的Mutator方法行为。 新集合是不可变的,因此当调用mutator方法时,它们当然会抛出UnsupportedOperationException 。 但是,有一些边缘情况,其中行为在所有集合中不一致。 例如,

  Collections.singletonList("").addAll(Collections.emptyList()) 

什么都不做,而

  List.of("").addAll(Collections.emptyList()) 

将投掷UOE。 一般来说,即使没有发生实际的突变,新的集合和不可修改的包装器在任何调用mutator方法时都会严格地抛出UOE。 其他不可变集合(例如来自Collections.empty*Collections.singleton*仅在发生实际变异时才会抛出UOE。

重复。 新的SetMap工厂拒绝重复的元素和键。 如果您使用常量列表初始化集合,这通常不是问题。 实际上,如果一个常量列表有重复,那可能就是一个bug。 这可能是一个问题,当允许调用者传入元素的集合或数组(例如,varags)时。 如果调用者传入重复项,则现有API将无声地省略重复项,而新工厂将抛出IllegalArgumentException 。 这是一种可能会影响来电者的行为变化。


这些问题都不是致命问题,但在改进现有代码时,您应该注意行为差异。 不幸的是,这意味着用新的收集工厂大规模替换现有的呼叫可能是不明智的。 可能需要在每个站点进行一些检查,以评估行为变化的任何潜在影响。

(IM)可变性

首先,重要的是要注意集合工厂返回不可变的变体。 不幸的是,这不会在类型系统中显示,因此您必须手动/精神地跟踪它。 这已经禁止了一些可能值得的替换,因此它必须在您的规则列表中变为0 。 🙂

例如,创建稍后由其他代码修改的种子元素集合可能如下所示:

 private final Set commonLetters = initialCommonLetters() private static Set initialCommonLetters() { Set letters = new HashSet<>(); letters.add("a"); letters.add("e"); return letters; } 

简单地写commonLetters = Set.of("a", "e");会很棒commonLetters = Set.of("a", "e"); 但这可能会破坏其他代码,因为返回的集合是不可变的。

常量

(im)可变性讨论立即导致常数。 这是介绍它们的主要原因! 您需要静态初始化程序块来创建COMMON_LETTERS常量的日子已经一去不复返了。 因此,这将是我首先查看用例的地方。

更换

正如你所说,似乎没有理由开始替换对Collections::empty...Collections::singleton...Arrays::asList只是为了它的乐趣。 但是,一旦我开始在类中使用新方法,我会做的就是替换旧的变体,让代码依赖于更少的概念,使理解变得更容易。

偏爱

最后一个参数也可以应用于of()变体。 虽然Collections::empty...Collections::singleton...对于他们的意图有点清楚,但我倾向于总是使用,无论你有多少个参数,通过编写代码来抵消这种优势,整体而言,使用较少的概念。

我认为没有理由继续使用Arrays::asList