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)