I have a method that goes though Lectors (basically users) in a department and returns a JSON of integers of how many lectors are assigned to which degree (degree is an enum)
I have the method here:
@Override
public String getDepartmentStatistics(String departmentName) {
List<Lector> lectorList = departmentRepository.getByName(departmentName).getLector();
int assistantNumbers = 0;
int associate_professorNumbers = 0;
int professorNumbers = 0;
for (Lector lector : lectorList) {
if (lector.getLectorDegree().equals(ASSISTANT)) {
assistantNumbers += 1;
} else if (lector.getLectorDegree().equals(ASSOCIATE_PROFESSOR)) {
associate_professorNumbers += 1;
} else if (lector.getLectorDegree().equals(PROFESSOR)) {
professorNumbers += 1;
} else {
return null;
}
}
return String.valueOf(new JSONObject()
.put("Assistants", assistantNumbers)
.put("Associate professors", associate_professorNumbers)
.put("Professors", professorNumbers));
}
It's working as expected.
My question is how do i improve the quality/look of it?
Here is an example that uses a Map
to keep track of all the counts:
enum Degree {
PROFESSOR,
ASSISTANT,
ASSOCIATE_PROFESSOR
}
class Lector {
Degree degree;
public Degree getDegree() {
return degree;
}
}
public String getDepartmentStatistics(String departmentName) {
List<Lector> lectorList = /*...*/
Map<Degree, Integer> result = new EnumMap<>(Degree.class);
lectorList.stream()
.map(Lector::getDegree)
.forEach(degree -> {
Integer currentCount = result.getOrDefault(degree, 0);
result.put(degree, ++currentCount);
});
return String.valueOf(new JSONObject()
.put("Assistants", result.getOrDefault(Degree.ASSISTANT, 0))
.put("Associate professors", result.getOrDefault(Degree.ASSOCIATE_PROFESSOR, 0))
.put("Professors", result.getOrDefault(Degree.PROFESSOR, 0)));
}
Instead of Stream.forEach
you can also group via the stream. You can also use Map.compute
instead of getOrDefault
. This version is my subjectively preferred solution as its pretty clear about what is happening.
Compared to using if
or switch
new Degree
values work out of the box (only the return statement isn't generic yet, but it can be generified too).
EDIT: Improved solution with Map.merge
as pointed out by Andy Turner:
public String getDepartmentStatistics(String departmentName) {
List<Lector> lectorList = /*...*/
Map<Degree, Integer> result = new EnumMap<>(Degree.class);
lectorList.stream()
.map(Lector::getDegree)
.forEach(degree -> result.merge(degree, 1, Integer::sum));
return String.valueOf(new JSONObject()
.put("Assistants", result.getOrDefault(Degree.ASSISTANT, 0))
.put("Associate professors", result.getOrDefault(Degree.ASSOCIATE_PROFESSOR, 0))
.put("Professors", result.getOrDefault(Degree.PROFESSOR, 0)));
}
EDIT2: Let me include a fully functional approach that does not mix imperative and functional approaches as much (as discussed in the comments):
public String getDepartmentStatistics(String departmentName) {
List<Lector> lectorList = /*...*/
Map<Degree, Integer> result =
lectorList.stream()
.collect(Collectors.groupingBy(
Lector::getDegree,
() -> new EnumMap<>(Degree.class),
Collectors.counting()
));
return String.valueOf(new JSONObject()
.put("Assistants", result.getOrDefault(Degree.ASSISTANT, 0))
.put("Associate professors", result.getOrDefault(Degree.ASSOCIATE_PROFESSOR, 0))
.put("Professors", result.getOrDefault(Degree.PROFESSOR, 0)));
}