Running on Java 24-ea+24-2960 (Preview)
Home of The JavaSpecialists' Newsletter

Refactoring to Streams Course

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

Using Java 17

Duration of 1 Day


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.

Refactoring to Streams Enquiry


Prerequisites

This course is ideally suited to the professional Java programmer who would like to learn how to apply Java Lambdas and Streams to their code base.

Pricing Options

We have several options for you to join this course:

1. Virtual In-house Course:

Presented via video conference to your team of programmers by the author of the course. Price is €2976 for up to 10 students, above that is an additional €270 per student.

  • Example 1: Course with 8 students taught remotely via video conference, price is €2976. Price per student is €372.
  • Example 2: Course with 12 students taught remotely via video conference, price is €3516. Price per student is €293.
  • Example 3: Course with 24 students taught remotely via video conference, price is €6756. Price per student is €281.

Please contact us if you have any questions.

2. In-person In-house Course:

Presented at your company in-person by one of our Certified JavaSpecialist Instructors. Price is €4380 for up to 10 students, above that is an additional €400 per student, plus the travel expenses of the instructor. Note that for in-person in-house courses, we need a minimum of three consecutive training days.

  • Example 1: Course with 8 students taught on-site at your company, price is €4380. Price per student is €547.
  • Example 2: Course with 12 students taught on-site at your company, price is €5180. Price per student is €431.
  • Example 3: Course with 18 students taught on-site at your company, price is €7580. Price per student is €421.

Please contact us if you have any questions.

2. Open Enrollment Classroom Course:

We occasionally offer this course as a classroom course in Chania on the Island of Crete. Price for the course is €949 per student.

We also offer this course as an open enrollment live remote course that you can attend from anywhere. Price is €649 per student.

Please contact us if you have would like to make a booking or if you have any questions.

4. Self-Paced Course:

This course is also available as a self-paced course.

Please contact us if you have any questions.

* Prices exclude EU VAT and withholding taxes where applicable. Please contact us for an exact quote for your country.

Open Courses

All our courses are offered as in-house courses. Please contact us on heinz@javaspecialists.eu.

* Price is excluding EU VAT where applicable. Please contact us for an exact quote for your country.

Detailed Outline

The emphasis of this training workshop is to learn how to apply modern Java constructs to an existing code base. We will thus keep theoretical instruction to a minimum, but are always happy to answer any questions that might come up. Here is our outline:

  • Introduction
  • Refactoring
  • Inspecting Code with IntelliJ IDEA
  • Java Language Changes
  • Default Methods in Interfaces
    • Exercise 1: Replace with List.sort
  • Static Methods in Interfaces
    • Comparator.comparing
    • Functional Interfaces
  • Lambdas
    • Converting an Anonymous Type to Lambda Syntax
    • Statement vs Expression Lambda
    • Exercise: Replace anonymous type with lambda
  • Method References
    • Exercise: Replace lambda with method reference
  • Iterable and Map forEach()
    • Exercise: Replace loop with forEach()
  • removeIf()
    • Exercise: Replace loop with removeIf()
  • Map Compound Methods
    • Exercise: Replace with Compound Map Methods
  • Streams
    • Stream.all/any/noneMatch()
      • Exercise: Replace with all/any/noneMatch
    • Stream.map() and collect()
      • Exercise: Replace with Map.collect()
    • Collectors.toCollection()
      • Exercise: Replace with map() and Collectors.toCollection()
    • Stream.filter()
      • Exercise: Replace with map(), filter(), collect()
    • Collectors.toMap()
      • Exercise: Replace with stream(), collect(), Collectors.toMap()
    • Stream.reduce()
      • Exercise: Replace with stream(), map(), reduce()
    • Stream.flatMap()
      • Exercise: Replace with flatMap()
    • Optional, findFirst(), findAny()
      • Exercise: Replace with findFirst() or findAny()
    • groupingBy(), mapping()
      • Exercise: Replace with collect(), groupingBy() and mapping()
  • Checked Exceptions
    • Handling checked exceptions with sneaky throw
    • Exercise: Handling checked exceptions with ThrowingFunction
  • Conclusion

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book

110% Money-back Guarantee

Should you not be satisfied with the quality of the training or the delivery, we will gladly refund you 100% of the course fees. This needs to be brought to our attention within the first 4 hours of the course and a chance should be given to correct whatever you are not satisfied with. If you are still not satisfied, we will refund you 100% of the course fees, plus we will pay our own travel expenses. The training material then remains the property of JavaSpecialists.EU.


Cancellation Policy

If the course is cancelled more than two weeks before the start of the course, a 10% cancellation fee of the fees will apply, plus any non-refundable travel expenses incurred by the trainer.

If the course is cancelled within two weeks of the start of the course, a 50% cancellation fee of the fees will apply, plus any non-refundable travel expenses incurred by the trainer.

No refund will be given to cancellations during the course.


Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...