Reputation: 4724
I am trying to convert the below nested for loop into hashmap using java stream but i got struck in the collector step. Could you please help?
Existing code:
private static HashMap<String, Long> getOutput(List<Employee> eList) {
HashMap<String, Long> outputList = new HashMap<>();
for (Employee employee : eList) {
List<Department> departmentList = employee.getDepartmentList();
for (Department department : departmentList) {
if (department.getType().equals(DepartmentType.SCIENCE)) {
outputList.put(employee.getName(),department.getDepartmentId()));
}
}
}
return outputList;
}
So far i tried:
private static HashMap<String, Long> getOutput(List<Employee> eList) {
return eList.stream()
.flatMap(emp -> emp.getDepartmentList().stream()
.filter(dept -> dept.getType().equals(DepartmentType.SCIENCE))
.collect(HashMap::new, ???)
}
Upvotes: 3
Views: 1682
Reputation: 3325
I know it's a little late but here is a contribution + explanation into your already beautiful mix of answer:
private static HashMap<String, Long> getOutput(List<Employee> eList) {
return eList
.stream() // for each Employee
.flatMap(emp -> emp.getDepartmentList() // get all his departments
.stream()
//filter departments by your predicate
.filter(dept -> dept.getType().equals(DepartmentType.SCIENCE))
// build an Entry with the employee and each department
.map(dept -> new SimpleEntry(emp.getName(),dept.getDepartmentId())))
// Each SimpleEntry<Name,DeptId> is then added to the your final Map
.collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue, (val1, val2) ->{ return val1;},HashMap::new));
}
SimpleEntry
is just an Implementation of the Map.Entry
Interface:
public class SimpleEntry implements Entry<String, Long> {
private String name;
private Long deptId;
public SimpleEntry(String name, Long deptId) {
this.name = name;
this.deptId = deptId;
}
@Override
public String getKey() {
return this.name;
}
@Override
public Long getValue() {
return this.deptId;
}
@Override
public Long setValue(Long value) {
return this.deptId = value;
}
}
Upvotes: 0
Reputation: 1476
It seems like your main issue is preserving the stream's current emp
reference after you've done the flatMap
. To keep this reference, you will need to flatMap
to some sort of class that can hold both the Employee and Department - such as a generic Tuple (aka Pair).
Java doesn't have an intuitive Tuple class built into it's API, so your options are:
Edit:
The comments (thanks @Holger!) have enlightened that it appears that there are many departments per employee. My original code risks throwing an exception since there would be duplicate keys, while the OP's original code simply overwrites the map entries. Consider using the groupingBy
collector and changing the return type of this method.
private static Map<String, List<Long>> getOutput(List<Employee> eList) {
return eList.stream()
// get a stream of employee / department pairs
.flatMap(emp -> emp.getDepartmentList().stream().map(dep -> new EmployeeDepartmentPair(emp, dep))
// filter the departments to SCIENCE
.filter(x -> x.department.getType().equals(DepartmentType.SCIENCE))
// group departmentIds by employee name
.collect(Collectors.groupingBy(x -> x.employee.getName(), Collectors.mapping(x -> x.department.getDepartmentId(), Collectors.toList())))
}
Original (see above edit):
Here's some updated code using option 3:
private static Map<String, Long> getOutput(List<Employee> eList) {
return eList.stream()
.flatMap(emp -> emp.getDepartmentList().stream().map(dep -> new EmployeeDepartmentPair(emp, dep))
.filter(x -> x.department.getType().equals(DepartmentType.SCIENCE))
.collect(Collectors.toMap(x -> x.employee.getName(), x -> x.department.getDepartmentId()));
}
private static class EmployeeDepartmentPair {
public final Employee employee;
public final Department department;
public EmployeeDepartmentPair(Employee emp, Department d) {
this.employee = emp;
this.department = d;
}
}
Upvotes: 3
Reputation: 3664
This is never going to pretty as a stream, since you need the departments from your first filter to calculate the value to put into the map. So you need to filter the departments twice: the second time to find the one that gave you a positive match the first time and get its Id value.
IMHO, this code is best formatted in its current form, since it makes it clearer to grasp what it is exactly that it does, as well as simpler to debug. Here is, by comparison, the same code turned into a stream:
return eList.stream()
.flatMap(emp -> emp.getDepartmentList().stream()
.filter(dep -> dep.getType().equals(DepartmentType.SCIENCE))).collect(
Collectors.toMap(Employee::getName, emp -> emp.getDepartmentList().stream()
.filter(dep ->dep.getType.equals(DepartmentType.SCIENCE)
.findFirst().get().getDepartmentId())), (s, a) -> a);
}
Essentially, what was missing in your question was the Collectors.toMap()
method, which takes as parameters:
toMap()
method with only two arguments, but it throws an IllegalStateException
if duplicate values are inserted.Upvotes: 2