Java 8 Flashcards
Behavior parameterization
Behavior parameterization is a useful pattern to easily adapt to changing requirements and saves on engineering efforts in the future
This is the ability for a method to take multiple different behaviors as parameters and use them internally to accomplish different behaviors.
Behavior parameterization steps
- Model your selection criteria (predicate):
public interface ApplePredicate{boolean test (Apple apple); }} - Now declare multiple implementations of ApplePredicate to represent different selection criteria
This is related to the strategy design pattern
Strategy pattern
strategy design pattern lets you define a family of algorithms, encapsulate each algorithm (called a strategy), and select an algorithm at run-time.
Verbose language
the programmer must write a lot of code to do minimal job
Boilerplate code
boilerplate refers to sections of code that have to be included in many places with little or no alteration.
Examples of methods that can be parameterized with different behaviors in Java API
The Java API contains many methods that can be parameterized with different behaviors, which
include sorting, threads, and GUI handling.
Lambda expression
A lambda expression can be understood as a concise representation of an anonymous function that can be passed around: - Anonymous— it doesn’t have an explicit name like a method would normally have: less to write and think about! - Function— lambda isn’t associated with a particular class like a method is.But like a method, a lambda has a list of parameters, a body, a return type, and a possible list of exceptions that can be thrown. - Passed around— A lambda expression can be passed as argument to a method or stored in a variable. - Concise— You don’t need to write a lot of boilerplate like you do for anonymous classes
Why lambda expresions
code will be clearer and more flexible.
- you no longer have to write clumsy code using anonymous classes to benefit from behavior parameterization!
-Lambda expressions let you provide the implementation of the abstract method of a functional interface directly inline and treat the whole expression as an instance of a functional interface (more technically speaking, an instance of a concrete implementation of the functional interface).
You can achieve the same thing with an anonymous inner class, although it’s clumsier
Where can you use lambdas
You can use a lambda expression in the context of a
functional interface.
filter(inventory, (Apple a) -> “green”.equals(a.getColor()));
In the code shown here, you can pass a lambda as second argument to the method filter because it expects a Predicate, which is a functional interface
Functional interface. Examples from Java Api
= an interface that specifies exactly one abstract method.
Examples: Comparator, Runnable, ActionListener, Callable
@FunctionalInterface - used to indicate that the interface is intended to be a functional interface.
Why we need functional interfaces?
Functional interfaces are useful because the signature of the abstract method can describe the
signature of a lambda expression.
Function descriptor
The signature of the abstract method of the functional interface essentially describes the
signature of the lambda expression. We call this abstract method a function descriptor
Lambdas in practice
+ behavior parameterization
+ execute around pattern -the setup and cleanup phases are always similar and surround the important code doing the processing. This is called the execute around pattern,
New functional interfaces in Java 8
Most popular:
- Predicate - need to represent a boolean expression that uses an item
- Consumer - when perform some operations on a item
- Function - when maping information from an input object to an output
Predicate T -> boolean Consumer T -> void Function T -> R Supplier () -> T UnaryOperator T -> T BinaryOperator (T, T) -> T Bi(Predicate/Consumer/Function)
Boxing, Unboxing, Autoboxing
Performance cost
in Java there’s a mechanism to convert a primitive type into a corresponding reference type. This mechanism is called boxing.
The opposite approach (that is, converting a reference type into a corresponding primitive type) is called unboxing.
Java also has an autoboxing mechanism to facilitate the task for programmers: boxing and unboxing operations are done automatically.
But this comes with a performance cost. Boxed values are essentially a wrapper around primitive types and are stored on the heap. Therefore, boxed values use more memory and require additional memory lookups to fetch the wrapped primitive value.
Type inference
Example
Type inference refers to the automatic deduction of the data type of an expression in a programming language.
List listOfStrings = new ArrayList<>();
lambda: a -> “green”.equals(a.getColor())
Lambda Type checking
The type of a lambda is deduced from the context in which the lambda is used.
The type expected for a lambda expression is called the target type.
Lambda Type inference
The Java compiler deduces what functional
interface to associate with a lambda expression from its surrounding context (the target type),
meaning it can also deduce an appropriate signature for the lambda because the function
descriptor is available through the target type.
a -> “green”.equals(a.getColor())
Note that sometimes it’s more readable to include the types explicitly and sometimes more readable to exclude them.
Lamda syntax
(parameters) -> expression
or
(parameters) -> { statements; }
When type is infered (round parantheses can be ommited):
paramenter -> expression
Lambda Using local variables
Lambdas are allowed to capture (that is, to reference in their bodies) instance variables and static variables without restrictions.
But local variables have to be explicitly declared final or are effectively final
WHY THIS RESTRICTION? Case 1: public static void repeatMessage(String text, int count) { Runnable r = () -> { for (int i = 0; i < count; i++) { System.out.println(text); } }; new Thread(r).start(); } PROBLEM: Lambda expression may run long after the call to repeatMessage has returned and the parameter variables are gone
Case2: vois someMethod(){ int a; Runnable r = () -> {a = random();} new Thread(r).start(); new Thread(r).start(); } If two threads update var a at the same time, its value is undefined.
Method references
- shorthand for lambdas calling only a specific method.
if a lambda represents “call this method directly,” it’s best to refer to the method by name rather than by a description of how to call it.
Method references let you reuse an existing method implementation and pass it around directly.
For example,
Apple::getWeight is a method reference to
(Apple a) -> a.getWeight().
Remember that no brackets are needed because you’re not actually calling the method
Constructor references
You can create a reference to an existing constructor using its name and the keyword new as
follows: ClassName::new.
It's important that lambda matches apropiate signature, for example: ZERO argument constructor: Suplier s = Apple::new // () -> new Apple() ONE argument constructor: Function f = Apple::new // (Integer weight) -> new Apple(weight) TWO arg constructor: BiFunction bf = Apple:new // (Integer weight, String color) -> new Apple(weight, color);
Streams
Stream is a sequence of elements from a source that supports data processing operations.
- Sequence of elements — Like a collection, a stream provides an interface to a sequenced set of
values of a specific element type. - Source —Streams consume from a data-providing source such as collections, arrays, or I/O resources.
- Data processing operations— Streams support database-like operations and common operations
from functional programming languages to manipulate data, such as filter, map, reduce, find,
match, sort, and so on. Stream operations can be executed either sequentially or in parallel.
Advantages Streams
Streams API in Java 8 lets you write code that’s
Declarative— More concise and readable
Composable— Greater flexibility
Parallelizable— Better performance,
Can be mentioned 2 most important characteristcs:
Pipelining
Internal Interation - due to this, the Streams API can decide to run your code in parallel. Using external iteration, this isn’t possible because you’re committed to a single-threaded step-by-step sequential iteration.
- Streams lets you manipulate collections of data in a declarative way
- Streams can be processed in parallel transparently, without you having to write any multithreaded code! (To exploit a multicore architecture)
Collections VS Streams
Collections are about data; streams are about computations.
BOTH are sequence of elements
COLLECTIONS are computed at construction, STREAMS are computed at iteration
COLLECTIONS - external iteration(ex: using
for-each), STREAMS - internal iteration
COLLECTIONS - finite size, STREAMS - infinte size
STREAMS traversed only one. After that a stream is said
to be consumed.
*Collections first complete/create the collection, then is open to some operation (collection of all prime numbers -> a program loop that forever computes a new prime, dding it to the collection)
Stream’s elements are computed on demand.
Streams- 2 most important characteristcs
Pipelining— Many stream operations return a stream themselves, allowing operations to be chained
and form a larger pipeline. This enables certain optimizations that we explain in the next chapter, such
as laziness and short-circuiting. A pipeline of operations can be viewed as a database-like query on
the data source.
Internal iteration— In contrast to collections, which are iterated explicitly using an iterator,
stream operations do the iteration behind the scenes for you.
Stream operations
- intermediate operations- that can be connected together to form a pipeline ( filter, map, limit)
- terminal operations - that executes the pipeline, close stream, and return a result (collect - list, count-int, foreach -void)
Optimization due to lazy nature of streams
- short-circuiting
- loop fusion
main.stream().filter(…).map(…).limit(3).collect(toList());
It will be executed in following way: filtering pork mapping pork filtering beef mapping beef filtering chicken mapping chicken [pork, beef, chicken]
- Despite the fact that many dishes have > 300 calories, only the first three are selected! This is because of the limit operation and a technique called short-circuiting
- Despite the fact that filter and map are two separate operations, they were merged into
the same pass (we call this technique loop fusion)
working with streams in general involves three items
A data source (such as a collection) to perform a query on
A chain of intermediate operations that form a stream pipeline
A terminal operation that executes the stream pipeline and produces a result
Streams is similar to the …………… pattern
BUILDER
In the builder pattern, there’s a chain of calls to set up a configuration (for streams this is a chain of intermediate
operations), followed by a call to a build method (for streams this is a terminal operation).