- Standard Functional Interfaces
- Consumer takes a value and returns nothing. Consumer has accept method
- DoubleConsumer takes a double primitives and does not return anything. It does not extend consumer interface and hence cannot be used in forEach. forEach method needs a consumer.
- BiConsumer takes two parameters of generic type and returns nothing.
- ObjDoubleConsumer takes first parameter generic and second double primitve.
- ObjectDoubleConsumer DOES NOT exist, instead it is called as ObjDoubleConsumer.
- ObjectIntConsumer DOES NOT exist, instead it is called as ObjIntConsumer.
Supplier takes nothing but returns a value. Supplier has a get method.
Supplier<Double> takes no argument and has a get method and returns the object Double.
DoubleSupplier takes no argument and has method getAsDouble and returns primitive double.
LongSupplier takes no argument and has method getAsLong and returns primitive long.
BooleanSupplier takes no argument and has method getAsBoolean and returns primitive boolean.
Function takes something and returns something. Function has apply method.
BiFunction interface takes two different generic values and returns a generic value, taking a total of three generic arguments.
LongFunction<R> accepts the primitive long and returns the generic.
DoubleFunction<R> takes a double value and returns a generic result, taking exactly one generic argument.
IntFunction accepts the primitive int and returns the generic.
ToDoubleFunction takes exactly one generic value and returns a double value, requiring one generic argument.
ToIntFunction, ToIntBiFunction interface takes two generic values and returns an int value, for a total of two generic arguments.
ToInt, ToLong, ToDouble prefix on functional interface means they return a primitive of that type.
IntSomething, LongSomething, DoubleSomething that accepts the argument of primitive type.
Predicate takes one parameter and returns boolean. Predicate has test method.
Predicates returns primitive boolean and not Boolean.
IntUnaryOperator doesn't allow generics and has method applyAsInt() which accepts primitve int and retunrs primitive int.
LongUnaryOperator takes a primitive long and returns a primitive long.
UnaryOperator<A> extends Function<A,A> {}
.BinaryOperator<T> takes two parameters of a generic type and returns the same type.
BinaryOpertor<A> extends Functiona<A,A,A> {} BinaryOperator<Long> takes two Long arguments and returns a Long value.
DoubleToLongFunction takes primitive double and returns primitive long.
- Streams
- The correct method to obtain an equivalent sequential stream of an existing stream is sequential(), which is inherited by any class that implements BaseStream.
- Java allows you to operate on a stream only once. if tried to access again it throws an IllegalStateException because the stream has already been used up once.
- Stream cannot be used again once it is executed.
- Stream.generate(() -> ‘a’); generates infinite stream. infinite streams should generally have a limit operation or the code never completes.
- skip(2) will skip the first 2 elements from the stream.
- IntStream().parallel returns an IntStream.
- IntStream().boxed().parallel() returns an Stream.
- Stream.concat(Stream1, Stream 2, … ). concat method accepts the reference of stream and not IntStream.
- There is no “sort” method on Stream. the method is called as sorted(Comparator).
- Stream.of(1, 2) method returns a generic stream and not a IntStream. So sum() terminal method is not available.
- mapToInt() this method which is intermediate operation needs a ToIntFunction.
- There isn’t a mapToObject() in the stream API. Note there is a similarly named mapToObj() method on IntStream.
- The generate() and iterate() sources return an infinite stream.
- The peek() method is an intermediate operation. if there is no terminal operation after peek(), the stream pipeline is not executed, so the peek() method is never executed, and nothing is printed. Stream always needs a terminal operation in the end for intermediate operation to execute.
- findAny method is capable of returning any element of the stream regardless of whether it is serial, parallel, ordered or unordered.
- findFirst() always returns first element on the ordered stream, regardless it is serial or parallel. On unordered element it is free to return any element.
- DoubleStream
- average() method on DoubleStream returns OptionalDouble. This object declares a getAsDouble() method rather than a get() method.
- max() method on DoubleStream returns OptionalDouble.
- count() gives the long number and sum() gives the double number 0.0
- IntStream has a min() terminal method that returns. But if you have regular stream of integers then min() method will need a comparator parameter.
- Optional
- Optional.empty() returns empty optional.
- Optional.of(null) will compile but throw NPE exception during runtime.
- Optional.ofNullable(null) will not throw NPE, but will return true opt.isPresent().
- Optional.ofNullable(null) should be used if you think value can be null as well.
- if the stream is empty, the optional is also empty. when trying to get an value from empty optional we get NoSuchElementException.
- IntStream.empty().average().getAsDouble(); NoSuchElementException
- Map & FlatMap
- Both map() and flatMap() can be applied to a Stream and they both return a Stream.
- The difference is that the map operation produces one output value for each input value,
- Whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.
- The flatMap() method works with streams rather than collections. flatMap() method should return a stream.
- Reduction
- A terminal operation where a single value or object is generated by reading each element in the prior step in a stream pipeline.
- sum(), min(), max(), count() are the examples of reduce() operation.
- reduce() explicitly asks you to specify how to reduce the data that made it through the stream.
- The reduction is parallel, but since the accumulator and combiner are well-behaved (stateless and associative), the result is consistent.
- Moreover the first parameter of reduce method is applied to all the elements of the list.
- Accumulator in serial or parallel reduction should be associative and stateless.
- Accumulator is considered as stateful if it is accessing the instance variable.
- GroupingBy
- The groupingBy() collector always returns a Map (or a specific implementation class of Map).
- To get Map>, you can group using a Function that returns an Integer such as s.collect(groupingBy(String::length)).
- To get the Map>, you need to group using a Function that returns a Boolean and specify the type, such as s.collect(groupingBy(String::isEmpty, toCollection(HashSet::new))).
- The partitioningBy() method automatically groups using a Boolean key. However, we can also have a Boolean key with groupingBy(). For example, we could write s -> s.length() > 3.
- Lambda Expressions
- they are basically short hand implementations of interface, but prohibit usage of this keyword.
- the interface must only contain one abstract method and optionally can be annotated with Functional annotation.
- Statefull lambda expressions should be avoided with both serial and parallel streams because they can lead to unintended side effects.
- One way to avoid modifying a List with a statefull lambda expression is to use a collector that outputs a List.
- Method Reference
- Constructors and instance method can be used as method references
- String::charAt cannot be used as method reference for the Function. Because function accepts one parameter and returns something. String::charAt can be applied to a BiFunction, so example is test.charAt(1), String=test, 1=Integer, ‘e’ that would be returned back is Character.
- Like a lambda, method references use type inference.
- Both lambda and method references can accept a parameter and be executed later.
- One big difference is with a lambda like: () -> s.charAt(3). The s variable must be final or effectively final variable in both lambdas and method references. However, there isn’t a way to use the hard-coded number in a method reference.
- Since the lambda references an effectively final variable, the method reference needs to as well.
- When assigned to a local variable, var cannot be used because there is not enough information to infer the type so you cannot say var test = ArrayList::new;
- Things happening in parallel
- The parallel() method should be applied to a stream, while the parallelStream() method should be applied to a Collection
- Note that calling parallel() on an already parallel stream is unnecessary but allowed.
- JVM will fall back to single threaded process if all the conditions for performing the parallel reductions are not met.
- To execute a parallel reduction with the collect() method,
- the stream or collector must be unordered,
- the collector must be concurrent, Collectors.groupingByConcurrent must be used
- and the stream must be parallel
- Certain stream operations, such as limit() or skip(), force a parallel stream to behave in a serial manner.
- The stream must be explicitly set to be parallel in order for the JVM to apply a parallel operation.
- Parallel stream operations are not synchronized. It is up to the developer to provide synchronization or use a concurrent collection if required. groupingByConcurrent can be used.
- The BaseStream interface, which all streams inherit, includes a parallel() method. Of course, the results of an operation may change in the presence of a parallel stream, such as using a problematic (non-associative) accumulator.
- Applying forEachOrdered() on a parallel stream forces the terminal operation to be performed in single thread. So it could likely get slower.
- Intermediate operations can still take an advantage of parallel stream as the forEachOrdered is a terminal operation and will be applied at very end.
- For performing parallel reduction with the collect() method which takes the Collector argument, the collector argument must be marked as concurrent, unordered. And the stream must be parallel.