DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Securing Your Software Supply Chain with JFrog and Azure
Register Today

Trending

  • Health Check Response Format for HTTP APIs
  • Comparing Cloud Hosting vs. Self Hosting
  • What ChatGPT Needs Is Context
  • RBAC With API Gateway and Open Policy Agent (OPA)

Trending

  • Health Check Response Format for HTTP APIs
  • Comparing Cloud Hosting vs. Self Hosting
  • What ChatGPT Needs Is Context
  • RBAC With API Gateway and Open Policy Agent (OPA)
  1. DZone
  2. Coding
  3. Java
  4. Refactoring Java 8 Code With Collector

Refactoring Java 8 Code With Collector

Suraj Muraleedharan user avatar by
Suraj Muraleedharan
·
Aug. 07, 20 · Tutorial
Like (20)
Save
Tweet
Share
12.03K Views

Join the DZone community and get the full member experience.

Join For Free

I am planning to refactor a code that is based on Java 7 to Java 8 using the functional components, specifically Collector. The plan is simple: learn it by coding it. Let’s get started…

Overview of the Java Object We Are Dealing With

Java object to refactor


The following code is intended to fetch a few specific pieces of data from the Company object, where the company has a list of employees. I would like to perform the following operations:

  • Find out the most common name (Employee name) in the company.
  • Find the highest salaried employee of the company.
  • Find the sum of salaries for all men.
  • Find the most popular grade/band in the company.

Prior to Java 8, it was tough to refactor this logic and find out some common parts. But functional components make refactoring easy.

Pre-Java 8 Version

Let’s go through the code,

Find out the most common employee name in the company

Java
xxxxxxxxxx
1
21
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    String repeatingName = null;
3
    Map<String, Integer> nameAndCount = new HashMap<>();
4
    for (Department department : company.getDepartments()) {
5
        for (Employee employee : department.getEmployees()) {
6
            if (!nameAndCount.containsKey(employee.getName())) {
7
                nameAndCount.put(employee.getName(), 1);
8
            } else {
9
                Integer count = nameAndCount.get(employee.getName());
10
                count++;
11
                nameAndCount.put(employee.getName(), count);
12
            }
13
        }
14
    }
15
    for (Map.Entry<String, Integer> entry : nameAndCount.entrySet()) {
16
        if (entry.getValue().equals(Collections.max(nameAndCount.values()))) {
17
            repeatingName = entry.getKey();
18
        }
19
    }
20
    return repeatingName;
21
}


Find the highest salaried employee

Java
xxxxxxxxxx
1
15
 
1
private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
2
    Employee costlyEmployee = null;
3
    Map<Employee, Long> employeeAndSalary = new HashMap<>();
4
    for (Department department : company.getDepartments()) {
5
        for (Employee employee : department.getEmployees()) {
6
            employeeAndSalary.put(employee, employee.getSalary());
7
        }
8
    }
9
    for (Map.Entry<Employee, Long> entry : employeeAndSalary.entrySet()) {
10
        if (entry.getValue().equals(Collections.max(employeeAndSalary.values()))) {
11
            costlyEmployee = entry.getKey();
12
        }
13
    }
14
    return costlyEmployee;
15
}


Find the sum of salaries for all Men

Java
xxxxxxxxxx
1
11
 
1
private static Long findSumOfAllMenSalary(Company company) {
2
    Long totalSalary = 0L;
3
    for (Department department : company.getDepartments()) {
4
        for (Employee employee : department.getEmployees()) {
5
            if (employee.getGender().equals(Gender.MALE)) {
6
                totalSalary = totalSalary + employee.getSalary();
7
            }
8
        }
9
    }
10
    return totalSalary;
11
}


Find the most popular grade/band in the company

Java
xxxxxxxxxx
1
21
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Band popularBand = null;
3
    Map<Band, Integer> bandAndCount = new HashMap<>();
