Java Refactoring Examples

Introduction

Java refactoring makes maintenance easier. Java refactoring is the process of restructuring existing Java code without changing its external behavior. 

Read time: 6 minutes

What is Refactoring?

Refactoring involves making incremental changes to the code to improve its structure, readability, and maintainability without altering its functionality. 

Java code refactoring examples

Let’s take a look at three Java refactoring examples from our jSparrow rule “Replace Nested Loops with flatMap”. This rule is highly queried (according to our website analytics).

Java refactoring Replace Nested Loops with flatMap Banner. Coffee beans inte background.

Java Refactoring Example 1: Iterating over nested collections

Pre:

 List<List<User>> groups = findGroups();
    groups.forEach(group -> {
        group.forEach(user -> {
            sendAward(user);
        });
    });

Post

   List<List<User>> groups = findGroups();
    groups.stream()
        .flatMap(group -> group.stream())
        .forEach(user -> {
            sendAward(user);
        });

The two code snippets achieve the same result, but the refactored one is more efficient. Here’s why:

It is more concise: 

The stream API allows you to express operations on collections more concisely. In the first example, the flatMap operation is used to flatten the nested lists, making the code shorter and more expressive compared to nested loops in the refactored example.

It is easier to read: 

Streams often make the code more readable by providing a higher-level abstraction. The pipeline of operations (such as flatMap and forEach) can be easier to understand, especially when dealing with complex transformations.

It is less error-prone because it is declarative:

The stream example is more declarative. It describes what you want to achieve (flattening the list of lists and sending awards), while the nested loop example is more imperative, specifying how to achieve it. Declarative code is often considered more expressive and less error-prone.

It can be parallelized: 

Stream and flatMap operations can potentially be parallelized, offering performance benefits when dealing with large datasets. While the parallelization aspect might not be explicitly utilized in this example, using streams can make it easier to switch to parallel processing if needed.

Java Refactoring Example 2: Nested Stream::forEach

Pre

orders.stream()
    .map(Order::getOrderItems)
    .forEach(items -> {
        items.forEach(item -> {
            Product product = item.getProduct();
            int quantity = item.getQuantity();
            add(product, quantity);
        });
    });

Post

orders.stream()
    .map(Order::getOrderItems)
    .flatMap(items -> items.stream())
    .forEach(item -> {
        Product product = item.getProduct();
        int quantity = item.getQuantity();
        add(product, quantity);
    });

The refactored code snippet using flatMap is generally considered better than the first one due to the following reasons:

It is more flat, meaning it is cleaner and more concise: 

The map operation in the first code snippet produces a stream of lists of OrderItem, and then a nested forEach loop is used to iterate over these lists. In contrast, flatMap in the refactored code snippet flattens the stream, directly providing a stream of OrderItem objects. This results in cleaner and more concise code.

It reduces nested code:

Using flatMap reduces the nesting level in the code. The refactored example is a single-level iteration over the OrderItem objects, making the code more readable and avoiding unnecessary indentation.

The code’s intention is more clear:

flatMap expresses the intention more clearly. It explicitly conveys the idea that you want to flatten a nested structure, making the code more declarative and easier to understand.

It can be parallelized: 

The use of flatMap can enable parallelization opportunities because it provides a more sequential stream of elements. This can be advantageous for performance improvements, especially when dealing with large datasets.


Java Refactoring Example 3: Deep nested loops

Pre

 matrix3.stream().filter(row -> !row.isEmpty()).forEach(row -> {
        row.stream().filter(col -> !col.isEmpty()).forEach(col -> {
            col.stream().filter(cell -> !cell.isEmpty()).forEach(cell -> {
                cell.stream().filter(element -> !element.isEmpty()).map(element -> element.substring(0, 1)).forEach(element -> {
                    System.out.print(element);
                });
            });
        });
    });

Post

matrix3.stream().filter(row -> !row.isEmpty()).flatMap(row -> row.stream())
    .filter(col -> !col.isEmpty())
    .flatMap(col -> col.stream())
    .filter(cell -> !cell.isEmpty())
    .flatMap(cell -> cell.stream())
    .filter(element -> !element.isEmpty())
    .map(element -> element.substring(0, 1))
    .forEach(element -> {
        System.out.print(element);
    });

Like the previous examples, the refactored code snippet using flatMap which reduces nesting, makes it more expressive, makes the intent clear, and is more concise and efficient. 

Additionally, the refactored code snippet allows you to chain the stream operations in a more fluent and readable way. Each step in the pipeline is separated, making it easier to follow the data transformations.

That said, it’s essential to consider your team’s context and preferences. Some developers may find the first code more straightforward to grasp, especially if they are more accustomed to traditional practices. In the end, readability and maintainability are crucial factors, and the best choice might depend on the specific requirements of your project and team.

Conclusion

Software development is ever-evolving and refactoring is a crucial practice for maintaining code health. Without the skillset and the wide range of tools, software maintenance will be increased and the technical debt will stack up.

Whether you are a seasoned developer or a novice, the ideal time to begin improving your code through refactoring is now. If you have no clue where to begin, We recommend starting with Oracle’s Refactoring Java series and building up your skillset from thereon. 

To help seasoned developers deal with large codebases and open sources, we offer our refactoring tool jSparrow. Next time you feel confused or overwhelmed while working with a large code base feel free to try jSparrow refactoring. Thousands of developers use jSparrow’s free version in their daily projects and 900+ of them would rate jSparrow as useful. The above refactorings can be tested within jSparrow’s diff-view feature for free. You can find jSparrow in your Eclipse IDE market or install it directly into your Eclipse IDE.