JDK9在 List、Set、Map 等,都提供了 of() 方法,表面上看来,它们似乎只是建立 List、Set、Map 实例的便捷方法,例如:
List<String> nameLt = List.of("Justin", "Monica");
nameLt ==> [Justin, Monica]
Set<String> nameSet = Set.of("Justin", "Monica");
nameSet ==> [Monica, Justin]
Map<String, Integer> scoreMap = Map.of("Justin", 95, "Monica", 100);
scoreMap ==> {Justin=95, Monica=100}
比较特别的是 Map.of(),它是采取 Map.of(K1,V1,K2,V2) 的方式建立,也就是键、值、键值的方式来指定。List、Set、Map 的 of() 方法建立的是不可变的,不可添加新的元素,否则会抛出 UnsupportedOperationException,例如:
nameLt.add("Irene");
| java.lang.UnsupportedOperationException thrown:
| at ImmutableCollections.uoe (ImmutableCollections.java:70)
| at ImmutableCollections$AbstractImmutableList.add (ImmutableCollections.java:76)
| at (#5:1)
nameSet.add("Irene");
| java.lang.UnsupportedOperationException thrown:
| at ImmutableCollections.uoe (ImmutableCollections.java:70)
| at ImmutableCollections$AbstractImmutableSet.add (ImmutableCollections.java:280)
| at (#6:1)
scoreMap.put("Irene", 100);
| java.lang.UnsupportedOperationException thrown:
| at ImmutableCollections.uoe (ImmutableCollections.java:70)
| at ImmutableCollections$AbstractImmutableMap.put (ImmutableCollections.java:557)
| at (#7:1)
那么可以避免方才Collections的unmodifiableXXX()上提到之问题吗?这些of()方法多数都是采可变长度参数的方式定义,而是重载了多个不同参数个数的版本,以List的of()方法为例:
在参数少于10个的情况下,会使用对应个数的of()版本,因而不会有参考原List实例的问题,至于那个of(E…elements)版本,内部并不会直接参考原本elements参考的实例,而是建立一个新数组,然后对elements的元素逐一浅层复制,底下列出JDK中的原始码实作片段以便了解:
ListN(E... input) {
// copy and check manually to avoid TOCTOU
@SuppressWarnings("unchecked")
E[] tmp = (E[])new Object[input.length]; // implicit nullcheck of input
for (int i = 0; i < input.length; i++) {
tmp[i] = Objects.requireNonNull(input[i]);
}
this.elements = tmp;
}
因此在数据结构上,就算你对该版本的of()方法直接传入数组,也没有参考至原elements参考之物件的疑虑,从而更进一步支持了不可变特性,然而要注意,因为是元素是浅层复制,如果你直接变更了元素的状态,of()方法传回的物件还是会反应出对应的状态变更。例如:
class Student {
...> String name;
...> }
| created class Student
Student student = new Student();
student ==> Student@cb644e
student.name = "Justin";
$3 ==> "Justin"
List<Student> students = List.of(student);
students ==> [Student@cb644e]
students.get(0).name;
$5 ==> "Justin"
student.name = "Monica";
$6 ==> "Monica"
students.get(0).name;
$7 ==> "Monica"
以上面的程序片段来说,如果你想要更进一步的不可变特性,应该令Student类别在定义时也支持不可变特性,如此一来,使用List.of()方法才有意义,例如
class Student {
...> final String name;
...> Student(String name) {
...> this.name = name;
...> }
...> }
| created class Student
Student student = new Student("Justin");
student ==> Student@cb644e
List<Student> students = List.of(student);
students ==> [Student@cb644e]
你也许会想到Arrays.asList()方法,似乎与List.of()方法很像,Arrays.asList()方法传回的物件长度固定,确实也是无法修改,由于方法定义时使用不定长度参数,也可以直接指定数组作为引数,这就会引发类似的问题:
String[] names = {"Justin", "Monica"};
names ==> String[2] { "Justin", "Monica" }
List<String> nameLt = Arrays.asList(names);
nameLt ==> [Justin, Monica]
names[0] = "Irene";
$3 ==> "Irene"
nameLt;
nameLt ==> [Irene, Monica]
会发生这个问题的理由类似,Arrays.asList()传回的物件,内部参考了names参考之物件(你可以试着查看Arrays.java的原始码实作来验证);如果你需要的是不可变物件,而不是无法修改的物件,那么在JDK9之后,建议改用List.of(),而不是Arrays.asList()了。