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:
Map.computeIfAbsent
Optional
class to avoid null checks, and how optionals are used with streamsOur 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.
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.
We have several options for you to join this 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.
Please contact us if you have any questions.
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.
Please contact us if you have any questions.
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.
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.
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.
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:
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.
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.
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.