Java 8特性 - Collectors toMap

1、介绍

在本快速教程中,我们将讨论Collectors类的tomap()方法。我们将使用它将流收集到映射实例中。

对于这里介绍的所有示例,我们将使用书籍列表作为起点,并将其转换为不同的映射实现。

2、list 转 map

我们将从最简单的情况开始,将列表转换为映射

我们的书籍类定义为:

class Book {
    private String name;
    private int releaseYear;
    private String isbn;
     
    // getters and setters
}

我们将创建一个图书列表来验证我们的代码

List<Book> bookList = new ArrayList<>();
bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318"));
bookList.add(new Book("The Two Towers", 1954, "0345339711"));
bookList.add(new Book("The Return of the King", 1955, "0618129111"));

我们使用场景for this the following of the tomap(overload)法:

Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper)

通过toMap,我们可以指示如何获取映射的键和值的策略:

public Map<String, String> listToMap(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getIsbn, Book::getName));
}

我们可以很容易地验证它的工作原理:

@Test
public void whenConvertFromListToMap() {
    assertTrue(convertToMap.listToMap(bookList).size() == 3);
}

解决Key冲突

上面的例子工作得很好,但是如果有一个重复的键会发生什么呢?

让我们想象一下,我们在每本书的发行年份为地图设置了键

public Map<Integer, Book> listToMapWithDupKeyError(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity()));
}

鉴于我们之前的书籍清单,我们将看到一个非法状态例外:

@Test(expected = IllegalStateException.class)
public void whenMapHasDuplicateKey_without_merge_function_then_runtime_exception() {
    convertToMap.listToMapWithDupKeyError(bookList);
}

要解决这个问题,我们需要使用另一种方法附加参数mergefunction

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper,
  BinaryOperator<U> mergeFunction)

让我们引入一个合并函数,它指出在发生冲突的情况下,我们保留现有条目

public Map<Integer, Book> listToMapWithDupKey(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
      (existing, replacement) -> existing));
}

或者,换句话说,我们获得了第一个胜利行为:

@Test
public void whenMapHasDuplicateKeyThenMergeFunctionHandlesCollision() {
    Map<Integer, Book> booksByYear = convertToMap.listToMapWithDupKey(bookList);
    assertEquals(2, booksByYear.size());
    assertEquals("0395489318", booksByYear.get(1954).getIsbn());
}
  1. 其他 Map 类型
    默认情况下,tomap()方法将返回哈希映射。

但是我们可以返回不同的映射实现吗?答案是肯定的

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
  Function<? super T, ? extends U> valueMapper,
  BinaryOperator<U> mergeFunction,
  Supplier<M> mapSupplier)

其中mapsupplier是返回带有结果的新空映射的函数。

4.1. List 转 ConcurrentMap

让我们以上面相同的例子,添加一个mapsupplier函数返回一个concurrenthashmap

public Map<Integer, Book> listToConcurrentMap(List<Book> books) {
    return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
      (o1, o2) -> o1, ConcurrentHashMap::new));
}

让我们继续测试我们的代码:

@Test
public void whenCreateConcurrentHashMap() {
    assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap);
}

4.2. Sorted Map

最后,让我们看看如何返回已排序的映射。为此,我们需要对列表进行排序,并使用treemap作为mapsupplier参数:

public TreeMap<String, Book> listToSortedMap(List<Book> books) {
    return books.stream() 
      .sorted(Comparator.comparing(Book::getName))
      .collect(Collectors.toMap(Book::getName, Function.identity(), (o1, o2) -> o1, TreeMap::new));
}

上面的代码将根据图书名称对列表进行排序,然后将结果收集到TreeMap:

@Test
public void whenMapisSorted() {
    assertTrue(convertToMap.listToSortedMap(bookList).firstKey().equals("The Fellowship of the Ring"));
}

5、结论

在本文中,我们研究了Collectors类的tomap()方法。它允许我们从流中创建一个新的映射。我们还学习了如何解决关键冲突和创建不同的映射实现

`Collectors.toMap()` 是 Java Stream API 中的一个收集器,用于将流中的元素转换成一个 `Map`。它通常与其他流操作一起使用,在处理集合数据并希望得到键值对的结果时非常有用。 下面是具体的细节: ### 构造函数 `toMap` 支持多种形式的构造函数,以适应不同的需求场景: 1. **简单的键映射** - 如果键是唯一的,并直接来自于流中的对象: ```java Map<Key, Value> map = stream.collect(Collectors.toMap(Function.identity(), Function.identity())); ``` 2. **自定义键和值映射** - 可以为键和值提供两个独立的映射规则: ```java Map<K, U> collect = stream.collect(Collectors.toMap(keyMapper, valueMapper)); ``` 3. **解决冲突的情况** - 当存在相同的键时可以指定合并策略: ```java Map<K, U> collect = stream.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction)); ``` 4. **指定返回的地图类型** - 您还可以选择创建特定类型的 `Map` 实现如 `TreeMap`, `HashMap` 等等: ```java <K,U,M extends Map<K,U>> M toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) ``` 例如下面的例子展示了如何从一个包含员工信息的对象列表生成一个基于ID索引的地图: ```java List<Employee> employees = Arrays.asList( new Employee(1L, "John", 50_000), new Employee(2L, "Jane", 60_000)); // 使用 Collectors.toMap 创建基于 ID 的地图 Map<Long, String> employeeNamesById = employees.stream() .collect(Collectors.toMap(Employee::getId, Employee::getName)); System.out.println(employeeNamesById); // 输出 {1=John, 2=Jane} ``` 在这个例子中,我们指定了一个方法引用作为键(即每个雇员唯一标识符),以及另一个方法引用获取姓名做为对应的值。 请注意如果你的数据源中有重复项而未给出适当的合并功能,则会抛出异常 IllegalArgumentException 。因此确保你知道自己的输入数据特性或明确地处理潜在的问题!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值