每日一记:关于Arrays.asList和Collections.unmodifiableList的一点理解
1、正常创建一个List,对List进行操作
List<Integer> collect = Stream.of(1 ,3 ,5 ,7 ,9).collect(Collectors.toList());
//第一位改变为9
collect.set(0, 9);
//尾部插入一个值
collect.add(99);
collect.forEach(System.out::println);
//output
9
3
5
7
9
99
2、有时候为了更加方便的创建List,使用Arrays.asList
2.1 出现情况
List<Integer> asList = Arrays.asList(1 ,3 ,5 ,7 ,9);
asList.set(0,9);
//asList.add(99);
asList.forEach(System.out::println);
//output
9
3
5
7
9
//把上述注释放开
oops~报错了
2.2 原因分析
直接点入asList这个方法,查看源码,发现这个通过Arrays.asList创建出来的List是继承这个AbstractList的
分析源码发现,此时的ArrayList有set的复用,当时并没有add方法的复用,所以默认就是采用父类的方法了,我们再点进去父类分析
最终找到这个方法,所以就是为什么使用Arrays.asList创建出来的list使用set,当时使用add方法就会报错
2.3 解决方案
//使用new ArrayList()包装一下
3、还有情况就是想创建一个不可变的List,不能set更改值,也不能add增加值,“只读”情况
3.1 出现情况
3.2 原因分析
使用Collections.unmodifiableList方法包装List之后,重新生成的List是不能修改的,这点我们通过源码去观察一下
- 第一步走到了这里,如果list List集合具备快速随机访问的能力(实现RandomAccess接口),then new 第一个list
- 否则就是new 第二个list
- 但是其实,第一个是继承了第二个的,所以我们直接去第二个list(UnmodifiableList中)查看一下源码
可以发现,无论是set或者是add,甚至是remove,sort,均会抛出异常
也就是说,通过这个包装方法得到的list集合,是只读的,不可变的,正如方法名所说的一样
3.3 适用场景
模拟一个大型购物商场,顾客选择完自己想要购买的商品,到售货员哪里进行结算的流程
抽象出来两个角色:顾客和售货员
/**
* 模拟顾客购买商品的流程
*
* @author Amg
* @date 2021/9/8 10:01
*/
public class Customer {
private List<Long> ids;
Customer() {
ids = new ArrayList<>();
}
/**
* 添加商品
* @param goodsId 商品主键id
*/
public void add(Long goodsId) {
ids.add(goodsId);
}
/**
* 移除商品
* @param goodsId 商品主键id
*/
public void remove(Long goodsId) {
ids.remove(goodsId);
}
/**
* 返回最终的商品列表
* @return List<Long>
*/
public List<Long> getIds() {
return ids;
}
}
/**
* 售货员
* @author Amg
* @date 2021/9/8 10:18
*/
public class Sales {
private List<Long> list;
public Sales(List<Long> goodsId) {
list = goodsId;
}
/**
* 结算
*/
public double countPrice() {
double price = 0;
for (Long id : list) {
//根据list里面的商品获取价格,这里简单先模拟
if (id % 2 == 0) {
price += 1;
} else {
price += 2;
}
}
return price;
}
}
/**
* 商场客户端
* @author Amg
* @date 2021/9/8 10:17
*/
public class Client {
public static void main(String[] args) {
Customer customer = new Customer();
//添加商品
customer.add(123L);
customer.add(456L);
customer.add(789L);
//移除商品
customer.remove(123L);
List<Long> ids = customer.getIds();
Sales sales = new Sales(ids);
System.out.println(sales.countPrice());
}
}
🤔上述简单模拟了一个流程,看起来能走通的亚子。如果都能确保不会出现问题,那自然是可以的
但是问题就是,我们不能确定会不会有突发情况,例如可能会出现的问题
-
顾客商品传递的过程就被篡改了
-
商品传递没有被篡改,但是有**动了歪心思的售货员(可能想让老板快点换个车)**在结算的时候,偷偷多算了钱
-
当然也会存在,售货员跟顾客认识,结账的时候帮你偷偷少算点钱
…
用代码来模拟这几种情况
-
情况一:顾客商品传递的过程就被篡改了
/** * 篡改商品id * @param goodsId 旧的商品列表 * @return 返回一个新的篡改好的list */ public static void tamperOldIds(List<Long> goodsId) { //在这里进行篡改,添加或者删除,这里模拟添加 goodsId.add(999L); goodsId.add(1097L); goodsId.add(573L); } //Client调用的时候 //假设被替换了 tamperOldIds(ids); Sales sales = new Sales(ids); System.out.println(sales.countPrice()); //output就是篡改后的计算的价格
-
情况二:被售货员篡改了(多算/少算 都是篡改)
public double countPrice() { double price = 0; //动了歪心思,售货员篡改商品,这里也是模拟添加 list.add(3332L); list.add(3336L); list.add(3339L); for (Long id : list) { //根据list里面的商品获取价格,这里简单先模拟 if (id % 2 == 0) { price += 1; } else { price += 2; } } return price; }
所以可以得到一个结论,我们要杜绝顾客的购买商品列表被篡改
使用 Collections.unmodifiableList 来包装一下顾客的购买商品列表,即可使其不能再被修改,上述的修改就都行不通了
/**
* 返回最终的商品列表,只读
* @return List<Long>
*/
public List<Long> getIds() {
return Collections.unmodifiableList(ids);
}
所以Collections.unmodifiableList方法适用的场景就是某一份数据在获取的时候就不能再被修改
记住一个词就好了,只读