Lambdas & Streamsin Java8~ quick introduction ~



Lambdas & Streamsin Java8~ quick introduction ~

0 0


objectivity-presentation-java8-lambda-and-streams

Java8 - Lambdas & Streams - Presentation created for Java group DevMeeting at Objectivity

On Github loskunos / objectivity-presentation-java8-lambda-and-streams

Lambdas & Streams

in Java8~ quick introduction ~

Grzegorz Kędzierski || gkedzierski@objectivity.co.uk || Objectivity 2014

Presentation agenda

...is very simple

  • First, I will talk a little bit about anonymous methods akaLambda Expressions,
  • Then, I will introduce second big thing in Java8, which isStream API

What does these topics have in common?

Functional programming background

  • Lambda
    Treat methods as first-class citizens
  • Stream API
    Declarative programming

so... are we going to talk about Functional Programming Paradigm?

Nope!

Just lambda and streams, ... FP is to big topic for this presentation ... and Java8 still isn't a functional language ... but more on that later.

interlude (I)

Verbs vs Nouns

Imagin a world inhabited by Verbs and Nouns

... but only Nouns can move freely

Verbs can move only when they are escorted by Nouns

(or when they disguise themselves as nouns)

interlude (I) - cont’d

Verbs vs Nouns

In such a world only Nouns are first-class citizens

Verbs live just to serve their masters

This is pre Java8 world

Did this changed in Java 8?

interlude (I) - cont’d

Verbs vs Nouns

Nice (but a little bit outdated by Java8) "article" (short story?)about Nouns and Verbs in the Java Kingdom

"Execution in the Kingdom of Nouns"http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html

Lambda... here we go!

Let's start with an example

...of non-lambda style of programming

button.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    System.out.println(e);
  }
});

...what a waste of space (and time!)

and this has even a name - Boilerplate code!

Let's rewrite it with lambda on board!

button.addActionListener((ActionEvent e) -> {
  System.out.println(e);
});

... simpler, but we can do even better, let remove parameter type!

button.addActionListener(e -> {
  System.out.println(e);
});

...replace block with single statement!

button.addActionListener(e -> System.out.println(e));

...add method reference.

button.addActionListener(System.out::println);

Some examples (from spec) of Lambda expressions

() -> {}                     // No parameters; result is void

() -> 42                     // No parameters, expression body

() -> { return 42; }         // No parameters, block body with return

() -> {                      // Complex block body with returns
  int result = 15;
  for (int i = 1; i < 10; i++) {
    result *= i;
  }
  return result;
}

Some examples (from spec) of Lambda expressions

(int x) -> x+1             		// Single declared-type parameter

(x) -> x+1                 		// Single inferred-type parameter

x -> x+1                   		// Parens optional for single inferred-type case

(Thread t) -> { t.start(); } 	// Single declared-type parameter

(int x, int y) -> x+y      		// Multiple declared-type parameters

(x,y) -> x+y               		// Multiple inferred-type parameters

(final int x) -> x+1       		// Modified declared-type parameter

(x, final y) -> x+y        		// Illegal: can't modify inferred-type parameters

(x, int y) -> x+y          		// Illegal: can't mix inferred and declared types

Lambda syntax description

LambdaExpression: LambdaParameters '->' LambdaBody

LambdaParameters: Identifier | '(' FormalParameterListopt ')' | '(' InferredFormalParameterList ')'

LambdaBody: Expression | Block

ParameterList: FullParameter {, FullParameter}

FullParameter: {Annotation} [final] Type Identifier

InferredFormalParameterList: Identifier {, Identifier}

Lambda can be only defined in two contexts

  • as an argument to some method
  • as a (right side) part of an assignment expression

In other words, lambda needs to know the context - or result type - to which it is assigned.

Little JVM digression - same lambda can be apply to different types, underneath it's using Java8's new ability called poly expression, which deduced type by looking at the target type.

But if lambda needs to have a result type, what can it be?

The answer is:

Functional Interface

Functional Interface is a funny buzzword for naming interfaces with only one "abstract" method.(abstract, because nowadays interfaces can have non-abstract default methods...)

In other words, all existing interfaces that have only one abstract methods can be now called functional interface, and we can used them as a result type for lambda expressions.

Functional methods, should have same parameters and result type as lambda, which we want to assigned to it, eg:
Fun fun = (int x, int y) -> x+y

interface Fun {
  int sum(int a, int b)
}
// We can also ignore the result, but only when lambda is a block or
// is an expressions with void type
FunVoid funVoid = (int x, int y) -> { int i = x+y; }

interface FunVoid {
  void sum(int a, int b)
}

Because Java8 have type inference ability, we can also write

Fun fun = (x,y) -> x + y

And java will inference actual argument type, by looking at a result type.

In package java.util.function are definitions of somegeneric functional interfaces, like

Function: T -> R;
Consumer: T -> void;
Supplier: () -> T
Predicate: T -> boolean

BiFunction: T, U -> R
BinaryOperator: T, T -> T

