杰瑞科技汇

Java List转Map总报错?教你一招快速搞定!

Of course! Converting a List to a Map is a very common task in Java. The goal is typically to transform a collection of objects into a key-value map, where each element in the list becomes a value in the map, and a specific property of that element becomes the key.

Java List转Map总报错?教你一招快速搞定!-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering the modern Java 8+ approach (which is the most common and recommended), the classic approach with loops, and handling common scenarios like duplicate keys.

The Core Problem: Choosing a Key and a Value

Before you start, you must decide two things:

  1. What will be the key? (e.g., id, username, email)
  2. What will be the value? (e.g., the entire object, a different property of the object)

Let's use a sample Person class for our examples:

class Person {
    private int id;
    private String name;
    private String city;
    public Person(int id, String name, String city) {
        this.id = id;
        this.name = name;
        this.city = city;
    }
    // Getters
    public int getId() { return id; }
    public String getName() { return name; }
    public String getCity() { return city; }
    @Override
    public String toString() {
        return "Person{id=" + id + ", name='" + name + "', city='" + city + "'}";
    }
}

The Modern Java 8+ Way (Recommended)

Since Java 8, the Stream API and Collectors provide a powerful and concise way to collect data. This is the preferred method in modern Java code.

Java List转Map总报错?教你一招快速搞定!-图2
(图片来源网络,侵删)

a) Collectors.toMap()

This is the most direct method. You provide a function to extract the key and a function to extract the value.

Scenario: Create a Map where the key is the Person's id and the value is the entire Person object.

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
List<Person> people = List.of(
    new Person(1, "Alice", "New York"),
    new Person(2, "Bob", "London"),
    new Person(3, "Charlie", "Paris")
);
// Key: Person's id, Value: The Person object itself
Map<Integer, Person> peopleById = people.stream()
    .collect(Collectors.toMap(
        Person::getId,         // Key mapper: How to get the key from a Person
        person -> person       // Value mapper: How to get the value from a Person
    ));
System.out.println(peopleById);
// Output: {1=Person{id=1, name='Alice', city='New York'}, 2=Person{id=2, name='Bob', city='London'}, 3=Person{id=3, name='Charlie', city='Paris'}}

Using Method References: You can often use method references for a cleaner look.

// Value mapper using a method reference that returns the object itself
Map<Integer, Person> peopleByIdShort = people.stream()
    .collect(Collectors.toMap(Person::getId, person -> person)); // person -> person can be written as Function.identity()
// Even shorter for the value
Map<Integer, Person> peopleByIdShortest = people.stream()
    .collect(Collectors.toMap(Person::getId, Function.identity()));

b) Handling Duplicate Keys

What happens if two people in the list have the same id? The Collectors.toMap() method will throw an IllegalStateException by default.

Java List转Map总报错?教你一招快速搞定!-图3
(图片来源网络,侵删)

You can handle this by providing a "merge function" as a third argument. This function decides what to do when a duplicate key is encountered.

Scenario: The list has two people with id = 1.

List<Person> peopleWithDuplicateId = List.of(
    new Person(1, "Alice", "New York"),
    new Person(2, "Bob", "London"),
    new Person(1, "Anna", "Berlin") // Duplicate ID!
);
// Option 1: Keep the existing value (the first one encountered)
Map<Integer, Person> keepFirst = peopleWithDuplicateId.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Function.identity(),
        (existing, replacement) -> existing // Merge function
    ));
System.out.println("Keep first: " + keepFirst);
// Output: Keep first: {1=Person{id=1, name='Alice', city='New York'}, 2=Person{id=2, name='Bob', city='London'}}
// Option 2: Keep the new value (the last one encountered)
Map<Integer, Person> keepLast = peopleWithDuplicateId.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Function.identity(),
        (existing, replacement) -> replacement // Merge function
    ));
System.out.println("Keep last: " + keepLast);
// Output: Keep last: {1=Person{id=1, name='Anna', city='Berlin'}, 2=Person{id=2, name='Bob', city='London'}}
// Option 3: Combine the values (e.g., concatenate names)
Map<Integer, String> combineNames = peopleWithDuplicateId.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName,
        (name1, name2) -> name1 + ", " + name2 // Merge function
    ));