4
    for (Department department : company.getDepartments()) {
5
        for (Employee employee : department.getEmployees()) {
6
            if (!bandAndCount.containsKey(employee.getBand())) {
7
                bandAndCount.put(employee.getBand(), 1);
8
            } else {
9
                Integer count = bandAndCount.get(employee.getBand());
10
                count++;
11
                bandAndCount.put(employee.getBand(), count);
12
            }
13
        }
14
    }
15
    for (Map.Entry<Band, Integer> entry : bandAndCount.entrySet()) {
16
        if (entry.getValue().equals(Collections.max(bandAndCount.values()))) {
17
            popularBand = entry.getKey();
18
        }
19
    }
20
    return popularBand;
21
}


In all the cases, we need to iterate through various departments and then employees. But since the conditions and the data we want to collect are in a different format, we won't be able to find something common and refactor it properly. Let’s see how Java 8 deals with this.


As an initial step, let's use streams and do the processing.

Find out the most common employee name in the company

Let’s go through the logic of nameAndCount, since the iterations (for loop on department and employee) have a defined result(nameAndCount). We can use streams to get that value:

Java
xxxxxxxxxx
1
15
 
1
company.getDepartments().stream().flatMap(department -> department.getEmployees().stream())
2
 .forEach(employee -> addToNameAndCountMap(nameAndCount, employee));
3
 
4
...
5
...
6
  
7
private static void addToNameAndCountMap(Map<String, Integer> nameAndCount, Employee employee) {
8
    if (!nameAndCount.containsKey(employee.getName())) {
9
        nameAndCount.put(employee.getName(), 1);
10
    } else {
11
        Integer count = nameAndCount.get(employee.getName());
12
        count++;
13
        nameAndCount.put(employee.getName(), count);
14
    }
15
}


Why didn’t we get the result via collect? We have some logic there. We need to identify the duplicates. The value of the map is the count of employee names. Let’s see the final results.

Java
xxxxxxxxxx
1
25
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    Map<String, Integer> nameAndCount = new HashMap<>();
3
    company.getDepartments().stream()
4
            .flatMap(department -> department.getEmployees().stream())
5
            .forEach(employee -> addToNameAndCountMap(nameAndCount, employee));
6
    String repeatingName = null;
7
    Integer maxCount = Collections.max(nameAndCount.values());
8
    Optional<String> repeat = nameAndCount.entrySet().stream()
9
            .filter(e -> e.getValue().equals(maxCount))
10
            .map(Map.Entry::getKey).findFirst();
11
    if (repeat.isPresent()) {
12
        repeatingName = repeat.get();
13
    }
14
    return repeatingName;
15
}
16
 
17
private static void addToNameAndCountMap(Map<String, Integer> nameAndCount, Employee employee) {
18
    if (!nameAndCount.containsKey(employee.getName())) {
19
        nameAndCount.put(employee.getName(), 1);
20
    } else {
21
        Integer count = nameAndCount.get(employee.getName());
22
        count++;
23
        nameAndCount.put(employee.getName(), count);
24
    }
25
}


Find the highest salaried employee

Let’s do the same refactoring here as well. But here, we can use collect as the logic is fairly less complex.

Java
xxxxxxxxxx
1
13
 
1
private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
2
    Employee costlyEmployee = null;
3
    Map<Employee, Long> employeeAndSalary = company.getDepartments().stream()
4
            .flatMap(department -> department.getEmployees().stream())
5
            .collect(Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b));
6
    Long maxSalary = Collections.max(employeeAndSalary.values());
7
    Optional<Employee> costly = employeeAndSalary.entrySet().stream()
8
            .filter(e -> e.getValue().equals(maxSalary)).map(Map.Entry::getKey).findFirst();
9
    if(costly.isPresent()) {
10
        costlyEmployee = costly.get();
11
    }
12
    return costlyEmployee;
13
}


Find the sum of salaries for all Men

Well, this one seems much simpler.

Java
xxxxxxxxxx
1
 
