转换列表<;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))));