COURSE

Refactoring to Streams Course with Dr Heinz Kabutz

Topics covered at JAVA-REFACT-HZ-03
View Schedule & Book More dates available

Next up:

Overview


Are you an experienced Java developer who is looking to modernise old Java code and make it more maintainable and easier to understand? Join renowned Java Specialists Dr Heinz Kabutz to learn how to restructure an existing body of code without changing its external behaviour.

Join globally renowned Java expert Dr Heinz Kabutz for this hands-on workshop for the busy Java professional who wants to quickly learn and apply new essentials on core Java topics.

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. Since Java 8, we have had a constant stream of improvements to the Java Programming Language that make it easier to craft great Java code.

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.




Online Learning at Skills Matter

This course will be offered virtually over 1 full-day session.

Our virtual courses offer the same expert-led, hands-on experience we've offered since 2013 — only now we’re making it accessible from the comfort of your own home (office).

You'll join Heinz and participants from around the globe in an online classroom where you'll utilise a collaboration tools like Zoom, Slack, and Notion.so to master refactoring of old Java code.


Learn how to:

Students who have successfully completed this course, can expect to:

  1. Know how to refactor to streams and lambdas with and without IDE assistance.
  2. Understand the place of streams and lambdas in the history of the JDK.
  3. Understand functional interface.
  4. Understand why the lambda notation is a shorthand for an anonymous inner class based on a functional interface.
  5. Know the long and short forms that lambdas can take depending on their complexity.
  6. Use method references as a further simplification of certain forms of lambda.
  7. Understand why and how default and static methods in interfaces can use lambdas to improve generality, correctness and readability.
  8. Judge when it might be unsafe to use methods like Map.computeIfAbsent.
  9. Understand the concept of a stream and its relationship to iterable collections.
  10. Know why coding with streams follows the algorithm logic more naturally than using for/while loops.
  11. Create, transform and terminate streams using filters, mappings, matchers, collectors, reducers and more.
  12. Use collectors rather than forEach to build collections from a stream
  13. Use the Optional class to avoid null checks, and how optionals are used with streams.
  14. Know how to handle exceptions in lambdas using sneaky throws (without Lombok).
  15. Understand how functional interfaces, streams and optionals are optimized for the primitive types int, long and double.

About the Author

Heinz Kabutz

Dr Heinz Kabutz has programmed significant portions of several large Java applications and has taught Java to thousands of professional programmers. He is a regular speaker at all the major Java conferences and is the mastermind behind The Java Specialists' Newsletter. Heinz was chosen as a Java Champion by Sun Microsystems, the inventors of Java, for his work in advancing Java.

Programme


Modules/topics covered

  • 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 lambda with method reference
  • removeIf()
    • Exercise: Replace loop with removeIf()
  • Map Compound Methods
    • Exercise: Replace with Compound Map Methods
  • 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

Audience

Audience

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 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 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.

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.