I was just started looking Java8 and was impressed with the features it has.
Bought the book 'Java 8 IN ACTION' , which is very good to start with.
I would share some important points from this book and add the more information as and when I get time to read this.
Let's Start
LAMBDA EXPRESSIONS
It’s an Anonymous function that can be passed around: it
doesn’t have a name, but it has a list of parameters, a body, a return type,
and also possibly a list of exceptions that can be thrown
Good and precise way to
represent Behaviour Parameterisation
BEFORE:
Comparator<Apple>
byWeight = new Comparator<Apple>() {
public int compare(Apple a1,
Apple a2){
return
a1.getWeight().compareTo(a2.getWeight());
}
};
AFTER
(WITH LAMBDA EXPRESSIONS):
Comparator<Apple> byWeight
=
(Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight());
With Type inference - No explicit type on the parameters a1,a2
With Type inference - No explicit type on the parameters a1,a2
Comparator<Apple> byWeight =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
Syntax:
(parameters) -> expression
or (note the curly braces for statements)
(parameters) -> { statements; }
Has to be used in conjunction with Functional Interfaces.
To demonstrate, for ex- we have the below process method which accepts Runnable Functional Interface
public void process(Runnable r) { r.run(); }Various ways we can call using Lambda expressions... Old Style
Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Using Anonymous class"); } }; process(r1);
Runnable r2 = () -> System.out.println("Using Lambda Expressions"); process(r2); process(() -> System.out.println("With Lambda Expression Passed Directly"));
a
functional interface is an interface that specifies exactly one abstract
method, which are annotated with @FunctionalInterface
Ex:-
Comparator, Runnable, Callable, Predicate, Consumer, Function
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.
Ex:-
Runnable – Functional Interface
run()
- Function Descriptor
Listing 1 - Filter method which takes the List and a Predicate, which the filters the list based on the type of Predicate it has passed.
Using Consumer functional Interface
Method References
Method references let you reuse existing method definitions and pass them just like lambdas.
BEFORE:
appleList.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight()));
AFTER (USING A METHOD REFERENCE AND JAVA.UTIL.COMPARATORS.COMPARING):
appleList.sort(comparing(Apple::getWeight));
For example, Apple::getWeight is a method reference to getWeight() defined in the Apple
class. It can be seen as a shorthand for the lambda expression (Apple a) ->
a.getWeight().
RECIPE FOR CONSTRUCTING METHOD REFERENCES
There are three main kinds of method references.
1. A method reference to a static method (for example, the method parseInt of Integer,
written Integer::parseInt)
2. A method reference to an instance method of an arbitrary type (for example, the
method length of a String, written String::length)
3. A method reference to an instance method of a specific object (for example, suppose
you have an object expensiveTransaction from class Transaction with an instance
method getValue(), you can write expensiveTransaction::getValue)
Composing Comparators
Reverse order - to sort in decreasing weight
appleList.sort(comparing(Apple :: getWeight).reverse());
Chaining Comparators - multiple Comparators to further refine the comparisions
appleList.sort(comparing(Apple :: getWeight)
.reverse()
.thenComparing(Apple :: getCountry));
Composing Functions
The Function interfaces comes with two default methods: andThen and compose that both
return an instance of Function.
The method andThen returns a function that applies a given function first to an input, and
then applies another function to the result of that application.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 4
The method compose will first apply the function given as argument to compose and then apply the function to the result. It works opposite to andThen
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // 3
In the next part, I will cover another interesting feature 'Processing Data with Streams'
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> result = new ArrayList<>(); for (T e : list) { if(predicate.test(e)) { result.add(e); } } return result; }Here's some examples to call the filter:
Filter by Apple color Listblueapples = filter(getAppleInventory(), (Apple apple) -> "blue".equalsIgnoreCase(apple.getColor())); Filter by Even numbers: List evenNums = filter(getNumbers(), (Integer i) -> i % 2 == 0); Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List nonEmpt = filter(listOfStrings, nonEmptyStringPredicate);
Using Consumer functional Interface
public interface Consumer<T>{ public void accept(T t); }You might use this interface when you need to access an object of type T and perform some operations on it.
public static <T> void forEach(List<T> list, Consumer<T> consumer) { for (T i : list) consumer.accept(i); }we can call forEach using Lambda expressions... - The below will compute the sum of the integerList
private static int sum = 0; forEach(integerList, (Integer i) -> { sum = sum + i; System.out.println("Sum :: " + sum); });Using Function functional Interface
public interface Function<T, R>{ public R apply(T t); }You might use this interface when you need to define a lambda that can extract information from the input object (for example, extracting the weight of an apple) or transform the input (for example, a string to its length).
public static <T,R> List<R> map(List<T> list, Function<T,R> function) { List<R> resultType = new ArrayList<>(); for (T s : list) resultType.add(function.apply(s)); return resultType; }we can call map() using Lambda expressions to transform the output from String to Integer...
List<Integer> integerList = map(Arrays.asList("convert", "String", "to", "respective", "Integer", "length"), (String s) -> s.length());
Method References
Method references let you reuse existing method definitions and pass them just like lambdas.
BEFORE:
appleList.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight()));
AFTER (USING A METHOD REFERENCE AND JAVA.UTIL.COMPARATORS.COMPARING):
appleList.sort(comparing(Apple::getWeight));
For example, Apple::getWeight is a method reference to getWeight() defined in the Apple
class. It can be seen as a shorthand for the lambda expression (Apple a) ->
a.getWeight().
RECIPE FOR CONSTRUCTING METHOD REFERENCES
There are three main kinds of method references.
1. A method reference to a static method (for example, the method parseInt of Integer,
written Integer::parseInt)
2. A method reference to an instance method of an arbitrary type (for example, the
method length of a String, written String::length)
3. A method reference to an instance method of a specific object (for example, suppose
you have an object expensiveTransaction from class Transaction with an instance
method getValue(), you can write expensiveTransaction::getValue)
Composing Comparators
Reverse order - to sort in decreasing weight
appleList.sort(comparing(Apple :: getWeight).reverse());
Chaining Comparators - multiple Comparators to further refine the comparisions
appleList.sort(comparing(Apple :: getWeight)
.reverse()
.thenComparing(Apple :: getCountry));
Composing Functions
The Function interfaces comes with two default methods: andThen and compose that both
return an instance of Function.
The method andThen returns a function that applies a given function first to an input, and
then applies another function to the result of that application.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 4
The method compose will first apply the function given as argument to compose and then apply the function to the result. It works opposite to andThen
Function<Integer, Integer> h = f.compose(g);
int result = h.apply(1); // 3
In the next part, I will cover another interesting feature 'Processing Data with Streams'