We can rewrite our previous example now using generic FI:

BinaryOperator<Integer> fun = (x,y) -> x + y;

Java8 provides annotation @FunctionalInterface which we can use to mark interface as functional interface, but this isn't necessary. We can use all interfaces, even those without this annotation.

When using this annotation, java compiler ensures that marked interface have only one abstract method.

Capturing variables from outer scope.

In the world of Anonymous Classes, such classes can only access outer variables if those variables are marked as final.

Almost same applies to lambda expressions, but...

We don't have to marked them with final keyword.

Starting from Java8 all "variables or parameters whose value are never changed after they are initialized are effectively final".

Demo time...

Method reference

Method reference is the ability to assign to functional interface a...method handler.

It looks like this:

System::getProperty  // static method handler

"abc"::length        // instance method on instance handler

System.out::println  // instance method on instance handler

String::length       // instance method on class handler

ArrayList::new       // constructor handler handler

We need to assign handlers to some functional interfaces type, which should bound any necessary input parameters.

It's more easier than it sounds...

// static method - parameters and returning type same as in target method
UnaryOperator<String> getProperty = System::getProperty;

// instance method on instance - parameters and returning type same as in target method
Supplier<Integer> length = "abc"::length;

// instance method on instance
Consumer<Object> printer = System.out::println;

//  instance method on class - first parameter is class instance, then parameters from target method
Function<String,Integer> length2 = String::length;

// constructor handler - parameters same as constructor, return type in same type hierarchy as class
// (or any of its parents)
Supplier<List> listFactory = ArrayList::new;

Demo time...

Some lambda use cases:

Strategy pattern

Template method pattern

Lazy evaluation of parameters

Writing DSLs

And a lot more!

But... comparing this to pre java8 times, is this something realy new?

In Java we could always passed code as data, but this "code" needs to be wrapped explicitly as Java Class (eg. Anonymous class).

Ability of creating lambdas in Java8 looks like syntactic sugar comparing to the the old notation.But underneath it uses different JVM mechanism... so no, it's NOT only a syntactic sugar.

Lambdas are compiled as static methods inside the class in which there are defined. Does this make a difference? Image a single class that use 100 lambdas inside. If we would compile them as nested classes, then we end up with 101 classes on classpath. Lambda will generate only one class. Nice.

interlude (II)

So... are Verbs first-class citizens in Java8 world?

Almost... but still, when going outside, Verbs must dress up as nouns(let's call them transgender verbs :P)

Interesting sources:

State of the Lambdahttp://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

State of the Lambda: Libraries Editionhttp://cr.openjdk.java.net/~briangoetz/lambda/lambda-libraries-final.html

Translation of Lambda Expressionshttp://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

JSR 335: Lambda Expressions for the JavaTM Programming Languagehttps://jcp.org/en/jsr/detail?id=335

Streams

External vs Internal iterations

Let's start with simple iteration using for loop

List<String> strings = ...
for (int i = 0; i < strings.size(); i++) {
  System.out.println(strings.get(0));
}

Starting from Java 1.5 we can rewrite it using for each loop

for (String s: strings) {
  System.out.println(s);
}

This were the examples of external iterations - we, in code, decide how to iterate over some collection.

External vs Internal iterations

Next step - internal iteration

strings.forEach(System.out::println);
vs external iteration
for (String s: strings) {
  System.out.println(s);
}

Looks only like a small, meaningless syntactic change, but...

We are giving the control of the iteration to the library code and it changes a lot!

Internal iterations

Our code moves from imperative style to more declarative one, so...

We are not telling how to iterate - library code decides how to do the iteration.

Library can support out of the box laziness, parallelism, out-of-order execution etc.

Client code is much cleaner, programmer needs to focus only on the business problem, not on the technical details of implementation.

... let's get back to Streams.

Stream...

is just a sequence of values on with we can manipulate and do common things, like filtering, mapping, reducing etc.

Manipulation is done via declarative style of programming and internal iterations.

All classes of this new API are located in package java.util.stream.

Because one example is worth a thousand of words...

Let's looks to the code

Streams and parallelism

It's realy simple, just...

stream.parallel()...

And thats all! Now all operation on stream will be executed in parallel.

Streams and parallelism

But be careful!

Underneath Stream library is using Fork/Join framework for implementing parallelism.

...and it's is using a common thread pool from Fork/Join.

Having a long-running operation on Stream can block all threads in the pool!!!

Streams and parallelism

To solve this problem we can either create a new pool and run stream operation in it

ForkJoinPool forkJoinPool = new ForkJoinPool(20);
forkJoinPool.submit(() ->
  stream.parallel()...
).get();

or use a property to increase common thread pool size

java.util.concurrent.ForkJoinPool.common.parallelism

...I think that this is

THE END

Sources

Source of this presentationhttps://github.com/loskunos/objectivity-presentation-java8-lambda-and-streams

Source of demo used during this presentationhttps://github.com/loskunos/objectivity-presentation-java8-lambda-and-streams-demo