> 文章列表 > 《Java8实战》第8章 Collection API 的增强功能

《Java8实战》第8章 Collection API 的增强功能

《Java8实战》第8章 Collection API 的增强功能

8.1 集合工厂

如果我想创建一个集合,之前的做法是先new一个list,然后再一个个的add,这样子有点繁琐。
现在的方法可以这样,是使用 Arrays.asList()工厂方法:
List<String> friends = Arrays.asList("Raphael", "Olivia", "Thibaut");
但是这样子创建只能更新,不可以增加、删除。

8.1.1 List 工厂

工厂方法 List.of方法,创建的是一个只读的列表。
它可以保护你的集合,以免被意外地修改。
建议是除非你需要进行某种形式的数据处理并对数据进行转换,否则应该尽量使用工厂方法。工厂方法使用起来更简单,实现也更容易,并且在大多数情况下就够用了。

8.1.2 Set 工厂

创建一个set集合 Set.of
Set<String> friends = Set.of("Raphael", "Olivia", "Thibaut");

8.1.3 Map 工厂

与list、set相比,创建map稍显复杂,因为你需要同时传递键和值。Java 9中提供了两种初始化一个不可变 Map 的方式。
Map<String, Integer> ageOfFriends = Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26);
如果你只需要创建不到 10 个键值对的小型 Map,那么使用这种方法比较方便。
规模比较大的话,就使用Map.ofEntries 的工厂方法,该工厂方法接受以变长参数列表形式组织的 Map.Entry<K, V>对象作为参数

import static java.util.Map.entry; 
Map<String, Integer> ageOfFriends = Map.ofEntries(entry("Raphael", 30), entry("Olivia", 25), entry("Thibaut", 26)); 
System.out.println(ageOfFriends); 

8.2 使用 List 和 Set

Java 8 在 List 和 Set 的接口中新引入了以下方法。

  • removeIf 移除集合中匹配指定谓词的元素。实现了 List 和 Set 的所有类都提供了该方法(事实上,这个方法继承自 Collection 接口)。
  • replaceAll 用于 List 接口中,它使用一个函数(UnaryOperator)替换元素。
  • sort 也用于 List 接口中,对列表自身的元素进行排序。

8.2.1 removeIf 方法

循环的时候remove就会抛异常,ConcurrentModificationException。因为在底层实现上,for-each 循环使用了一个迭代器对象,集合由两个不同的对象管理着
一般解决这个问题就需要显示的调用Iterator 对象,并通过它调用 remove()方法

for (Iterator<Transaction> iterator = transactions.iterator(); iterator.hasNext(); ) { Transaction transaction = iterator.next(); if(Character.isDigit(transaction.getReferenceCode().charAt(0))) { iterator.remove(); } 
} 

不过这样子的话,代码就显得繁琐,所以就可以使用removeIf了,
transactions.**removeIf**(transaction -> Character.isDigit(transaction.getReferenceCode().charAt(0)));

8.2.2 replaceAll 方法

List 接口提供的 replaceAll 方法让你可以使用一个新的元素替换列表中满足要求的每个元素。
原来的方式:

referenceCodes.stream() .map(code -> Character.toUpperCase(code.charAt(0)) + code.substring(1)) .collect(Collectors.toList()) .forEach(System.out::println);
输入: [a12, C14, b13] 
输出: A12, C14, B13或者
for (ListIterator<String> iterator = referenceCodes.listIterator(); iterator.hasNext(); ) { String code = iterator.next(); iterator.set(Character.toUpperCase(code.charAt(0)) + code.substring(1)); 
} 

现在可以这样子:
referenceCodes.replaceAll(code -> Character.toUpperCase(code.charAt(0)) + code.substring(1));

8.3 使用 Map

Java 8 在 Map 接口中新引入了几个默认方法

8.3.1 forEach 方法

使用 Map.Entry<K, V>迭代器访问 Map 集合中的每一个元素:

for(Map.Entry<String, Integer> entry: ageOfFriends.entrySet()) { String friend = entry.getKey(); Integer age = entry.getValue(); System.out.println(friend + " is " + age + " years old"); 
} 

从 Java 8 开始,Map 接口开始支持 forEach 方法,该方法接受一个 BiConsumer,以 Map的键和值作为参数。
ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " + age + " years old"));

8.3.2 排序

Entry.comparingByValue 、Entry.comparingByKey这两个可以帮助map排序

favouriteMovies .entrySet() .stream() .sorted(Entry.comparingByKey()) .forEachOrdered(System.out::println);

8.3.3 getOrDefault 方法

当获取的对象不存在的话,就返回一个默认值
System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix"));
如果key为Olivia的值不存在,那么就返回"Matrix"

8.3.4 计算模式

