For more QCon posts, see my live blog table of contents. Last year, she talked about creating new apps in Java 8. This year is about migrating to Java 8 for “legacy” code. She showed specific JetBrains IDEA features. I just took notes on the concepts that are IDE agnostic. Presentation will be at bit.ly/refJ8
Usage from audience poll
- About half compile with Java 7
- About half compile with Java 8
- About a quarter (half of Java 8 people) compile with Java 8, but apps still looks like Java 7
Why use Java 8
- Performance improvements in common data structure
- Fork/Join speed improvements
- Changes to support concurrency
- Fewer lines of code – less boilerplate
- New solutions to problems – more elegant/easier to write code
- Minimize errors – ex: Optional avoids NullPointerException
Before refactoring
- Make sure have high test coverage
- Focus on method level changes – tests should still work
- Have performance tests too to ensure not negatively impacting system
- Decide on goals – performance? clearer code? easier to write new code? easier to read new code? use lambdas because team used to it? developer morale?
- Limit the scope – small incremental commits. Don’t make change that will affect whole code base.
Straightforward refactoring of lambda expressions
- Predicate, Comparator and Runnable are common places where might have used anonymous inner classes and can switch to lambdas
- IDEs suggest where can replace code with lambdas (SonarLint does as well)
- Quick win – reduce old boilerplate
- [She didn’t talk about this, but it will make your code coverage go down. Make sure your management doesn’t worry about that!]
- Lose explicit type information when use lambda. Might want to extra to method to make it clearer and call the method from the lambda.
Abstract classes – candidates for lambdas
- See if can use an interface (if single abstract method) so can start using lambdas
- Tag with @FunctionalInterface to make clearer
- Then look at implementers (especially anonymous inner classes) and see if can convert to lambda expressions
Conditional logging
- if (debug) { log.debug() } pattern is a good candidate for logging
- Often people forget to add that conditional so opportunity to defer evaluation for those too via search/replace. Also, protects against conditional not matching log level. if (debug) { log.severe() }. Uh oh!
- Use a default method on the existing logging interface that takes a supplier instead of a String and has the if check
Collections and Streams API
- Turn for loops into collect() or forEach()
- Remember you can call list.forEach() directly without going through a stream
- If do something like get rid of initial list size, test performance before and after to make sure not a needed optimization
- The automatic streams refactoring might not result in shorter code. Think about whether the surrounding code wants to be refactored as well. A more dramatic refactoring. Go back to later. Also, avoid refactoring if and else code since not a simple filter.
- Remember to include method references when converting code too
- Watch for surrounding code. For example, if sorting a list, move into the the stream. Or if iterating over new list, add as a forEach instead of turning into a list.
Optional
- Be careful. Refactor locally.
- Easy to accidentally change something that requires changing a ton of code.
Performance
- Lambdas expressions don’t necessarily perform better than anonymous inner classes. Similarly for streams.
- Using lambdas to add conditional logging is a big quick win performance improvement.
- Streams didn’t need the “initial size of list” optimization
- Adding parallel() to stream operations sometimes performs better and sometimes worse. Simple operation often performs worse in parallel because of overhead of parallelizing.
- Introducing Optionals increases cost.
Java 7 refactorings
- Use static imports
- Reudndant type in diamond operator for generics