当我们有重复的元素和自定义过滤条件时,在Java 8中将List <MyObject>转换为Map <String,List <String >>

pri*_*ime 3 java collections java-stream

我有一个学生班的实例.

class Student {
    String name;
    String addr;
    String type;

    public Student(String name, String addr, String type) {
        super();
        this.name = name;
        this.addr = addr;
        this.type = type;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", addr=" + addr + "]";
    }

    public String getName() {
        return name;
    }

    public String getAddr() {
        return addr;
    }
}
Run Code Online (Sandbox Code Playgroud)

我有一个代码来创建一个地图,它将学生名称作为关键字和一些处理过的addr值(一个List,因为我们有addr同一个学生的多个值)存储为.

public class FilterId {

public static String getNum(String s) {
    // should do some complex stuff, just for testing
    return s.split(" ")[1];
}

public static void main(String[] args) {
    List<Student> list = new ArrayList<Student>();
    list.add(new Student("a", "test 1", "type 1"));
    list.add(new Student("a", "test 1", "type 2"));
    list.add(new Student("b", "test 1", "type 1"));
    list.add(new Student("c", "test 1", "type 1"));
    list.add(new Student("b", "test 1", "type 1"));
    list.add(new Student("a", "test 1", "type 1"));
    list.add(new Student("c", "test 3", "type 2"));
    list.add(new Student("a", "test 2", "type 1"));
    list.add(new Student("b", "test 2", "type 1"));
    list.add(new Student("a", "test 3", "type 1"));
    Map<String, List<String>> map = new HashMap<>();

    // This will create a Map with Student names (distinct) and the test numbers (distinct List of tests numbers) associated with them.
    for (Student student : list) {
        if (map.containsKey(student.getName())) {
            List<String> numList = map.get(student.getName());
            String value = getNum(student.getAddr());

            if (!numList.contains(value)) {
                numList.add(value);
                map.put(student.getName(), numList);
            }
        } else {
            map.put(student.getName(), new ArrayList<>(Arrays.asList(getNum(student.getAddr()))));
        }
    }

    System.out.println(map.toString());

}
}
Run Code Online (Sandbox Code Playgroud)

输出将是: {a=[1, 2, 3], b=[1, 2], c=[1, 3]}

我怎样才能以更优雅的方式在java8中做同样的事情,可能正在使用流?

在java 8中找到了这个Collectors.toMap,但是找不到一种方法来实际执行相同的操作.

我试图将元素映射为CSV,但它不起作用,因为我无法找到一种方法来轻松删除重复项,输出不是我目前所需要的.

Map<String, String> map2 = new HashMap<>();
map2 = list.stream().collect(Collectors.toMap(Student::getName, Student::getAddr, (a, b) -> a + " , " + b));
System.out.println(map2.toString());
// {a=test 1 , test 1 , test 1 , test 2 , test 3, b=test 1 , test 1 , test 2, c=test 1 , test 3}
Run Code Online (Sandbox Code Playgroud)

Fed*_*ner 12

随着流,你可以使用Collectors.groupingBy连同Collectors.mapping:

Map<String, Set<String>> map = list.stream()
    .collect(Collectors.groupingBy(
        Student::getName,
        Collectors.mapping(student -> getNum(student.getAddr()),
            Collectors.toSet())));
Run Code Online (Sandbox Code Playgroud)

我已经选择创建集合而不是列表映射,因为您似乎不希望列表中出现重复.


如果确实需要列表而不是集合,则首先收集集合然后将集合转换为列表会更有效:

Map<String, List<String>> map = list.stream()
    .collect(Collectors.groupingBy(
        Student::getName,
        Collectors.mapping(s -> getNum(s.getAddr()),
            Collectors.collectingAndThen(Collectors.toSet(), ArrayList::new))));
Run Code Online (Sandbox Code Playgroud)

这使用Collectors.collectingAndThen,首先收集然后转换结果.


另一种更紧凑的方式,没有流:

