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
));
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 aclassfor earlier JDK versions (alternatively there are few quick and dirty options likeList<Object>, or concatenatedStringbut it doesn't byes you anything, so I would not advise going this route).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:
In order to group entries containing
Studentobject 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 aKey(i.e. instantiated once and defined asstatic finalfield). If an object is valid an "normal"Keyshould be returned.When you have this method implemented just replace the classifier function of
groupingBy()with a method reference which makes use of it.