1
private static Long findSumOfAllMenSalary(Company company) {
2
    return company.getDepartments().stream()
3
            .flatMap(d -> d.getEmployees().stream())
4
            .filter(e -> e.getGender().equals(Gender.MALE))
5
            .map(Employee::getSalary).mapToLong(Long::longValue).sum();
6
}


Find the most popular grade/band in the company

We need to apply a similar logic as we did for the first method. (I mean findMostRepeatingNameInCompany.) Let's try it.

Java
xxxxxxxxxx
1
25
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Map<Band, Integer> bandAndCount = new HashMap<>();
3
    company.getDepartments().stream()
4
            .flatMap(department -> department.getEmployees().stream())
5
            .forEach(employee -> addToBandAndCoutMap(bandAndCount, employee));
6
    Band popularBand = null;
7
    Integer maxBand = Collections.max(bandAndCount.values());
8
    Optional<Band> popular = bandAndCount.entrySet().stream()
9
            .filter(e -> e.getValue().equals(maxBand))
10
            .map(Map.Entry::getKey).findFirst();
11
    if(popular.isPresent()) {
12
        popularBand = popular.get();
13
    }
14
    return popularBand;
15
}
16
 
17
private static void addToBandAndCoutMap(Map<Band, Integer> bandAndCount, Employee employee) {
18
    if (!bandAndCount.containsKey(employee.getBand())) {
19
        bandAndCount.put(employee.getBand(), 1);
20
    } else {
21
        Integer count = bandAndCount.get(employee.getBand());
22
        count++;
23
        bandAndCount.put(employee.getBand(), count);
24
    }
25
}


We have some common values and might be able to save some lines if we find those common things and refactor it properly, let's go with that…


This time, let's use stream.collect() method everywhere. So we have changes only on two method calls, findMostRepeatingNameInCompany and findMostPopularBandInCompany.

Find out the most common employee name in the company

Let’s try to use collect here, and avoid the addToNameAndCountMap method call. If we use the right collector, we can collect data to a map, with a custom key or value. We are going to use Collectors.groupingBy(Employee::getName,Collectors.counting()) for this.

Java
xxxxxxxxxx
1
14
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    Map<String, Long> nameAndCount = company.getDepartments().stream()
3
            .flatMap(department -> department.getEmployees().stream())
4
            .collect(Collectors.groupingBy(Employee::getName,Collectors.counting()));
5
    Long maxCount = Collections.max(nameAndCount.values());
6
    Optional<String> repeat = nameAndCount.entrySet().stream()
7
            .filter(e -> e.getValue().equals(maxCount))
8
            .map(Map.Entry::getKey).findFirst();
9
    String repeatingName = null;
10
    if (repeat.isPresent()) {
11
        repeatingName = repeat.get();
12
    }
13
    return repeatingName;
14
}


Find the most popular grade/band in the company

Let’s do the same thing here as well.

Java
xxxxxxxxxx
1
14
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Map<Band, Long> bandAndCount = company.getDepartments().stream()
3
            .flatMap(department -> department.getEmployees().stream())
4
            .collect(Collectors.groupingBy(Employee::getBand, Collectors.counting()));
5
    Band popularBand = null;
6
    Long maxBand = Collections.max(bandAndCount.values());
7
    Optional<Band> popular = bandAndCount.entrySet().stream()
8
            .filter(e -> e.getValue().equals(maxBand))
9
            .map(Map.Entry::getKey).findFirst();
10
    if(popular.isPresent()) {
11
        popularBand = popular.get();
12
    }
13
    return popularBand;
14
}


Now we have a common pattern, and it seems there are possibilities to extract some functional components out of it. Let’s explore that possibility.


We could see that the methods findMostRepeatingNameInCompany, findEmployeeWithHighestSalaryInTheCompany and findMostPopularBandInCompany follows a similar pattern. The only difference is the type of map and the collector we use. So, let's write a method that returns a generic map and accept a collector.

