文章目录
一、创建不可变集合
不可变集合:不可以被修改的集合。
一旦创建完毕后,长度不能改,内容也不能改。
二、创建不可变集合
的应用场景
如果某个数据不能被修改,把它防御性拷贝到不可变集合中是个很好的实际。
当集合对象被不可信的库调用时,不可变形式是安全的。
简单理解:如果你不想让别人去修改集合中的内容,那么就可以给它去提供一个不可变的集合。
当别人拿到这个不可变的集合后,它只能进行查询操作,是不能修改 / 删除 / 添加的。
这样的场景有很多,例如斗地主游戏中的牌盒,牌盒中装了所有的牌,这些牌是固定的,一共 54张
。
我不想让别人去修改,那万一别人加个 3
进去,那就是出老千了。
因此为了防止别人出老千,我们就可以使用不可变的集合
还有斗地主游戏中的出牌规则,这些规则不想增加,也不想删除,也不想让别人修改,也可以使用不可变集合。
![image-20240429213932604](https://img-blog.csdnimg.cn/img_convert/fccf0b5fc2d1002ed8da8b235cbac0e7.png)
还有用代码获取的操作系统硬件信息,也是不能被修改的。
总而言之,只要你不想让别人去修改集合中的数据,只想让别人进行查询操作的话,这些数据就可以放到不可变集合中。
三、创建不可变集合的书写格式
在 List
、Set
、Map
接口中,都存在静态的 of方法
,可以获取一个不可变的集合。
四、创建不可变的 List集合
可以发现这个方法的形参是个可变参数,因此在这个方法里面我们可以传递很多数据。
![image-20240429215654401](https://img-blog.csdnimg.cn/img_convert/23d2cedda9779a8027224270d0234ee4.png)
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
List<String> list = List.of("张三", "李四", "王五", "赵六");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println(list.get(2));
System.out.println(list.get(3));
System.out.println("---------------------------");
for (String s : list) {
System.out.println(s);
}
System.out.println("---------------------------");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------------");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------------");
//list.remove("李四");
//list.add("aaa");
list.set(0,"aaa");
}
五、创建不可变的 Set集合
可以发现,它的形参跟刚刚的 List.of()
是一模一样的,也是一个可变参数,因此我们可以往小括号中传递很多信息。
![image-20240429215920381](https://img-blog.csdnimg.cn/img_convert/d982bad23ff564416372186ba4489aa8.png)
细节:当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性,否则就会报错
![image-20240430065645597](https://img-blog.csdnimg.cn/img_convert/e370da55106b82e4d007bef8db9149d8.png)
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");
//由于Set集合是没有索引的,因此如果我们想要查询,只能遍历
for (String s : set) {
System.out.println(s);
}
System.out.println("-----------------------");
Iterator<String> it = set.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-----------------------");
//set.remove("王五");
六、创建不可变的 Map集合
Java会把第一个参数和第二个参数认为是一个键值对对象,第三个参数和第四个参数认为是一个键值对对象…
![image-20240430065852710](https://img-blog.csdnimg.cn/img_convert/9b79a75b70803563844c03212f8e8ced.png)
细节1:键是不能重复的,如果重复就会报错。
细节2:Map里面的of方法,参数是有上限的,最多只能传递20个参数,即10个键值对。
List
跟 Set
能传递多个,就是因为它的形参是 可变参数
。
但是 Map
,选中 Map
跟进,搜索 of()
,可以发现它并没有带可变参数的 of()
。
![image-20240430070632698](https://img-blog.csdnimg.cn/img_convert/720d51ffe0d48e585295c3818a78783d.png)
参数最多的就是 20个参数
。
![image-20240430070723205](https://img-blog.csdnimg.cn/img_convert/883517bf85842b79bfdf0358f5e78f4b.png)
那为什么它没有设计带有可变参数的方法呢?我们自己来写个方法试试。
如果我们想让 Map
的 of方法
也能接收多个键和值,那么键和值都需要写成可变参数。
但键和值的类型不确定,因此可以使用泛型方法来解决,写完就会发现立马报错:Vararg parameter must be the last in the list
(必须在形参的最后)。
![image-20240430071224132](https://img-blog.csdnimg.cn/img_convert/8c189c9d0a99ca4de75d758584c64a48.png)
//一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
"赵六", "广州", "孙七", "深圳", "周八", "杭州",
"吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
"陈二", "嘉兴");
Set<String> keys = map.keySet();
for (String key : keys) {
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
System.out.println("--------------------------");
}
细节3:如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法。
我们可以将 键值
看做是一个整体,传递给 ofEntries()
,这个方法就可以给你返回一个不可变集合。
![image-20240430071438703](https://img-blog.csdnimg.cn/img_convert/21abf145ba91a7257e6a2b39407aa12a.png)
//创建一个普通的Map集合
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
hm.put("李四", "北京");
hm.put("王五", "上海");
hm.put("赵六", "北京");
hm.put("孙七", "深圳");
hm.put("周八", "杭州");
hm.put("吴九", "宁波");
hm.put("郑十", "苏州");
hm.put("刘一", "无锡");
hm.put("陈二", "嘉兴");
hm.put("aaa", "111");
利用上面的数据来获取一个不可变的集合,首先需要获取到所有的键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
由于ofEntries方法
的形参是一个可变参数,可变参数实际上就是一个数组,因此我们需要把entries变成一个数组。
我们可以用 entries
调用里面的 toArray()
,to:变成,array:数组
。
首先来调用一下没有参数的 toArray()
,没有参数就表示没有指定类型,那么它返回的就是 Object类型
的。
但此时我们想要给这个数组指定类型,就可以使用下面的第二个带形参的方法,我们需要对这个数组指定类型。
![image-20240430072817743](https://img-blog.csdnimg.cn/img_convert/c9380e604fc58cb761dbb288153e9a82.png)
//toArray方法在底层会比较集合的长度跟数组的长度两者的大小
//如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
//假设数组初始化长度为20,11个键值对会填满前11个位置,后面的9个默认初始化值为null
//因此数组的长度直接写0就行了,我们不用去关心它的长度,因为底层它会自己判断8
Map.Entry[] arr = entries.toArray(new Map.Entry[0]);
//不可变的map集合
//可变参数实际上就是一个数组,因此我们直接将数组传递过去也是可以的
Map map = Map.ofEntries(arr);
map.put("bbb","222"); // 这行代码会报错
上面这种写法代码太多太麻烦了,我们试着将它简化一下
Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map.copyOf()
但是这样写还是太麻烦了,因此Java给我们在 Map接口
中还提供了一个 copyOf()
。
在这个方法中它会做一个判断
static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map) {
// 当你传过来的 Map集合 就是一个不可变的集合,那么它直接把本身的集合给你返回
if (map instanceof ImmutableCollections.AbstractImmutableMap) {
return (Map<K,V>)map;
} else { // 但如果你传递过来的 Map集合 是可变的,这段代码就跟我们刚刚写的一模一样
return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
}
}
因此在以后,如果我们要生成不可变的 Map集合
直接使用 copyOf
就行了,只不过它是在 JDK10
的时候才出现的。
![image-20240430074351587](https://img-blog.csdnimg.cn/img_convert/01e2a476c29d0609bd5b80afa4c2236d.png)
Map<String, String> map = Map.copyOf(hm);
map.put("bbb","222"); // 报错
七、总结
1、不可变集合的特点?
定义完成后只能查询,不能修改 / 添加 / 删除
2、如何创建不可变集合?
List
、Set
、Map
接口中,都存在静态的 of()
可以创建不可变集合
3、三种方式的细节
-
List
:直接用 -
Set
:元素不能重复 -
Map
:元素不能重复、键值对数量最多是10个,超过10个用ofEntries方法
如果你的JDK版本大于等于10,可以用
copyOf()
简化代码