Contents

Modern Java in Action 4 - refactoring and testing

Refactoring, testing

Refatoring

Here are three simple refactorings that can be used if you want to migrate your codebase to Java 8 (or higher). You may want to do so for many different reasons (readability, conciseness, code reuse, security).

  • Refactoring anonymous classes to lambda expressions
  • Refactoring lambda expressions to method references
  • Refactoring imperative-style data processing to streams

Recommended paper about automatic refactoing of pre-Java 8 codebases into Java8: Lambda Refactoring

Gotchas

  • different meaning of this and super (in lambda this refers to enclosing class, in inner class - to itself)
  • lambdas cannot (and anonymous inner classes can) shadow enclosing class variables

Code flexibility

Question: when to introduce functional interfaces?

deferred exectution

One usecase is for deferred execution; for example: this code exposes interrnal isLoggable

1
2
3
if (logger.isLoggable(Log.FINER)) {
    logger.finer("Problem: " + generateDiagnostic());
}

so one can do

1
logger.log(Level.FINER, "Problem: " + generateDiagnostic());

and in order to not evaluate the logged message on each call, we provide a Supplier<String>:

1
2
3
4
5
6
7
8
// log method
public void log(Level level, Supplier<String> msgSupplier) {
    if(logger.isLoggable(level)){
        log(level, msgSupplier.get());              1
    }
}
// usage
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());

design patterns

The below patterns can be cleanly expressed with lambdas:

  • Strategy - instead of creating behaviors as instances of specific abstract class or interface implementation and using them as constructor parameter in a class that uses specific strategy, you can pass a single lambda expression
  • Template method - instead of defining abstract method for changing (moving part) fragment of a method and overriding the change in subclasses, you can pass lambda expression
  • Observer - registering an observer doesn’t require creation of Observer implementations; it can be done by passing a lambda that will be called when observable wants to notify observers (good for simple behaviors)
  • Chain of responsibility - each class that will take part in forming a chain would do its work and then call successor’s handling method; this can be easily expressed as function composition
  • Factory - you create objects without exposing constructor

Testing lambdas

Here are some hints:

  • focus on testing core lambdas behaviors
  • extract complex lambdas to methods so that they can be separately tested
  • when testing higher order lambdas, write tests that work with different lambdas
  • stacktraces are easier to read when they refer methods than when they use lambdas
  • use peek() to log data from in the middle of the pipeline