Java
xxxxxxxxxx
1
 
1
private static <T> Map<T, Long> processEmployeeToMap(Company company,
2
                                                     Collector<Employee, ?, Map<T,Long>>
3
                                                     employeeMapCollector) {
4
    return company.getDepartments().stream()
5
            .flatMap(department -> department.getEmployees().stream())
6
            .collect(employeeMapCollector);
7
}


This is a generic method, it works for different inputs, if T is a string, it will use the collector to get a Map<String, Long> as the return type. If we use another object, say, Employee, then the return type will be Map<Employee, Long>. Let’s try using this method call in the next three methods.

Find out the most common employee name in the company

Java
xxxxxxxxxx
1
14
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
3
            Collectors.groupingBy(Employee::getName,Collectors.counting());
4
    Map<String, Long> nameAndCount = processEmployeeToMap(company,repeatingNameCollector);
5
    Long maxCount = Collections.max(nameAndCount.values());
6
    Optional<String> repeat = nameAndCount.entrySet().stream()
7
            .filter(e -> e.getValue().equals(maxCount))
8
            .map(Map.Entry::getKey).findFirst();
9
    String repeatingName = null;
10
    if (repeat.isPresent()) {
11
        repeatingName = repeat.get();
12
    }
13
    return repeatingName;
14
}


Find the highest salaried employee

Let’s do the same refactoring here as well:

Java
xxxxxxxxxx
1
13
 
1
private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
2
    Collector<Employee,?,Map<Employee,Long>> highSalary =
3
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
4
    Map<Employee, Long> employeeAndSalary = processEmployeeToMap(company,highSalary);
5
    Long maxSalary = Collections.max(employeeAndSalary.values());
6
    Optional<Employee> costly = employeeAndSalary.entrySet().stream()
7
            .filter(e -> e.getValue().equals(maxSalary)).map(Map.Entry::getKey).findFirst();
8
    Employee costlyEmployee = null;
9
    if(costly.isPresent()) {
10
        costlyEmployee = costly.get();
11
    }
12
    return costlyEmployee;
13
}


Find the most popular grade/band in the company

Java
xxxxxxxxxx
1
14
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Collector<Employee,?,Map<Band,Long>> popularBandCollector =
3
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
4
    Map<Band, Long> bandAndCount = processEmployeeToMap(company,popularBandCollector);
5
    Band popularBand = null;
6
    Long maxBand = Collections.max(bandAndCount.values());
7
    Optional<Band> popular = bandAndCount.entrySet().stream()
8
            .filter(e -> e.getValue().equals(maxBand))
9
            .map(Map.Entry::getKey).findFirst();
10
    if(popular.isPresent()) {
11
        popularBand = popular.get();
12
    }
13
    return popularBand;
14
}


Find the sum of salaries for all men

Well, we need a different way here, as this one is completely different from the rest of the methods.

Java
xxxxxxxxxx
1
 
1
public static Function<Department, Stream<Employee>> 
2
          allEmployeeInDept = department -> department.getEmployees().stream();
3
 
4
private static Long findSumOfAllMenSalary(Company company) {
5
    return company.getDepartments().stream()
6
            .flatMap(d -> allEmployeeInDept.apply(d))
7
            .filter(e -> e.getGender().equals(Gender.MALE))
8
            .map(Employee::getSalary).mapToLong(Long::longValue).sum();
9
}


Well, we have used the functional components and achieved a better reusable code, but still, this can be cleaned up a bit.


You might have noticed that all these methods need a single value in return, not a list. And, we use optional and some logic to identify that single element. Can we extract that too, let’s try that now.

Java
xxxxxxxxxx
1
 
1
private static <T> T fetchParamsFromMap(Map<T, Long> param) {
2
    return param.entrySet().stream()
3
            .filter(e -> e.getValue().equals(Collections.max(param.values())))
4
            .map(Map.Entry::getKey).findFirst().orElse(null);
5
}