Map<String, Set<String>> map = new HashMap<>(); // or LinkedHashMap
list.forEach(s -> 
    map.computeIfAbsent(s.getName(), k -> new HashSet<>()) // or LinkedHashSet
        .add(getNum(s.getAddr())));
Run Code Online (Sandbox Code Playgroud)

此变体用于Iterable.forEach迭代列表并按Map.computeIfAbsent学生姓名对转换后的地址进行分组.

  • 喜欢使用`Collectors.collectingAndThen`. (4认同)

Mar*_*o13 5

首先,当前的解决方案并不十分优雅,而与任何流媒体解决方案无关。

的模式

if (map.containsKey(k)) {
    Value value = map.get(k);
    ...
} else {
    map.put(k, new Value());
}
Run Code Online (Sandbox Code Playgroud)

通常可以用简化Map#computeIfAbsent。在您的示例中,这将是

// This will create a Map with Student names (distinct) and the test
// numbers (distinct List of tests numbers) associated with them.
for (Student student : list)
{
    List<String> numList = map.computeIfAbsent(
        student.getName(), s -> new ArrayList<String>());
    String value = getNum(student.getAddr());
    if (!numList.contains(value))
    {
        numList.add(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

(这是Java 8函数,但仍与流无关)。


其次,您要在其中构建的数据结构似乎不是最合适的。一般而言,

if (!list.contains(someValue)) {
    list.add(someValue);
}
Run Code Online (Sandbox Code Playgroud)

是一个强烈的信号,表明您不应该使用a List,而应该使用a Set。该集合将仅包含每个元素一次,并且您将避免在contains列表上进行调用(O(n)),因此对于较大的列表而言可能会很昂贵。

甚至如果你真的需要一个List到最后,往往更优雅和高效是先收集在一个元素Set,事后该转换SetList在一个专用的一步。

因此,第一部分可以这样解决:

// This will create a Map with Student names (distinct) and the test
// numbers (distinct List of tests numbers) associated with them.
Map<String, Collection<String>> map = new HashMap<>();
for (Student student : list)
{
    String value = getNum(student.getAddr());
    map.computeIfAbsent(student.getName(), s -> new LinkedHashSet<String>())
        .add(value);
}
Run Code Online (Sandbox Code Playgroud)

它将创建一个Map<String, Collection<String>>。然后可以将其转换为Map<String, List<String>>

// Convert the 'Collection' values of the map into 'List' values 
Map<String, List<String>> result = 
    map.entrySet().stream().collect(Collectors.toMap(
        Entry::getKey, e -> new ArrayList<String>(e.getValue())));
Run Code Online (Sandbox Code Playgroud)

或者,更一般地说,为此使用实用程序方法:

private static <K, V> Map<K, List<V>> convertValuesToLists(
    Map<K, ? extends Collection<? extends V>> map)
{
    return map.entrySet().stream().collect(Collectors.toMap(
        Entry::getKey, e -> new ArrayList<V>(e.getValue())));
}
Run Code Online (Sandbox Code Playgroud)

建议这样做,但是您也可以将for循环转换为流操作:

Map<String, Set<String>> map = 
    list.stream().collect(Collectors.groupingBy(
        Student::getName, LinkedHashMap::new,
        Collectors.mapping(
            s -> getNum(s.getAddr()), Collectors.toSet())));
Run Code Online (Sandbox Code Playgroud)

或者,您可以一步完成“分组依据”和从Set到的转换List

Map<String, List<String>> result = 
    list.stream().collect(Collectors.groupingBy(
        Student::getName, LinkedHashMap::new,
        Collectors.mapping(
            s -> getNum(s.getAddr()), 
            Collectors.collectingAndThen(
                Collectors.toSet(), ArrayList<String>::new))));
Run Code Online (Sandbox Code Playgroud)

或者您可以引入一个自己的收集器来执行List#contains调用,但是所有这些都比其他解决方案可读性差得多……