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学生姓名对转换后的地址进行分组.
首先,当前的解决方案并不十分优雅,而与任何流媒体解决方案无关。
的模式
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,事后该转换Set成List在一个专用的一步。
因此,第一部分可以这样解决:
// 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调用,但是所有这些都比其他解决方案可读性差得多……
| 归档时间: |
|
| 查看次数: |
2121 次 |
| 最近记录: |