5.4 一些细节

Lambda表达式的引入也推动了一些新方法被加入集合类。让我们来看看Map类的一些变化。

构建Map时,为给定值计算键值是常用的操作之一,一个经典的例子就是实现一个缓存。传统的处理方式是先试着从Map中取值,如果没有取到,创建一个新值并返回。

假设使用Map artistCache定义缓存,我们需要使用费时的数据库操作查询艺术家信息,代码可能如例5-31所示。

例5-31 使用显式判断空值的方式缓存

  1. public Artist getArtist(String name) {
  2. Artist artist = artistCache.get(name);
  3. if (artist == null) {
  4. artist = readArtistFromDB(name);
  5. artistCache.put(name, artist);
  6. }
  7. return artist;
  8. }

Java 8引入了一个新方法computeIfAbsent,该方法接受一个Lambda表达式,值不存在时使用该Lambda表达式计算新值。使用该方法,可将上述代码重写为例5-32所示的形式。

例5-32 使用computeIfAbsent缓存

  1. public Artist getArtist(String name) {
  2. return artistCache.computeIfAbsent(name, this::readArtistFromDB);
  3. }

你可能还希望在值不存在时不计算,为Map接口新增的computecomputeIfAbsent就能处理这些情况。

在工作中,你可能尝试过在Map上迭代。过去的做法是使用value方法返回一个值的集合,然后在集合上迭代。这样的代码不易读。例5-33展示了本章早些时候介绍的一种方式,创建一个Map,然后统计每个艺术家专辑的数量。

例5-33 一种丑陋的迭代Map的方式

  1. Map<Artist, Integer> countOfAlbums = new HashMap<>();
  2. for(Map.Entry<Artist, List<Album>> entry : albumsByArtist.entrySet()) {
  3. Artist artist = entry.getKey();
  4. List<Album> albums = entry.getValue();
  5. countOfAlbums.put(artist, albums.size());
  6. }

谢天谢地,Java 8为Map接口新增了一个forEach方法,该方法接受一个BiConsumer对象为参数(该对象接受两个参数,返回空),通过内部迭代编写出易于阅读的代码,关于内部迭代请参考3.1节。使用该方法重写后的代码如例5-34所示。

例5-34 使用内部迭代遍历Map里的值

  1. Map<Artist, Integer> countOfAlbums = new HashMap<>();
  2. albumsByArtist.forEach((artist, albums) -> {
  3. countOfAlbums.put(artist, albums.size());
  4. });