This method returns the key of the biggest param in a map, using generics, this one can be applied to all of our methods

Find out the most common employee name in the company

Java
xxxxxxxxxx
1
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
3
            Collectors.groupingBy(Employee::getName, Collectors.counting());
4
    Map<String, Long> nameAndCount = processEmployeeToMap(company, repeatingNameCollector);
5
    return fetchParamsFromMap(nameAndCount);
6
}


that simple? yes, it is

Find the highest salaried employee

Java
xxxxxxxxxx
1
 
1
private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
2
    Collector<Employee, ?, Map<Employee, Long>> highSalary =
3
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
4
    Map<Employee, Long> employeeAndSalary = processEmployeeToMap(company, highSalary);
5
    return fetchParamsFromMap(employeeAndSalary);
6
}


Find the most popular grade/band in the company

Java
xxxxxxxxxx
1
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Collector<Employee, ?, Map<Band, Long>> popularBandCollector =
3
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
4
    Map<Band, Long> bandAndCount = processEmployeeToMap(company, popularBandCollector);
5
    return fetchParamsFromMap(bandAndCount);
6
}


Done? No, we are not done yet… there is a bit more to do.


In the methods findMostRepeatingNameInCompany, findEmployeeWithHighestSalaryInTheCompany, and findMostPopularBandInCompany, there is still a repeating pattern, we use processEmployeeToMap, and immediately, we call fetchParamsFromMap. Let’s combine it.

Java
xxxxxxxxxx
1
 
1
private static <T> T fetchBestOfMappedEmployees(Company company,
2
                                                Collector<Employee, ?, Map<T, Long>> employeeMapCollector) {
3
    Map<T, Long> mappedResults = company.getDepartments().stream()
4
            .flatMap(department -> department.getEmployees().stream())
5
            .collect(employeeMapCollector);
6
    return mappedResults.entrySet().stream()
7
            .filter(e -> e.getValue().equals(Collections.max(mappedResults.values())))
8
            .map(Map.Entry::getKey).findFirst().orElse(null);
9
}


Now, let’s see how our will methods look:

Find out the most common employee name in the company

Java
xxxxxxxxxx
1
 
1
private static String findMostRepeatingNameInCompany(Company company) {
2
    Collector<Employee, ?, Map<String, Long>> repeatingNameCollector =
3
            Collectors.groupingBy(Employee::getName, Collectors.counting());
4
    return fetchBestOfMappedEmployees(company, repeatingNameCollector);
5
}


Find the highest salaried employee

Java
xxxxxxxxxx
1
 
1
private static Employee findEmployeeWithHighestSalaryInTheCompany(Company company) {
2
    Collector<Employee, ?, Map<Employee, Long>> highSalary =
3
            Collectors.toMap(employee -> employee, Employee::getSalary, (a, b) -> b);
4
    return fetchBestOfMappedEmployees(company, highSalary);
5
}


Find the most popular grade/band in the company

Java
xxxxxxxxxx
1
 
1
private static Band findMostPopularBandInCompany(Company company) {
2
    Collector<Employee, ?, Map<Band, Long>> popularBandCollector =
3
            Collectors.groupingBy(Employee::getBand, Collectors.counting());
4
    return fetchBestOfMappedEmployees(company, popularBandCollector);
5
}


We can even continue to simplify this, we can put the collectors outside and the parent calls can pass the collection, and then we don’t really need this method calls itself.

All code is available on GitHub, and each separate iteration is in a separate commit, please check this out at https://github.com/surajcm/java_fun_extraction_01/commits/master.

Java (programming language)

Published at DZone with permission of Suraj Muraleedharan. See the original article here.

Opinions expressed by DZone contributors are their own.

Trending

  • Health Check Response Format for HTTP APIs
  • Comparing Cloud Hosting vs. Self Hosting
  • What ChatGPT Needs Is Context
  • RBAC With API Gateway and Open Policy Agent (OPA)

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: