You are on page 1of 17

Java 8 basics

Default methods, functional interfaces, lambda expressions

Simon Kroly
simon.karoly@codespring.ro
Interfaces

Static methods
for eliminating external utility classes (e.g. Collections).
E.g. list.sort(comparator) using a default method from the List interface and a static
method from the Comparator interface
Default methods
Extending existing interfaces: these methods are inherited by the implementations,
explicit implementations are not required. E.g. if an interface already has several
implementations, it can be extended without modifying the classes.
The mechanism can be used for extending APIs without losing backward compatibility
Interfaces
Sample interface:
public interface Presentable {
void doSomething();
default void doSomethingByDefault() {
System.out.println("Default implementation");
}
static void executeUtilityMethod() {
System.out.println("Static method");
}
}

Sample implementation:
public class Sample implements Presentable {
@Override
public void doSomething() {
System.out.println("Method implementation within the class");
}
}

Using the sample:


public class Main { Result:
public static void main(String[] args) {
Sample is = new Sample(); Method implementation within the class
Default implementation
is.doSomething();
Static method
is.doSomethingByDefault();
Presentable.executeUtilityMethod();
}
}
Interfaces

Extending interfaces that contain default methods:


If the default method is not redeclared, then it will be inherited from the super interface
It the default method is redeclared, then it will become an abstract method
If the default method is redefined, then the previous implementation will be overridden by
the new method
Restrictions:
The final type modifier cannot be used
The methods of the Object super class cannot be overridden by default methods
Obs.:
The methods of the Object super class are implicitly declared by all interfaces. We can also have explicit
declarations for the non-final methods (for creating documentation - e.g. the equals method from the
Comparator interface). The final methods (getClass, wait, notify, notifyAll) from the Object super class
cannot be explicitly declared.

Default methods multiple inheritance?!


State information is not inherited.
The same default method (identical header) is implemented by two interfaces and both
interfaces are implemented by a class the method is considered abstract and it has to
be implemented within the class.
Obs. If there is an inheritance between the interfaces the more specific implementation will be considered.
Interfaces

Interfaces vs. abstract classes (after introducing default methods)


Abstract classes are inherited, multiple inheritance is not allowed.
There are no attributes declared in the interfaces.
Problem: the OO principles are violated by using default methods
True they have to be used only in justifiable situations
Observations:
The introduction of default methods is justified by the extension of the Java platform: lambda expressions
and the stream API were introduced based on this mechanism.
The usage of this mechanism can be justified only in similar situations: when an old API has to be
extended without losing the backward compatibility.
Lambda expressions

Lambda expression
Anonymous function (in the Computer Science domain)
Observation: a more exact definition is used within the Mathematics domain (lambda calculus)
Other terms: lambda abstraction, lambda function, function literal, anonymous function
Sometimes closure is also used as a synonym (especially in the Java domain), but this is not completely
correct from a theoretical point of view
Usage:
Passing functions as parameters to other functions
Returning functions from other functions
Lambda expression vs. (?) closure
Closure: function/function reference + environment/context information (the set of variables within the
lexical scope of the function) non-local free variables can be accessed by the function, even if it is called
from outside its lexical scope (captured variables)
Lexical/static scoping: the variables declared within a function can be accessed by another function, also
declared within the same function (if the usage of embedded functions is allowed)
Before Java 8 we already had a limited support for closures, using inner classes
From a theoretical point of view: not all lambda expressions are closures (it is not sure that an anonymous
function has access to non-local variables) and not all closures are lambda expressions (not only
anonymous functions have access to non-local variables)
But: lambda expressions are frequently implemented using closures. This method is used by Java the
terms are often used as synonyms within the community (e.g. Closure was the name of the project
initiated for introducing lambda expressions in Java).
Lambda expressions

Introducing lambda expressions


Methods (functions) are now first class citizens (first class objects) within the language
Primary motivation: JCF: more efficient processing (e.g. parallel operations), stream API
Functions can be passed as parameters to collections (internal processing)
Secondary motivation: (limited) support for functional programming
Already introduced in other modern OO languages (e.g. C#)
Lambda expressions

Lambda expression in Java: anonymous function with a simplified syntax


Type modifiers, return type, parameter types (in some situations) can be omitted
Syntax:
(parameters) -> expression
(parameters) -> { instructions; }
Samples:
(int x, int y) -> x + y
(x, y) -> x + y
x -> 2 * x
() -> 42
(String s) -> System.out.println(s)
c -> { int n = c.size(); return n; }
Lambda expressions

Syntax:
Parameter types can be explicitly declared, or implicitly discovered (no combination is
allowed within a single expression)
The parenthesis can be omitted if a single parameter is present
Within the last example the operations defined by the lambda expression were performed
on a collection, but any other type can be used, if the methods can be called for that
instance.
Usage a simple example:
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello Lambda!");
r.run();
}

