Java学习笔记 —— Java 8 Map 新特性
- 获取链接
- X
- 电子邮件
- 其他应用
Java 8引入了Map接口支持的几种默认方法。 我们从forEach开始介绍。
forEach
旧版Java遍历Map的键和值一直很尴尬,需要在Map上使用Map.Entry
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,并将键和值作为参数。 使用forEach可以让代码更为简洁:
ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " + age + " years old"));
Java 8引入了两种便捷的方法来比较Map中的条目,可以按值或键对Map进行排序:
- Entry.comparingByValue
- Entry.comparingByKey
以下代码:
Map<String, String> favouriteMovies
= Map.ofEntries(entry("Raphael", "Star Wars"),
entry("Cristina", "Matrix"),
entry("Olivia","James Bond"));
favouriteMovies
.entrySet()
.stream() .sorted(Entry.comparingByKey()) .forEachOrdered(System.out::println);
的输出为:
Cristina=Matrix
Olivia=James Bond
Raphael=Star Wars
getOrDefault
因为要查找的key可能不存在,为了防止NullPointerException,就必须检查空引用,一般我们会补一个默认值。 Java8之后可以使用getOrDefault方法简单实现这个需求。key 作为第一个参数,默认值作为第二个参数:
Map<String, String> favouriteMovies
= Map.ofEntries(entry("Raphael", "Star Wars"),
entry("Olivia", "James Bond"));
System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix"));
System.out.println(favouriteMovies.getOrDefault("Thibaut", "Matrix"));
如果 key 存在于Map中,但意外地指向了null值,则getOrDefault仍可以返回null。 另外一点,无论 key 是否存在,默认值表达式都会执行一次。
Java 8还包括一些更高级的模式来处理给定键的值的存在和不存在。
Compute patterns
有时,我们希望根据 Map中是否存在key,有条件地执行运算并存储其结果。 例如,用map对耗时计算结果进行缓存。 如果存在key,则无需重新计算结果。通常使用下面三个方法:
- computeIfAbsent —— 如果给定key没有指定值(不存在,或者其值为null),计算新值并将其添加到Map中。
- computeIfPresent —— 如果存在指定的key,计算一个新值并将其添加到 map。
- compute —— 始终计算给定key的新值并将其存储。
我们可以使用computeIfAbsent来缓存信息。 假设计算一组文件的SHA-256。 如果你以前处理过该文件,则无需重新计算。
我们使用Map来实现缓存,使用MessageDigest的一个实例来计算SHA-256哈希值:
Map<String, byte[]> dataToHash = new HashMap<>();
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
然后,我们遍历数据并缓存结果:
lines.forEach(line ->
dataToHash.computeIfAbsent(line,
this::calculateDigest));
private byte[] calculateDigest(String key) {
return messageDigest.digest(key.getBytes(StandardCharsets.UTF_8));
}
这种模式对于批量添加元素也很有用。如果需要向Map
String friend = "Raphael";
List<String> movies = friendsToMovies.get(friend);
if(movies == null) {
movies = new ArrayList<>();
friendsToMovies.put(friend, movies);
}
movies.add("Star Wars");
System.out.println(friendsToMovies);
改用 computeIfAbsent之后就很简单了:
friendsToMovies.computeIfAbsent("Raphael", name -> new ArrayList<>())
.add("Star Wars");
如果与键关联的当前值存在于Map中并且为非null,则validateIfPresent方法将计算一个新值。需要注意一点,如果产生的值为null,当前的key会从Map中删除。
Remove patterns
Java 8 之前,删除指定映射关系的元素,实现如下:
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;
}
Java8 可以使用如下方法:
favouriteMovies.remove(key, value);
Replacement patterns
Java 8 提供了两个新方法,用于替换Map中的条目:
- replaceAll —— 将每个条目的值替换为应用BiFunction的结果。此方法的作用类似于List上replaceAll。
- replace —— 如果存在key,则可以替换Map中的值。仅当键映射到特定值时,附加重载才替换该值。
可以使用如下方法格式化map中的所有值:
Map<String, String> favouriteMovies = new HashMap<>();
favouriteMovies.put("Raphael", "Star Wars");
favouriteMovies.put("Olivia", "james bond");
favouriteMovies.replaceAll((friend, movie) -> movie.toUpperCase());
System.out.println(favouriteMovies);
Merge
假设需要合并两个map,比如两组联系人的map。 可以按如下方式使用putAll:
Map<String, String> family = Map.ofEntries(
entry("Teo", "Star Wars"), entry("Cristina", "James Bond"));
Map<String, String> friends = Map.ofEntries( entry("Raphael", "Star Wars"));
Map<String, String> everyone = new HashMap<>(family);
everyone.putAll(friends);
System.out.println(everyone);
只要没有重复的key,此代码就可以正常工作。 如果需要更加灵活的merge,就可以使用新方法。 此方法采用BiFunction合并具有重复key的值:
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"));
也将merge方法与forEach结合使用,提供一种解决冲突的方法。
Map<String, String> everyone = new HashMap<>(family);
friends.forEach((k, v) ->
everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2));
System.out.println(everyone);
最后要注意一点,Javadoc指出,merge方法具有相当复杂的空值处理方式:
如果指定的键尚未与值关联或与null关联,则[merge]会将其与给定的非null值关联。 否则,[merge]将关联的值替换为给定重新映射函数的[result],如果结果为null,则将其删除。
我们还可以使用merge来实现初始化检查。 假设有一个map,用于记录观看电影的次数。 你需要先检查map中代表电影的关键点,然后才能增加其值:
Map<String, Long> moviesToCount = new HashMap<>();
String movieName = "JamesBond";
long count = moviesToCount.get(movieName); if(count == null) {
moviesToCount.put(movieName, 1); }
else {
moviesToCount.put(moviename, count + 1);
}
以上代码可以简化为
moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L);
在这种情况下要合并的第二个参数是1L。 因为为该key返回的值为null,所以第一次赋值为1。 下次计数时,由于key的值已初始化为1,因此将应用BiFunction来增加计数。
- 获取链接
- X
- 电子邮件
- 其他应用
评论