8.2 使用List和Set
Java 8在List和Set的接口中新引入了以下方法。
removeIf移除集合中匹配指定谓词的元素。实现了List和Set的所有类都提供了该方法(事实上,这个方法继承自Collection接口)。replaceAll用于List接口中,它使用一个函数(UnaryOperator)替换元素。sort也用于List接口中,对列表自身的元素进行排序。
以上所有方法都作用于调用对象本身。换句话说,它们改变的是集合自身,这一点跟流的操作有很大的不同,流的操作会生成一个新(复制)的结果。为什么要添加这些新方法呢?因为集合的修改烦琐而且容易出错。所以Java 8的开发团队添加了removeIf和replaceAll来解决这一问题。
8.2.1 removeIf方法
来看看下面这段代码,它试图从所有的交易记录中删除那些以数字打头的引用代码(reference code)的交易:
for (Transaction transaction : transactions) {if(Character.isDigit(transaction.getReferenceCode().charAt(0))) {transactions.remove(transaction);}}
发现其中的问题了吗?非常不幸,这段代码可能导致ConcurrentModificationException。为什么会这样?因为在底层实现上,for-each循环使用了一个迭代器对象,所以代码的执行会像下面这样:
for (Iterator<Transaction> iterator = transactions.iterator();iterator.hasNext(); ) {Transaction transaction = iterator.next();if(Character.isDigit(transaction.getReferenceCode().charAt(0))) {transactions.remove(transaction); ←---- 问题在这儿,我们使用了两个不同的对象来迭代和修改集合}}
注意,在这段代码中,集合由两个不同的对象管理着:
Iterator对象,它使用next()和hasNext()方法查询源;Collection对象,它通过调用remove()方法删除集合中的元素。
因此,迭代器对象的状态没有与集合对象的状态同步,反之亦然。为了解决这个问题,你只能显式地使用Iterator对象,并通过它调用remove()方法:
for (Iterator<Transaction> iterator = transactions.iterator();iterator.hasNext(); ) {Transaction transaction = iterator.next();if(Character.isDigit(transaction.getReferenceCode().charAt(0))) {iterator.remove();}}
如此一来这段代码就变得非常烦琐。现在,你使用Java 8提供的removeIf方法可以取代这段代码中的逻辑,该方法不仅简单,还可以避免前述的缺陷。removeIf方法接受一个用于判断删除哪一个元素的谓词作为参数:
transactions.removeIf(transaction ->Character.isDigit(transaction.getReferenceCode().charAt(0)));
不过,有些时候,你想要做的不是删除列表中的元素,而是替换它们。为了解决这个问题,Java 8新增了replaceAll方法。
8.2.2 replaceAll方法
List接口提供的replaceAll方法让你可以使用一个新的元素替换列表中满足要求的每个元素。你可以使用Stream API解决这一问题,如下所示:
referenceCodes.stream() ←---- [a12, C14, b13].map(code -> Character.toUpperCase(code.charAt(0)) +code.substring(1)).collect(Collectors.toList()).forEach(System.out::println); ←---- 输出A12, C14, B13
这段代码会生成一个新的字符串集合。然而,你想要的是更新现有集合的方法。你还可以使用ListIterator对象(该对象提供了set()方法,其可以替换集合中的元素):
for (ListIterator<String> iterator = referenceCodes.listIterator();iterator.hasNext(); ) {String code = iterator.next();iterator.set(Character.toUpperCase(code.charAt(0)) + code.substring(1));}
如你所见,这段代码相当烦琐。此外,刚才介绍过,把Iterator对象和集合对象混在一起使用比较容易出错,特别是还需要修改集合对象的场景。在Java 8中,你可以通过下面这种简单的代码实现同样的逻辑:
referenceCodes.replaceAll(code -> Character.toUpperCase(code.charAt(0)) +code.substring(1));
我们已经学习了List和Set的新特性,不过别忘了还有Map。下一节将介绍Map接口的新特性。
