Tech Tenets

Tech Tenets

Lambdas and Streams

Subscribe to my newsletter and never miss my upcoming articles

In Java8, some of the things that are added:

  1. lambdas, functional interface, method references -> create function objects
  2. streams -> processing sequence of data elements

The Index

  • Item 42: Prefer lambdas to anonymous class
  • Item 43: Prefer method references to lambdas
  • Item 44: Favour the use of standard functional interfaces
  • Item 45: Use streams judiciously
  • Item 46: Prefer side-effect-free functions in streams
  • Item 47: Prefer Collection to Stream as a return type
  • Item 48: Use caution when making streams parallel

Item 42: Prefer lambdas to anonymous class

function types: interface/abstract class with single abstract method. function objects: instances of function types

Lambda expressions

  1. Limited to functional interfaces (only one method required to be implemented by function objects)
  2. Lambda expressions can be used to make instances of functional interfaces.
  3. Java infers type, sometimes explicitly required
  4. Use generic instead of raw types for type inference, else explicit and verbose code would be needed
  5. Use them only if they make the code clearer and easy to understand
  6. Can not obtain a reference of itself. this refers to enclosing instance

When to use Anonymous class

  1. For abstract classes
  2. If functional object is large, lambda becomes difficult to understand
  3. Want this to refer to newly created instance
  4. Functional type has multiple abstract methods

Lambdas and Anonymous class are not reliable for serialization and deserialization. Avoid them, use private static nested class instead if possible.

Item 43: Prefer method references to lambdas

method references: even more concise way to create function objects than lambda.

Where method references are shorter and clearer, use them; where they aren’t, stick with lambdas.

methodref vs lambda

Item 44: Favour the use of standard functional interfaces

If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.

basic_functional_interface.png These are some of the 43 functional interfaces, the rest are derivations of these.

Use custom functional interface, if:

  1. It will be commonly used and could benefit from a descriptive name.
  2. It has a strong contract associated with it.
  3. It would benefit from custom default methods.
  4. Always annotate your functional interfaces with the @FunctionalInterface annotation.

Don’t write overloadings that take different functional interfaces in the same argument position like the submit() method of ExecutorService.

Item 45: Use streams judiciously

  1. The streams API was added in Java 8 to ease the task of performing bulk operations, sequentially or in parallel. It consist of source stream, >=0 intermediate operation and one terminal operation.
  2. Stream pipelines are evaluated lazily: evaluation doesn’t start until the terminal operation is invoked, and data elements that aren’t required in order to complete the terminal operation are never computed.
  3. The streams API is fluent: it is designed to allow all of the calls that comprise a pipeline to be chained into a single expression
  4. Overusing streams makes programs hard to read and maintain.
  5. Using helper methods is even more important for readability in stream pipelines than in iterative code
  6. Refactor existing code to use streams and use them in new code only where it makes sense to do so, resist the urge.

Use code block

  • want to read and modify local variables in scope
  • return from enclosing method, break, continue, throw

Use Streams

  • uniform transformation of sequences
  • filter sequences
  • combine with a single operation or attribute
  • search on basis of a criterion

Item 46: Prefer side-effect-free functions in streams

  • Streams is based on a functional paradigm. To make the most out of it adopt both the paradigm and the API.
  • Structure the code as a sequence of pure functions (use the output of the previous function as input and don’t use/modify local state).
  • The forEach operation should be used mostly to report the result of a stream computation, not to perform the computation.
  • In order to use streams properly, we have to know about collectors. The most important collector factories are toList, toSet, toMap, groupingBy, and joining.

Item 47: Prefer Collection to Stream as a return type

Initially collection interfaces like Collection, List, Set, Iterator and Arrays were used.

The decision to choose was simple:

  1. Use Collection interfaces
  2. If can’t implement Collection use Iterator
  3. If primitive elements are involved and performance is a concern -> Arrays

Stream added in Java8 complicated the matters a bit.

Stream and Iterable are not compatible with each other even though they have same iterator methods because Stream doesn’t implement Iterable. They can be interchanged by writing trivial adapters.

Collection or an appropriate subtype is generally the best return type for a public, sequence returning method as it would implement both Iterable and Stream.

Return a small enough sequence using ArrayList or HashSet but don’t store a large sequence to later return like a Collection as it has Integer.MAX_VALUE limit. Use Stream or Iterable for large sequences.

Item 48: Use caution when making streams parallel

  • Writing concurrent programs in Java keeps getting easier, but writing concurrent programs that are correct and fast is as difficult as it ever was.

  • Safety and liveness violations are a fact of life in concurrent programming, and parallel stream pipelines are no exception.

  • Parallelizing a pipeline is unlikely to increase its performance if the source is from Stream.iterate, or the intermediate operation limit is used.
  • Performance gains from parallelism are best on streams over ArrayList, HashMap, HashSet, and ConcurrentHashMap instances; arrays; int ranges; and long ranges because they can be accurately and cheaply split into subranges of any desired sizes and divide for parallel work. Stream use spliterator for this. The other reason is the good - to excellent locality of reference which is of much use in efficient parallel processing.

  • The terminal operation should be of reducing nature (like reduce or provided min, max, sum and count) for faster results. collect method isn’t good as combining streams is costly. All others are intermediate candidates.

  • The Stream specification places stringent requirements on the function objects like map, filter and programmer supplied. Adhere to them before parallelising else safety and correct result is compromised.
  • Under the right circumstances, it is possible to achieve near-linear speedup in the number of processor cores simply by adding a parallel call to a stream pipeline. Always try thoroughly before choosing.

This is it!

 
Share this
Proudly part of