每日一记:关于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是不能修改的,这点我们通过源码去观察一下

  1. 第一步走到了这里,如果list List集合具备快速随机访问的能力(实现RandomAccess接口),then new 第一个list
  2. 否则就是new 第二个list
  3. 但是其实,第一个是继承了第二个的,所以我们直接去第二个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());
	}
}

🤔上述简单模拟了一个流程,看起来能走通的亚子。如果都能确保不会出现问题,那自然是可以的

但是问题就是,我们不能确定会不会有突发情况,例如可能会出现的问题

  1. 顾客商品传递的过程就被篡改了

  2. 商品传递没有被篡改,但是有动了歪心思的售货员(可能想让老板快点换个车)在结算的时候,偷偷多算了钱

  3. 当然也会存在,售货员跟顾客认识,结账的时候帮你偷偷少算点钱

    ....

用代码来模拟这几种情况

  • 情况一:顾客商品传递的过程就被篡改了

    /**
    	 * 篡改商品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方法适用的场景就是某一份数据在获取的时候就不能再被修改

记住一个词就好了,只读

posted @ 2021-09-08 18:50  码农Amg  阅读(135)  评论(0编辑  收藏  举报