Java: Functional Interfaces

Functional Interface: an interface with one abstract method

Lamda Expression

(int x, int y) → {...do something with x and y...}

Lamda vs Method Reference

Lamdas

  • used to “pass functionality” as a parameter (create code block and pass reference as a pointer)
  • parameters require brackets unless single parameter (with no type specified)
  • if no parameters, requires: () ->
  • if no code braces used, return of final statements is assumed
  • if code braces used, must explicitly return if appropriate

Method References

  • similar to lamda expressions
  • used to assign existing code (such as String.startsWith()) to a functional interface
Static MethodsFI1 myLamda = x → Collections.sort(x);
or
FI1 myMethodRef = Collections::sort;
Instance MethodsString myString = “ABC”;
FI1 myLamda = s → myString.startsWith(s);
or
FI1 myMethodRef = myString::startsWith;
Parameter Instance MethodsFI1 myLamda = s → s.isEmpty();
or
FI1 myMethodRef = String::isEmpty;
ConstructorsFI1 myLamda = () → new ArrayList<String>();
or
FI1 myMethodRef = ArrayList::new;

Flavours of functional interface/Lamda

PredicateTakes a parameter and returns true/false
BiPredicateTakes two parameters and returns true/false
ConsumerTakes parameter, does work, returns nothing
BiConsumerTakes two parameters, does work, returns nothing
SupplierTakes NO parameters, generates / reads a value, returns the value
ComparatorTakes parameters, compares them and returns the result
FunctionTakes parameters, does work, returns a value
BiFunctionTakes two parameters, does work, returns a value
UnaryOperatorAs function but parameter and value of same type
BinaryOperatorAs BiFunction but parameters and value of same type
  • Checked exceptions do not work well with Functional Interfaces / Lamdas / Method Refs. Should not be thrown. Catch & thrown runtime exception instead.
  • Lamdas can only reference ‘local variables’ if they are ‘effectively final’ – cannot change after Lamda defined. Can reference parameters, static & instance variables.

Chain / Link Operations

Consumer:

  • consumer1.andThen(consumer2); // performs consumer1 and consumer2 on same input

Function:

  • function1.andThen(function2); //performs consumer1 and consumer2 on same input
  • function1.compose(function2); //performs consumer1 and passes output to consumer2

Predicate

  • predicate1.and(predicate2);
  • predicate1.or(predicate2);
  • predicate1.negate(predicate2);

Optional

  • Optional is a wrapper class that may or may not have a value
  • Can call .isPresent() or .isEmpty() to find out
  • Created by Optional.of(myValue) or Optional.empty()
  • value can be retrieved by get() – exception if not present
  • .orElse(myDefault) returns the value or a default if empty

Streams

Creation

Stream.empty();
Stream.of(1,2,3);
myCollection.stream();
Stream.generate(mySupplierFI); // infinite
Stream.iterate(initialValue, myUnaryOperator); // infinite
Stream.iterate(initialValue, myTerminationPredicate, myUnaryOperator);

Intermediate Operations

s.filter(myPredicate<T>); // drops items that fail predicate test and passes those that pass
s.distinct();             // uses .equals() to drop duplicate entries
s.limit(myCnt);           // takes first myCnt entries
s.skip(myCnt);            //skips myCnt entries
s.map(function<T,R>);     // converts a stream of T to a stream of R
s.flatMap(function<T,Stream<R> >);
    // explodes stream of Ts (with multiple subitems R) into a single stream of R
s.sorted();
s.sorted(comparator<T>);
s.peek(consumer<T>);      //process each item and continue (non-terminating forEach)

Terminal Operations

s.count();
s.min(comparator<T>);
s.max(comparator<T>);
s.findFirst/Any();
s.anyMatch(predicate);   // does any record match the predicate, returns boolean
s.allMatch(predicate);   // do all records match the predicate, returns boolean
s.noneMatch(predicate);  // do no records match the predicate, returns boolean
s.forEach(consumer);     //executes consumer on each item and returns void
s.reduce(initialT, BinaryOp<T, T>);
	// Starts with initialT, performs the binary operation on each item,
	 returns the final T
s.reduce(BinaryOp<T, T>);
	// As above but returns Optional<T>
s.reduce(initialT, BiFunction<T, U>, BinaryOp<T, T>);
	//starts with initialT, performs the binary function on each U
	from stream to convert to T, BinaryOp to combine and returns the final T
s.collect(Supplier<R>, BiConsumer<R, T>, BiConsumer<R,R>);
	// Supplier<R> provides ArrayList/StringBuilder to collect items in.
s.collect(Collector<T,A,R>);
	// uses prebuilt collectors from class java.util.stream.Collectors 

Available Collectors

  • Collectors.joining(“,”); (concatenates values into a single string)
  • Collectors.count(); (returns long)
  • Collectors.maxBy(myComparator);
  • Collectors.minBy(myComparator);
  • Collectors.toList();
  • Collectors.toSet();
  • Collectors.toCollection(mySupplierOfCollection);
  • Collectors.averagingInt/Long/Double();
  • Collectors.summarizingInt/Long/Double();
  • Collectors.toMap(…);
  • Collectors.groupingBy(…);
  • Collectors.partitioningBy(myPredicate, …); (split values into two classes using the predicate)

Lazy Evaluation

  • Underlying data stream still updatable before stream executes
  • Executes when it encounters a “terminal operation”

Primitive Streams

  • Special streams for primitive types: IntStream, LongStream, DoubleStream
  • Methods available to convert between types and to objects (e.g. .mapToDouble())
  • Primitive functional interfaces also available (e.g. IntConsumer, DoublePredicate)