转换列表<;MyObject>;映射<;字符串,列表<;字符串>&燃气轮机;在Java8中,当我们有重复的元素和自定义筛选条件时

转换列表<;MyObject>;映射<;字符串,列表<;字符串>&燃气轮机;在Java8中,当我们有重复的元素和自定义筛选条件时,java,collections,java-stream,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; } @Over

我有一个学生班的例子

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;
    }
}
我有一个代码来创建一个映射,它将学生姓名存储为,并将一些经过处理的
addr
值(一个列表,因为我们对同一个学生有多个
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());

}
}

对于streams,您可以使用:


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

此版本收集集合中的所有内容,并将最终结果转换为数组列表:

/*
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import java.util.function.*;
*/

Map<String, List<String>> map2 = list.stream().collect(groupingBy(
  Student::getName, // we will group the students by name
  Collector.of(
    HashSet::new, // for each student name, we will collect result in a hash set
    (arr, student) -> arr.add(getNum(student.getAddr())), // which we fill with processed addresses
    (left, right) -> { left.addAll(right); return left; }, // we merge subresults like this
    (Function<HashSet<String>, List<String>>) ArrayList::new // finish by converting to List
  )
));
System.out.println(map2);

// Output:
// {a=[1, 2, 3], b=[1, 2], c=[1, 3]}
/*
导入java.util.*;
导入java.util.stream.*;
导入静态java.util.stream.Collectors.*;
导入java.util.function.*;
*/
Map map2=list.stream().collect(groupingBy(
学生::getName,//我们将按姓名对学生进行分组
收藏(
HashSet::new,//对于每个学生名,我们将在一个哈希集中收集结果
(arr,student)->arr.add(getNum(student.getAddr()),//我们用处理过的地址填充它
(左,右)->{left.addAll(右);return left;},//我们像这样合并子结果
(函数)ArrayList::new//通过转换为List完成
)
));
System.out.println(map2);
//输出:
//{a=[1,2,3],b=[1,2],c=[1,3]}

编辑:使用Marco13的提示缩短了
完成器的长度。

我想您正在寻找以下内容

   Map<String,Set<String>> map =  list.stream().
           collect(Collectors.groupingBy(
                    Student::getName,
                    Collectors.mapping(e->getNum(e.getAddr()), Collectors.toSet())
                ));

   System.out.println("Map : "+map);
Map Map=list.stream()。
收集(Collector.groupingBy)(
学生::getName,
Collectors.mapping(e->getNum(e.getAddr()),Collectors.toSet())
));
System.out.println(“Map:+Map”);

首先,无论采用何种流媒体解决方案,当前的解决方案都不是很优雅

模式

if (map.containsKey(k)) {
    Value value = map.get(k);
    ...
} else {
    map.put(k, new Value());
}
if (!list.contains(someValue)) {
    list.add(someValue);
}
通常可以简化为。在您的示例中,这将是

// 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);
    }
}
是一个strong符号,表示您不应使用
列表,而应使用
集合
。集合将只包含每个元素一次,并且您将避免列表上的
contains
调用,这些调用是O(n),因此对于较大的列表可能会很昂贵

即使您最终确实需要一个
列表
,但首先收集
集合
中的元素,然后在一个专用步骤中将该
集合
转换为
列表
通常更优雅、更高效

所以第一部分可以这样解决:

// 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);
}
或者,您可以通过一个步骤完成“分组依据”和从
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))));
映射结果=
list.stream().collect(Collectors.groupingBy(
学生::getName,LinkedHashMap::新建,
图(
s->getNum(s.getAddr()),
收藏,收藏,然后(
Collectors.toSet(),ArrayList::new));

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

就像使用
收集器一样。collectingAndThen
。太好了。一小部分,在键上附加一些东西,如果我需要输出为
{student-a=[1,2,3],student-b=[1,2],student-c=[1,3]}
@prime只需更改
student::getName
s->“student-”+s.getName()
@FedericoPeraltaSchaffner很棒。有一件事,如果我们需要从
getNum
中排除一些元素,比如输出将是
{a=[1,2],b=[1,2],c=[1,3]}
现在
a
的列表没有
3
,因为它在
getNum
中失败了一些条件,我们如何处理这样的条件呢。例如:如果类型是
type3
,那么我们不需要将其添加到列表中。@prime如果您使用Java 9,请更改getNum以使其返回一个流,如果条件为true,则返回一个单元素流,如果为false,则返回一个空流。然后,在收集时,使用Collectors.flatMapping而不是Collectors.mapping。还有其他的方法,也许你需要为这种情况写另一个问题,链接到这个问题的contextpvoted!这是一个非常完整的答案。我认为您可以在每个地图的值上使用
replaceAll
,而不是在条目集上进行流式处理,然后收集到新地图。@FedericoPeraltaSchaffner是的,
replaceAll
(我承认直到现在才出现在屏幕上)可能是另一种选择,根据您想要为映射值提供的类型信息(
集合
vs
?扩展集合
——可能它必须是一个
列表
…)。但这只是回答这个问题的众多自由度中的一个。我认为,当这些自由度作为深度嵌套的代码<代码收藏夹< /代码>实现的不同选项时,人们应该考虑用简单的命名操作将其分解为<代码>代码>,为了可读性……同意,下游收集器在第二级之后变得不可读,这就是为什么我要继续使用
Map.computeIfAbsent
解决方案。使用
收集器。collectingAndThen
似乎太冗长,无法添加简单的完成转换…@Marco13我总能从Marco13中学到一些东西:]谢谢你的提示!我更新了答案。似乎不需要为收集器单独声明变量。一个
(函数)
cast似乎足以选择
ArrayList
的正确构造函数。所以,这不是演员阵容,更准确地说,这是某种类型的归属。好的,那么我将删除分散注意力的评论。但我也有点恼火,为什么把它拉到一个变量中(或者像你的例子那样“转换”)似乎是必要的。我想知道这是否是预期的,为什么类型推断似乎达到了极限,或者是否存在“更好的”
// 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);
    }
}
if (!list.contains(someValue)) {
    list.add(someValue);
}
// 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);
}
// 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())));
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())));
}
Map<String, Set<String>> map = 
    list.stream().collect(Collectors.groupingBy(
        Student::getName, LinkedHashMap::new,
        Collectors.mapping(
            s -> getNum(s.getAddr()), Collectors.toSet())));
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))));