就是如果key不存在,那么就执行某个操作

  • computeIfAbsent——如果指定的键没有对应的值(没有该键或者该键对应的值是空),那么使用该键计算新的值,并将其添加到 Map 中;
  • computeIfPresent——如果指定的键在 Map 中存在,就计算该键的新值,并将其添加到 Map 中;
  • compute——使用指定的键计算新的值,并将其存储到 Map 中。

computeIfAbsent 的一个应用场景是缓存信息。
// 如果key不存在就执行calculateDigest操作
lines.forEach(line -> dataToHash.computeIfAbsent(line, this::calculateDigest));
还有这个场景,

String friend = "Raphael"; 
List<String> movies = friendsToMovies.get(friend); 
if(movies == null) { movies = new ArrayList<>(); friendsToMovies.put(friend, movies); 
} 
movies.add("Star Wars"); 优化
friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>()) .add("Star Wars");

8.3.5 删除模式

java提供了一个重载方法的remove方法。
可能是这样子删除的:判断map的key存在,再比较值是否相等,然后再删除

String key = "Raphael"; 
String value = "Jack Reacher 2"; 
if (favouriteMovies.containsKey(key) && Objects.equals(favouriteMovies.get(key), value)) { favouriteMovies.remove(key); return true; 
} else { return false; 
}

现在这样:
favouriteMovies.remove(key, value);
vlaue小于10就删除
movies.entrySet().removeIf(entry -> entry.getValue() < 10);

8.3.6 替换模式

Map 中提供了两种新的方法来替换其内部映射项,分别是:

  • replaceAll——通过 BiFunction 替换 Map 中每个项的值。该方法的工作模式类似于之前介绍过的 List 的 replaceAll 方法;
  • Replace——如果键存在,就可以通过该方法替换 Map 中该键对应的值。它是对原有replace 方法的重载,可以仅在原有键对应某个特定的值时才进行替换。

movie 就是value
favouriteMovies.replaceAll((friend, movie) -> movie.toUpperCase());

8.3.7 merge 方法

Map<String, String> friends = Map.ofEntries(entry("Raphael", "Star Wars")); 
Map<String, String> everyone = new HashMap<>(family); 
everyone.putAll(friends); 
System.out.println(everyone); 

但是如果是有冲突的合并需要处理,可以这样

Map<String, String> family = Map.ofEntries( entry("Teo", "Star Wars"), entry("Cristina", "James Bond")); 
Map<String, String> friends = Map.ofEntries( entry("Raphael", "Star Wars"), entry("Cristina", "Matrix")); Map<String, String> everyone = new HashMap<>(family); 
friends.forEach((k, v) -> everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2)); // 如果存在重复的键,就连接两个值
System.out.println(everyone); 

merge 方法处理空值的方法相当复杂,在 Javadoc 文档中是这么描述的:
如果指定的键并没有关联值,或者关联的是一个空值,那么[merge]会将它关联到指定的非空值。否则,[merge]会用给定映射函数的[返回值]替换该值,如果映射函数的返回值为空就删除[该键]。

8.4 改进的 ConcurrentHashMap

并发安全,使用的是分段锁

8.4.1 归约和搜索

ConcurrentHashMap 类支持三种新的操作

  • forEach——对每个(键, 值)对执行指定的操作;
  • reduce——依据归约函数整合所有(键, 值)对的计算结果;
  • search——对每个(键, 值)对执行一个函数,直到函数取得一个非空值。

每种操作支持四种形式的参数,接受函数使用键、值、Map.Entry 以及(键, 值)对作为参数:

  • 使用键(forEachKey,reduceKeys,searchKeys);
  • 使用值(forEachValue,reduceValues,searchValues);
  • 使用 Map.Entry 对象(forEachEntry,reduceEntries,searchEntries);
  • 使用键和值(forEach,reduce,search)。
ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>(); 
long parallelismThreshold = 1; 
Optional<Integer> maxValue = Optional.ofNullable(map.reduceValues(parallelismThreshold, Long::max)); 

8.4.2 计数

ConcurrentHashMap 类提供了一个新的 mappingCount 方法,能以长整形 long 返回 Map中的映射数目。你应该尽量在新的代码中使用它,而不是继续使用返回 int 的 size 方法。

8.4.3 Set 视图

ConcurrentHashMap 类还提供了一个新的 keySet 方法,该方法以 Set 的形式返回ConcurrentHashMap 的一个视图(Map 中的变化会反映在返回的 Set 中,反之亦然)。

8.5 小结

  • Java 9 支持集合工厂,使用 List.of、Set.of、Map.of 以及 Map.ofEntries 可以创建小型不可变的 List、Set 和 Map。
  • 集合工厂返回的对象都是不可变的,这意味着创建之后你不能修改它们的状态。
  • List 接口支持默认方法 removeIf、replaceAll 和 sort。
  • Set 接口支持默认方法 removeIf。
  • Map 接口为常见模式提供了几种新的默认方法,并降低了出现缺陷的概率。
  • ConcurrentHashMap 支持从 Map 中继承的新默认方法,并提供了线程安全的实现。