Refactoring to Streams 2.1

Modernizing old Java code to make it easier to understand and more maintainable

Java celebrated its 25th birthday in 2020. Code written in 1995 stilll runs today, without even having to recompile it. This is one of the biggest reasons why Java has had such success in enterprises.

Over the years, we have seen lots of improvements to make Java code easier to work with. Inner classes came in Java 1.1. A proper collection framework joined us in Java 1.2. Java 5 gave us better type safety with generics.

But the biggest improvement for Java programmers came in Java 8 with Streams and Lambdas. We can now write Java code in the declarative style, rather than imperative. This expresses better the "what", rather than the "how" of the program logic.

Since Java 8, we have had a constant stream of improvements to the Java Programming Language. Records, sealed classes, pattern matching, local variable type inference, and many more. They all serve to make it easier to craft great Java code.

Unfortunately a lot of Java code bases are still stuck in the dark ages of Java 6. This needs refactoring.

"Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior." - Martin Fowler

In this one-day course we learn how and when to "refactor", focusing on the biggest improvement: Java Streams. However, we also show what else is new in the Java Programming Language.

This is a very hands-on course. Each section has exercises where we get to refactor an existing code base of a large ERP system with 330k LOC.

Here are some of the many things we will cover during this workshop:

  • How to refactor to streams and lambdas with and without IDE assistance
  • The place of streams and lambdas in the history of the JDK
  • What is a functional interface?
  • The four core functional interface types found in the java.util.function package
  • How the lambda notation is a shorthand for an anonymous inner class based on a functional interface
  • The long and short forms that lambdas can take depending on their complexity
  • Method references as a further simplification of certain forms of lambda
  • How default and static methods in interfaces can use lambdas to improve generality, correctness and readability
  • When it might be unsafe to use methods like Map.computeIfAbsent
  • The concept of a stream and its relationship to iterable collections
  • Why coding with streams follows the algorithm logic more naturally than using for/while loops
  • How to create, transform and terminate streams using filters, mappings, matchers, collectors, reducers, etc
  • Why we should use collectors rather than forEach to build collections from a stream
  • Using the Optional class to avoid null checks, and how optionals are used with streams
  • How to handle exceptions in lambdas using sneaky throws (without Lombok)
  • How functional interfaces, streams and optionals are optimized for the primitive types int, long and double

Who should take this course?

Our programmer stares at the Java code written a decade ago. The logic is all back to front. Eyes dart back and forth as they try to grasp the imperative control flow.

Java Streams and Lambdas promised to make this type of code flow better. But the code is old, very old. The original author moved on long ago. Why touch something that works? Or does it?

Some more staring ...

public boolean areFields(Collection<String> fieldNames) {
    if (fieldNames == null) return false;
    for (String fieldName: fieldNames) {
        if (!isField(fieldName)) return false;
    }
    return true;
}

Translated symbol-for-symbol into English, this reads: "For each element of type String that is called fieldName and that comes from the fieldNames parameter do the following: if not the method call isField taking as parameter the fieldName, then immediately return false and if we get to the end of the for loop and we have not returned false, then return true.

Hmm, no wonder programmers get paid so much. What did the author mean? Ahh, light goes on. They wanted to make sure that isField(fieldName) is true for all items. Instead of this tricky boolean logic, a quick refactoring to use streams.

public boolean areFields(Collection<String> fieldNames) {
    if (fieldNames == null) return false;
    return fieldNames.stream().allMatch(this::isField);
}

It now reads: return whether all items in the stream match the predicate this::isField. Crystal clear.

A bit later this gem appears:

public Map<String, TreeSet<String>> getEntitiesByPackage(
       Set<String> packageFilterSet, Set<String> entityFilterSet) {
   Map<String, TreeSet<String>> entitiesByPackage = new HashMap<>();
   // put the entityNames TreeSets in a HashMap by packageName
   for (String entityName : this.getEntityNames()) {
       ModelEntity entity = this.getModelEntity(entityName);
       String packageName = entity.getPackageName();
       if (UtilValidate.isNotEmpty(packageFilterSet)) {
           // does it match any of these?
           boolean foundMatch = false;
           for (String packageFilter : packageFilterSet) {
               if (packageName.contains(packageFilter)) {
                   foundMatch = true;
               }
           }
           if (!foundMatch) {
               continue;
           }
       }
       if (UtilValidate.isNotEmpty(entityFilterSet)
               && !entityFilterSet.contains(entityName)) {
           continue;
       }
       TreeSet<String> entities =
               entitiesByPackage.get(entity.getPackageName());
       if (entities == null) {
           entities = new TreeSet<>();
           entitiesByPackage.put(entity.getPackageName(), entities);
       }
       entities.add(entityName);
   }
   return entitiesByPackage;
}

Riiiight, this is going to be fun. Boolean logic, two continue statements in the middle of the loop. After spending some time on the code, and extracting the matching logic into methods, it looks like this:

public Map<String, TreeSet<String>> getEntitiesByPackage(
       Set<String> packageFilterSet, Set<String> entityFilterSet) {
   return getEntityNames().stream()
           .map(this::getModelEntity)
           .filter(entity -> packageFilter(entity, packageFilterSet))
           .filter(entity -> entityFilter(entity, entityFilterSet))
           .collect(Collectors.groupingBy(
                   ModelEntity::getPackageName,
                   Collectors.mapping(ModelEntity::getEntityName,
                           Collectors.toCollection(TreeSet::new))));
}

Only 11 lines of code instead of 38, with logic that is clearer to understand, utilizing streams and lambdas.

This is the type of fun we have in the Refactoring to Streams Course, ripping apart old dusty Java code and then reassembling it in a coherent and logical order.

Each section of the course has exercises that we need to complete. The refactorings above are two examples of how we can improve old Java code with Java Streams. There is much more. We also learn how we can manage checked exceptions and local variable access.

Your Instructor


Dr Heinz M. Kabutz
Dr Heinz M. Kabutz

Heinz Kabutz is the author of The Java Specialists’ Newsletter, a publication enjoyed by tens of thousands of Java experts in over 150 countries. He has been a Java Champion since 2005.


Frequently Asked Questions


How long do I have access to a fully paid course?
How does lifetime access sound? After enrolling, you have unlimited access to this course for as long as you like - across any and all devices you own. Furthermore, if we move to another platform, we will offer you a free transfer of your account for all the courses that you have purchased.
Can I pay via PayPal?
Yes, you can for outright purchases, but not for recurring payments such as paying by installments.
Can I get back the EU VAT?
Absolutely. First off, be sure to enter the VAT number in the appropriate field during the payment process. Then contact Teachable and ask them to reimburse you the VAT.
May I share my login details with my colleagues?
Unfortunately not. The terms of usage are for a single license. Teachable tracks your progress through the curriculum, so you won't know how much you have watched. We offer discounts on bulk licenses by one company. Please contact us for bulk licensing.
May we use the course for running in-house courses?
You may, as long as each of the students in the class has a valid license for that course. For example, some companies run lunch time Design Patterns study groups using our material. This is an effective way to learn. Please contact us for bulk licensing.
What if I am unhappy with the course?
We would never want you to be unhappy! If you are unsatisfied with your purchase, contact us in the first 14 days to tell us what you are unhappy about, and we will give you a full refund and deregister you from the course.
When does the course start and finish?
The course starts now and never ends! It is a completely self-paced online course - you decide when you start and when you finish. We do recommend putting time aside and setting goals to complete the course.
Can I watch the course offline during my commute?
Teachable have an iOS app that lets you watch offline. Android is not supported unfortunately.

Get started now!