I have the following Student
object:
public class Student {
private String gradeAndClass;
private String gender;
private String name;
// getters, constructor, etc.
}
In the original code, properties gradeAndClass and gender are enums, but for the purpose of simplicity let's consider them to be strings.
I have a map Map<String,Student>
, where key is a unique string and value is a Student
object.
I need this map to split into a bunch of maps Map<String,Student>
, so the result should be a list of submaps List<Map<String,Student>>
.
Let's consider the following example:
Map<String, Student> data = new HashMap<>();
data.put("key1", new Student("class_1", "Boy", "Jo"));
data.put("key2", new Student("class_2", "Girl", "Alice"));
data.put("key3", new Student("class_1", "Girl", "Amy"));
data.put("key4", new Student("class_2", "Girl", "May"));
data.put("key5", new Student("class_1", "Boy", "Oscar"));
data.put("key6", new Student("class_2", "Boy", "Jimmy"));
data.put("key7", new Student("illegal class name", "Boy", "err1"));
data.put("key8", new Student("class_2", "not supported", "err2"));
Is there an easy way to split the Map
first by gradeAndClass
then by gender
, so that the result would be a list containing the following submaps:
- Submap 1:
"key1", ["class_1", "Boy", "Jo"]
"key5", ["class_1", "Boy", "Oscar"]
- Submap 2:
"key3", ["class_1", "Girl", "Amy"]
- Submap 3:
"key6", ["class_2", "Boy", "Jimmy"]
- Submap 4:
"key2", ["class_2", "Girl", "Alice"]
"key4", ["class_2", "Girl", "May"]
Also, I'd like to aggregate the illegal inputs to a separate map:
- Submap 5:
"key7", ["illegal class name", "boy", "err1"]
"key8", ["class_2", "not supported", "err2"]
I tried to filter data related to each submap separately (making use of the fact gradeAndClass
and gender
in the original code are well-defined enums), but it is very inefficient.
Basically, I've hard-coded all the condition checks and had to filter and regroup by each gradeAndClass
and gender
. I also had to reiterate each entry to get the illegal entries in order to put them to a separate "error map".
Sorry, I am very new to streams, so I know my solution is definitely not scalable, and thus I am looking for suggestion. Would it be possible to use stream groupingBy()
to do all these?
Here's the code I've used to generate a separate submap:
Map<String, Student> submap = data.entrySet().stream()
.filter(entry -> "class_1".equals(entry.getValue()) &&
(other conditions))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
CodePudding user response:
To group students by grade and by gender, we need an object combining those two properties.
For that, we can use Java 16 record
, or a class
for earlier JDK versions (alternatively there are few quick and dirty options like List<Object>
, or concatenated String
but it doesn't byes you anything, so I would not advise going this route).
record Key(String gradeAndClass, String gender) {}
As the first step, we need to create an auxiliary map which associates each Key (corresponding to a particular grade & gender) with a list of entries of the source-map (containing a string key and a student object). And that can be done using collector groupingBy()
.
Then we need to transform each list in the auxiliary map into a Map<String,Student>
and collect the result into a list.
That's how it might be implemented:
List<Map<String, Student>> result = data.entrySet().stream()
.collect(Collectors.groupingBy(
entry -> new Key(entry.getValue().getGradeAndClass(),
entry.getValue().getGender())
))
.values().stream()
.map(list -> list.stream()
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue
))
).toList(); // or .collect(Collectors.toList()) for JDK versions earlier than 16
In order to group entries containing Student
object with incorrect value of grade or unspecified gender, you can implement a utility method that would, containing conditional logic for spotting such erroneous objects and returning a special instance of a Key
(i.e. instantiated once and defined as static final
field). If an object is valid an "normal" Key
should be returned.
When you have this method implemented just replace the classifier function of groupingBy()
with a method reference which makes use of it.
CodePudding user response:
We can achieve that using Java 8 Streams groupingBy method. Following is the sample code
Map<String, List<Map.Entry<String, Student>>> filters = data.entrySet()
.stream()
.collect(Collectors.groupingBy(entry -> entry.getValue().getGradeAndClass()));
Output is
illegal class name=[key7=Student@2d98a335],
class_2=[key2=Student@16b98e56, key6=Student@7ef20235, key4=Student@27d6c5e0, key8=Student@4f3f5b24]
class_1=[key1=Student@15aeb7ab, key5=Student@7b23ec81, key3=Student@6acbcfc0]}