Implementation: functional interfaces


Functional interfaces

Functional interface: an interface with a single abstract method (SAM Single


Abstract Method interfaces)
E.g. Runnable, Comparator, ActionListener, etc.
The type of a lambda expression: a lambda expression is the instance of a
functional interface
The lambda expression does not contain any information about the type of the functional
interface. This information is discovered based on the context/environment.
A lambda expression can be compatible with more than one functional interfaces, the
target type can vary based on the current context. E.g.:
interface IntOperation { int operate(int i); }
IntOperation iop = x -> x*2;
interface DoubleOperation {double operate(double i); }
DoubleOperation dop = x -> x*2;
The parameter types within a lambda expression have to be identic with the parameter
types within the corresponding functional interface. The returning type has to be
compatible with the type returned by the abstract method. Checked exceptions can be
thrown from the lambda only if these exceptions are declared within the header of the
abstract method.
Lambda expressions

Lambda expressions are objects. But: they dont have a unique identity.
They are instances of functional interfaces. These interfaces are subtypes of the Object
superclass. The methods of the Object superclass are inherited by these interfaces, but
the equals method can not be interpreted (because lambdas dont have an identity).
They can be used anywhere, if a target type is required in the current context:
Variable declarations, assignment operations
Return statements
Parameters for methods and constructors
Lambda expressions
Callable<Runnable> c = () -> () -> { System.out.println("hi"); };

A function without arguments, returning an other function without arguments and


return type (a Runnable object is returned by the call method of the Callable
interface)
Conditional expressions:
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);

Cast expressions:
Object o = (Runnable) () -> { System.out.println("hi"); };
Lambda scope

No new naming context is introduced by a lambda expression. Within a lambda expression the
variable names are interpreted in the same way as in the external environment. (Of course,
formal parameters are exceptions.)
The this and super keywords have the same interpretation within the lambda and its external environment
Class and instance variables are hidden by the formal parameters
class Sample { int i; Something s = i -> i * 2; };

correct, the i instance variable is hidden by the i formal parameter


Local variables cannot be hidden:
void sample() { int i; Something s = i -> i *2; };

not correct (see the first paragraph).


Variables from its external environment can be used within a lambda expression (variable
capture)
Static and instance variables can be used without any restrictions
Only effectively final local variables can be captured
The operations can be executed on different threads, and synchronization problems could occur
Samples

Implementing a listener:
b.addActionListener(e -> System.out.println(e.getActionCommand() + " pressed."));

Sorting collections (+iteration and processing):


Collections.sort(l, (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()));
l.forEach((p) -> System.out.println(p));
java.util.function

Functional interfaces for representing fundamental operations:


Consumer<T> - operation with a single argument and without a returned value (accept
method)
Function<T,R> - operation with a single parameter (of type T) and a returned value (of type
R) (apply method)
Predicate<T> - operation with a single parameter (of type T) returning a boolean value
(test method)
Supplier<T> - operation without parameters and a returned value of type T (get method)
UnaryOperator<T> - operation with a single operand of type T and a returned value of the
same type (derived from the Function interface apply(Object) abstract method)
BinaryOperator<T> - operation with two operands of the same type, and a returned value
(also with the same type) (derived from the BiFunction interface apply(Object, Object)
abstract method)
Furthermore: BiConsumer, BiFunction, BiPredicate, IntConsumer, IntSupplier, IntPredicate,
IntBinaryOperator, DoubleConsumer, etc., ObjIntConsumer, etc., ToIntFunction, DoubleToIntFunction, etc.
+static and default methods
Method references

Lambda: anonymous function it can be represented by an existing method


from class method references
Method references can be used for referencing:
A static method
An instance method from a given object
An instance method from an arbitrary instance
A constructor
Refernce to a static method:
ClassName::methodName
Sample: Integer::compare (a, b) -> Integer.compare(a, b)
Usage sample: Arrays.sort(integerArray, Integer::compare)
Method references

Instance method from a given object:


public class PersonComparatorProvider {

public int compareByLastName(Person a, Person b) {


return a.getLastName().compareTo(b.getLastName());
}

public int compareByAge(Person a, Person b) {


return new Integer(a.getAge()).compareTo(b.getAge());
}

PersonComparatorProvider pcp = new PersonComparatorProvider();


Collections.sort(l, pcp::compareByAge);
Method references

Instance method from an arbitrary object:


Arrays.sort(stringArray, String::compareToIgnoreCase);

Constructor:
private static List<Person> copyCollection(List<Person> src, Supplier<List<Person>> dest) {
List<Person> result = dest.get();
for (Person p:src) {
result.add(p);
}
return result;
}

List<Person> clone = copyCollection(l, ArrayList::new);

You might also like