Java 9个集合工厂的使用情况
Usage of Java 9 collection factories
根据 and 给出的评论和答案,我得出了以下两条经验法则(相应地也适用于 Set
和 Map
工厂)。
- 不要替换所有出现的地方
继续使用 Collections.emptyList()
以提高可读性,例如初始化惰性字段成员,如:
class Bean {
private List<Bean> beans = Collection.emptyList();
public List<Bean> getBeans() {
if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }
return beans;
}
}
- 使用新工厂作为方法参数构建器
在使用 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));
你(不)同意吗?
(Im)可变性
首先,重要的是要注意集合工厂return 不可变变体。不幸的是,这不会显示在类型系统中,因此您必须手动/精神上跟踪它。这已经禁止了一些可能有价值的替换,因此它必须在您的规则列表中变为 0.。 :)
例如,创建稍后由其他代码修改的种子元素集合可能如下所示:
private final Set<String> commonLetters = initialCommonLetters()
private static Set<String> initialCommonLetters() {
Set<String> letters = new HashSet<>();
letters.add("a");
letters.add("e");
return letters;
}
简单地编写 commonLetters = Set.of("a", "e");
会很棒,但这可能会破坏其他代码,因为 returned 集是不可变的。
常量
(im) 可变性讨论立即导致常量。这是介绍他们的主要原因!需要静态初始化程序块来创建 COMMON_LETTERS
常量的日子已经一去不复返了。因此,这将是我首先寻找用例的地方。
正在替换
正如您所说,似乎没有理由仅仅为了好玩而开始替换对 Collections::empty...
、Collections::singleton...
或 Arrays::asList
的调用。不过,我会做的是,一旦我开始在 class 中使用新方法,我就会替换旧的变体,让代码依赖更少的概念,从而更容易理解。
偏好
最后一个参数通常也适用于 of()
变体。虽然 Collections::empty...
和 Collections::singleton...
对他们的意图更清楚一些,但我稍微倾向于说总是使用 of
,无论你有多少参数,通过编写代码来抵消这种优势,因为一个整体,使用较少的概念。
我认为没有理由继续使用 Arrays::asList
。
一般来说,新工厂的使用对于新代码是安全的,因为没有现有代码依赖于现有集合的行为。
新的集合工厂不能替代使用现有 API 初始化集合的代码有多种原因。显然,不变性是最突出的原因之一。如果以后需要修改集合,显然不能不可变!但也有其他原因,其中一些非常微妙。
有关用新 API 替换现有 API 的示例,请参阅 JDK-8134373. The review threads are here: Part1 Part2。
这是问题的概要。
数组包装与复制。有时你有一个数组,例如可变参数,并且您想将其作为列表处理。有时 Arrays.asList
在这里是最合适的,因为它只是一个包装器。相比之下,List.of
创建一个副本,这可能是一种浪费。另一方面,调用者仍然拥有包装数组的句柄并且可以修改它,这可能是个问题,所以有时你想付出复制它的代价,例如,如果你想保留对在实例变量中列出。
散列集合迭代顺序。 新的 Set.of
和 Map.of
结构随机化了它们的迭代顺序。 HashSet
和 HashMap
的迭代顺序是不确定的,但实际上它是相对稳定的。代码可能会无意中产生对迭代顺序的依赖。切换到新的集合工厂可能会将旧代码暴露给迭代顺序依赖项,从而暴露出潜在的错误。
禁止空值。新集合完全禁止空值,而常见的非并发集合(ArrayList
、HashMap
)允许空值。
序列化格式。新合集的序列化格式与旧合集不同。如果集合是序列化的,或者它存储在其他一些已序列化的 class 中,则序列化输出将不同。这可能是也可能不是问题。但是,如果您希望与其他系统互操作,这可能是个问题。特别是,如果您将新集合的序列化形式传输到 Java 8 JVM,它将无法反序列化,因为新的 classes 在 Java 8 上不存在。
严格的 Mutator 方法行为。 新集合是不可变的,因此当调用 mutator 方法时它们当然会抛出 UnsupportedOperationException
。然而,在某些边缘情况下,所有集合的行为并不一致。例如,
Collections.singletonList("").addAll(Collections.emptyList())
什么都不做,而
List.of("").addAll(Collections.emptyList())
会抛出UOE。一般而言,新集合和不可修改的包装器始终严格地在对更改器方法的任何调用中抛出 UOE,即使不会发生实际更改也是如此。其他不可变集合,例如来自 Collections.empty*
和 Collections.singleton*
的集合,只有在发生实际突变时才会抛出 UOE。
重复。 新的 Set
和 Map
工厂拒绝重复的元素和键。如果您使用常量列表初始化集合,这通常不是问题。实际上,如果常量列表有重复项,则可能是错误。当允许调用者传入元素的集合或数组(例如,varags)时,这可能是一个问题。如果调用者传入重复项,现有 API 将默默地忽略重复项,而新工厂将抛出 IllegalArgumentException
。这是一种可能会影响来电者的行为变化。
None 这些问题是致命问题,但它们是您在改造现有代码时应该注意的行为差异。不幸的是,这意味着用新的收集工厂大量替换现有调用可能是不明智的。可能有必要在每个站点进行一些检查,以评估行为变化的任何潜在影响。
根据 Set
和 Map
工厂)。
- 不要替换所有出现的地方
继续使用 Collections.emptyList()
以提高可读性,例如初始化惰性字段成员,如:
class Bean {
private List<Bean> beans = Collection.emptyList();
public List<Bean> getBeans() {
if (beans == Collections.EMPTY_LIST) { beans = new ArrayList<>(); }
return beans;
}
}
- 使用新工厂作为方法参数构建器
在使用 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));
你(不)同意吗?
(Im)可变性
首先,重要的是要注意集合工厂return 不可变变体。不幸的是,这不会显示在类型系统中,因此您必须手动/精神上跟踪它。这已经禁止了一些可能有价值的替换,因此它必须在您的规则列表中变为 0.。 :)
例如,创建稍后由其他代码修改的种子元素集合可能如下所示:
private final Set<String> commonLetters = initialCommonLetters()
private static Set<String> initialCommonLetters() {
Set<String> letters = new HashSet<>();
letters.add("a");
letters.add("e");
return letters;
}
简单地编写 commonLetters = Set.of("a", "e");
会很棒,但这可能会破坏其他代码,因为 returned 集是不可变的。
常量
(im) 可变性讨论立即导致常量。这是介绍他们的主要原因!需要静态初始化程序块来创建 COMMON_LETTERS
常量的日子已经一去不复返了。因此,这将是我首先寻找用例的地方。
正在替换
正如您所说,似乎没有理由仅仅为了好玩而开始替换对 Collections::empty...
、Collections::singleton...
或 Arrays::asList
的调用。不过,我会做的是,一旦我开始在 class 中使用新方法,我就会替换旧的变体,让代码依赖更少的概念,从而更容易理解。
偏好
最后一个参数通常也适用于 of()
变体。虽然 Collections::empty...
和 Collections::singleton...
对他们的意图更清楚一些,但我稍微倾向于说总是使用 of
,无论你有多少参数,通过编写代码来抵消这种优势,因为一个整体,使用较少的概念。
我认为没有理由继续使用 Arrays::asList
。
一般来说,新工厂的使用对于新代码是安全的,因为没有现有代码依赖于现有集合的行为。
新的集合工厂不能替代使用现有 API 初始化集合的代码有多种原因。显然,不变性是最突出的原因之一。如果以后需要修改集合,显然不能不可变!但也有其他原因,其中一些非常微妙。
有关用新 API 替换现有 API 的示例,请参阅 JDK-8134373. The review threads are here: Part1 Part2。
这是问题的概要。
数组包装与复制。有时你有一个数组,例如可变参数,并且您想将其作为列表处理。有时 Arrays.asList
在这里是最合适的,因为它只是一个包装器。相比之下,List.of
创建一个副本,这可能是一种浪费。另一方面,调用者仍然拥有包装数组的句柄并且可以修改它,这可能是个问题,所以有时你想付出复制它的代价,例如,如果你想保留对在实例变量中列出。
散列集合迭代顺序。 新的 Set.of
和 Map.of
结构随机化了它们的迭代顺序。 HashSet
和 HashMap
的迭代顺序是不确定的,但实际上它是相对稳定的。代码可能会无意中产生对迭代顺序的依赖。切换到新的集合工厂可能会将旧代码暴露给迭代顺序依赖项,从而暴露出潜在的错误。
禁止空值。新集合完全禁止空值,而常见的非并发集合(ArrayList
、HashMap
)允许空值。
序列化格式。新合集的序列化格式与旧合集不同。如果集合是序列化的,或者它存储在其他一些已序列化的 class 中,则序列化输出将不同。这可能是也可能不是问题。但是,如果您希望与其他系统互操作,这可能是个问题。特别是,如果您将新集合的序列化形式传输到 Java 8 JVM,它将无法反序列化,因为新的 classes 在 Java 8 上不存在。
严格的 Mutator 方法行为。 新集合是不可变的,因此当调用 mutator 方法时它们当然会抛出 UnsupportedOperationException
。然而,在某些边缘情况下,所有集合的行为并不一致。例如,
Collections.singletonList("").addAll(Collections.emptyList())
什么都不做,而
List.of("").addAll(Collections.emptyList())
会抛出UOE。一般而言,新集合和不可修改的包装器始终严格地在对更改器方法的任何调用中抛出 UOE,即使不会发生实际更改也是如此。其他不可变集合,例如来自 Collections.empty*
和 Collections.singleton*
的集合,只有在发生实际突变时才会抛出 UOE。
重复。 新的 Set
和 Map
工厂拒绝重复的元素和键。如果您使用常量列表初始化集合,这通常不是问题。实际上,如果常量列表有重复项,则可能是错误。当允许调用者传入元素的集合或数组(例如,varags)时,这可能是一个问题。如果调用者传入重复项,现有 API 将默默地忽略重复项,而新工厂将抛出 IllegalArgumentException
。这是一种可能会影响来电者的行为变化。
None 这些问题是致命问题,但它们是您在改造现有代码时应该注意的行为差异。不幸的是,这意味着用新的收集工厂大量替换现有调用可能是不明智的。可能有必要在每个站点进行一些检查,以评估行为变化的任何潜在影响。