System.out.println("Combine names: " + combineNames);
// Output: Combine names: {1=Alice, Anna, 2=Bob}

c) Creating a Map of Different Properties

You are not limited to using the object itself as the value. You can map to any property.

Scenario: Create a Map where the key is the id and the value is the person's name.

Map<Integer, String> idToNameMap = people.stream()
    .collect(Collectors.toMap(
        Person::getId,
        Person::getName
    ));
System.out.println(idToNameMap);
// Output: {1=Alice, 2=Bob, 3=Charlie}

The Classic Pre-Java 8 Way (For Loops)

Before Java 8, you would typically use a traditional for loop or an enhanced for-each loop with a HashMap. This approach is more verbose but is good to understand for maintaining older codebases.

Scenario: Create a Map where the key is the id and the value is the entire Person object.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
List<Person> people = new ArrayList<>(List.of(
    new Person(1, "Alice", "New York"),
    new Person(2, "Bob", "London"),
    new Person(3, "Charlie", "Paris")
));
// Create an empty HashMap
Map<Integer, Person> peopleById = new HashMap<>();
// Loop through the list
for (Person person : people) {
    // Put the key-value pair into the map
    // Note: If a key already exists, this will overwrite the value with the new one.
    peopleById.put(person.getId(), person);
}
System.out.println(peopleById);
// Output: {1=Person{id=1, name='Alice', city='New York'}, 2=Person{id=2, name='Bob', city='London'}, 3=Person{id=3, name='Charlie', city='Paris'}}

Handling Duplicate Keys in a Loop: To handle duplicates, you need to add a manual check.

List<Person> peopleWithDuplicateId = List.of(
    new Person(1, "Alice", "New York"),
    new Person(2, "Bob", "London"),
    new Person(1, "Anna", "Berlin")
);
Map<Integer, Person> keepFirstInLoop = new HashMap<>();
for (Person person : peopleWithDuplicateId) {
    // putIfKeyAbsent will only add the value if the key is not already in the map
    keepFirstInLoop.putIfAbsent(person.getId(), person);
}
System.out.println("Keep first (loop): " + keepFirstInLoop);
// Output: Keep first (loop): {1=Person{id=1, name='Alice', city='New York'}, 2=Person{id=2, name='Bob', city='London'}}
// To keep the last one, you can just use put(), which overwrites by default
Map<Integer, Person> keepLastInLoop = new HashMap<>();
for (Person person : peopleWithDuplicateId) {
    keepLastInLoop.put(person.getId(), person);
}
System.out.println("Keep last (loop): " + keepLastInLoop);
// Output: Keep last (loop): {1=Person{id=1, name='Anna', city='Berlin'}, 2=Person{id=2, name='Bob', city='London'}}

Summary and Comparison

Feature Java 8+ Collectors.toMap() Classic for loop
Conciseness Excellent. A single, expressive line of code. Verbose. Requires manual map creation and iteration.
Readability High for those familiar with Streams and Lambdas. The "what" is clear. Clear and straightforward for all Java developers. The "how" is explicit.
Error Handling Built-in handling for duplicate keys via a merge function. Requires manual if checks or using putIfAbsent/put to handle duplicates.
Flexibility Very flexible. Can easily map to any key or value property. Flexible, but requires more boilerplate code for each change.
Performance Generally highly optimized. Can be slightly slower for tiny lists due to stream overhead, but negligible for most cases. Very direct and performant. No overhead from the Stream API.
Best For New, modern Java applications. It's the idiomatic and preferred way. Maintaining legacy codebases or when working in environments without Java 8+.

When to Use Which?

  • Use Java 8+ Collectors.toMap():

    • In all new projects using Java 8 or later.
    • When you want concise, declarative code.
    • When you need to handle duplicate keys in a complex way.
  • Use the Classic for loop:

    • When you are working with a Java version older than 8.
    • When you need maximum performance on a very large list and are in a performance-critical section (though this is rare).
    • When the logic is so simple that a stream feels like overkill (though this is subjective).
分享:
扫描分享到社交APP
上一篇
下一篇