You are on page 1of 49

JAVA Development

Anonymous classes, Class initializers,


Lambdas, Streams
Anonymous classes

Anonymous classes allow the inline declaration and implementation of a class.

● Are like local classes except that they cannot have a name.

● Enable you to make your code more concise


○ declare and instantiate a class at the same time

● Use them if you need to implement an interface to use it only once.


Anonymous classes - Example

//some common interface:


interface Greeting {
void greet(String name);
}

//defining a concrete class which implements the interface:


class EnglishGreeting implements Greeting {

//implement the method required by interface


public void greet(String name) {
System.out.println("Hello " + name);
}
}
Anonymous classes - Example

//using the concrete class:


Greeting englishGreeting = new EnglishGreeting();

//defining+using an anonymous class implementing same interface:


Greeting frenchGreeting = new Greeting() {
public void greet(String name) {
System.out.println("Salut " + name);
}
};

englishGreeting.greet("Johnny"); //-> Hello Johnny


frenchGreeting.greet("Pierre"); //-> Salut Pierre
Anonymous classes – Syntax

Syntax:
new Greeting () { /*…class body…*/ }

Parts:

• the new operator

• the name of the interface / base class to implement/extend (in our case the Greeting
interface, which this anonymous class will implement)

• parentheses that contain the arguments to a constructor, just like a normal class
instance creation expression (if there is no constructor, use an empty pair like here)

• class body block - similar to regular classes, may contain fields, methods..
Anonymous classes – Restrictions

Restrictions:

• cannot have any static fields/methods/classes, except static final constants

• cannot be public, private, protected or static

• cannot have constructors (as it has no name...)


○ note: if your class requires some construction logic, you could either use
an instance initializer block as a substitute, or create a regular local class
instead.
Class Initializers

Classes may have initializer blocks, which are executed at initialization.

Two types:
- static initializer blocks - executed only once, right after loading the class (happens before
the first actual use)
- instance (non-static) initializer blocks - executed each time a new instance is created, right
before any defined constructors!

class SomeClass {
{
//instance initializer code…
}

static {
//static instance initializer code…
}
}
Class initialization order

Order of initialization for a class:

● static variable initializers + static initialization blocks (in textual order)


○ called only the first time the class is used! (when it’s first loaded)

● the super() call in the constructor (either explicit or implicit)

● instance variable initializers + instance initialization blocks (in textual order)

● remaining body of constructor after super()


Class initialization order - Example

class MyClass { Then this code:


static int f1 = printAndGet("static field init"); System.out.println("For 1st instance:");
int f2 = printAndGet("instance field init"); new MyClass("A");

static { System.out.println("\nFor 2nd instance:");


printAndGet("static block init"); new MyClass();
}
Will result in output:
{
printAndGet("instance block init"); For 1st instance:
} static field init
static block init
MyClass() { printAndGet("constructor 1"); } instance field init
instance block init
MyClass(String s) { printAndGet("constructor 2");} constructor 2

//helper method which can be called also for field init For 2nd instance:
static int printAndGet(String text) { instance field init
System.out.println(text); return 1; instance block init
}
}
constructor 1
Class initializers - In anonymous classes

Class initializers may be used in anonymous classes as a substitute for


constructors (which are not allowed there)

Example: in an instance of an anonymous class that extends Map:

HashMap map = new HashMap<String, Integer>() {


//using an initializer block (to add some initial values)
{
this.put("ana", 28);
this.put("john", 31);
}
};
System.out.println(map); //-> {ana=28, john=31}
System.out.println(map instanceof Map); //-> true
System.out.println(map.getClass().getName()); //-> Main$1
Lambda Expressions

//defining a Function instance (using a lambda expression):


Function<Double, String> formatter =
value -> String.format("%.2f", value);

//using/applying the function to some arguments:


String str1 = formatter.apply(4.88952); //-> "4.89"
String str2 = formatter.apply(1.7728); //-> "1.77"
Lambda Expressions

What’s this Function<Double, String> thing?

Just an interface from the java.util.function package (full docs here)

public interface Function <T, R> {


R apply(T t);
}
Lambda Expressions - Example

The equivalent code with an anonymous class instead of a lambda expression:

//defining a Function instance using an anonymous class:


Function<Double, String> formatter = new Function<Double, String>() {
public String apply(Double value) {
return String.format("%.2f", value);
}
};

All this being equivalent to:

Function<Double, String> formatter = value -> String.format("%.2f", value);


Lambda Expressions - Usage

Functional interface = an interface that has only a single abstract method (SAM) (meaning a
single non-implemented method; it may have other implemented ones, like default or static)

Lambda expression = the easiest way to create an instance of a functional interface!


- equivalent to an anonymous class, but shorter/easier to use
- since Java 8

Lambda expressions:
- allow to easily create and pass around functions (blocks of code), almost like they were
regular objects! (supporting operations like: store functions in variables, pass them as
parameters to other functions, returning them from functions, applying them later…)
- they add (some) support for functional programming in Java!
Lambda Expressions - Syntax

- Lambda expressions - complete syntax:


(Type1 param1, Type2 param2, ..) -> {
/*statements…*/
return result;
};

- If the parameter types can be inferred from the context, they can be omitted:
(param1, param2) -> {
/*statements…*/
return result;
};
Lambda Expressions - Syntax (2)

- If there is only a single parameter, the parentheses around it can be omitted:


param1 -> {
/*statements…*/
return result;
};

- But if there are no parameters, empty parentheses are required:


() -> {
return new Date();
}
Lambda Expressions - Syntax (3)

- If the body of the function consists of only a single statement, the curly braces
and the “return” statement can be omitted:

param -> statement


Lambda Expressions - Examples

//DEFINING FUNCTIONS:

//with 1 param:
Function<Integer, Integer> incFunc = i -> i + 1;
Function<Person, Integer> getAge = (Person p) -> p.getAge();

//with 2 params:
BiFunction<Integer, Integer, Integer> maxFunc = (a, b) -> a > b ? a : b;

BiFunction<Person, Integer, Boolean> isOlderThan = (Person p, Integer age) -> {


return p.getAge() > age;
};
//equivalent shorter form:
BiFunction<Person, Integer, Boolean> isOlderThan = (p, age) -> p.getAge() > age;

//specialized forms - Predicate:


Predicate<Integer> isZero = x -> x == 0;
Lambda Expressions - Examples

//APPLYING FUNCTIONS:

int a = 2, b = 3, c = 0;
Person p = new Person(22, 180);

//applying generic Function - with .apply():


System.out.println("incFunc(" + a + ")= " + incFunc.apply(a));
System.out.println("sumFunc(" + a + "," + b + ")= " + sumFunc.apply(a, b));
System.out.println("maxFunc(" + a + "," + b + ")= " + maxFunc.apply(a, b));

System.out.println("isOlderThan(" + p + ",30)= " + isOlderThan.apply(p, 30));

//applying a Predicate - with .test():


System.out.println("isZero(" + a + ")= " + isZero.test(a));
System.out.println("isZero(" + c + ")= " + isZero.test(c));
Lambda Expressions - Predefined interfaces

Many predefined interfaces in the java.util.function package:


● Function <T,R> - Receives a value (type T) and returns another (type R)
● Predicate <T> - Receives a value (T) and returns a boolean
● Consumer <T> - Receives a value (T) and returns nothing (void)
● Supplier <T> - Receives nothing (no params) and returns a value (T)

Also variants with 2 parameters:


● BiFunction <T,U,R> - receives 2 values (T,U), returns another (R)
● BiConsumer <T,U> ...
● BiPredicate <T,U> ...

Also special variants for primitives:


● IntToLongFunction, IntPredicate, DoubleConsumer, etc.
Lambda Expressions - Method references

● Lambda expressions which contain only a call to some existing method can be written in
even shorter form, as a method reference

● Syntax - method owner & name, separated by “::”


○ First class methods:
(Apple a) -> a.getWeight() => Apple::getWeight

○ Static methods:
(String s) -> Integer.parseInt(s) => Integer::parseInt

○ Methods of other existing objects:


(Integer i) -> someList.get(i) => someList::get

○ Constructors:
(String name) -> new Book(name) => Book::new
Streams - The need

Nearly every Java application creates and processes collections.

There are many similar processing patterns like:


● finding some elements based on some criteria
● grouping elements based on some criteria
● filtering, sorting, transforming the elements, etc...

Which are re-implementing each and every time.


- How can that (generic) logic be reused?...
External Iteration

External iteration - application code controls


the iteration on a lower level (contains
step-by-step instructions on how to do it)

List<Artist> artists = …

Iterator<Artist> it = artists.iterator();
long count = 0;
while (it.hasNext()) {
Artist artist = it.next();
if (artist.isFrom("Bern")){
count++;
}
}
Internal Iteration

Internal iteration - application code


requests just what needs to be done, using
some high-level operations
(and the low level details on how to actually do
this are handled internally by the collection)

artists
.stream()
.filter(artist ->
artist.isFrom("Bern"))
.count();
Streams - Definition

- So what is a Stream?
- informally: a fancy iterator with operations
- more formally: a sequence of elements from a source that supports
aggregate operations

- Somehow like a collection, a Stream provides an interface to a sequence of


values of a specific type. Unlike a collection, it doesn’t store the elements, they
are computed on demand! (lazy)

- A collection can be turned into a Stream by calling the method .stream()

- Some common operations: filter, map, reduce, find, match, sorted


Streams - Creation

Streams can be created starting from:


- some values - with Stream.of()
- a Collection - with .stream() method
- arrays - with Arrays.stream()
- a function - with Stream.iterate()/generate() (can create infinite streams)
Streams Creation - Examples

● Values:
Stream<String> st = Stream.of("abc", "def");

● Collections:
Collection<String> col = /*…*/ ;
Stream<String> st = col.stream();

● Arrays:
int[] numbers = {2, 3, 5, 7, 11, 13};
IntStream st = Arrays.stream(numbers);

● Iterate:
Stream<Integer> st = Stream.iterate(0, n -> n + 2).limit(10);

● Generate:
Stream<Double> st = Stream.generate(Math::random).limit(5);
Streams Operations

Stream operation types:


- intermediate: produce a value of type Stream; lazy!
- terminal: produce a value of a definite type (not Stream); non-lazy

Operations applied to a stream create a pipeline, which is evaluated on 1st terminal op:
Streams - Intermediate Operations
Stream Operations - filter, map

filter() - filters the elements of a stream, keeping the ones fulfilling a given condition
map() - transforms (maps) each element of the stream to another element (of
possibly different type) by applying a given function

List<String> first5words =
streamOfStrings
.filter(s -> s.length() > 1) //filter out short words
.map(String::toLowerCase) //transform to lower
.distinct() //remove duplicates
.sorted() //sort them
.limit(5) //take first 5
.collect(toList()); //terminal op
Streams - Filter

List<String> startingWithDigit =

Stream.of("a", "1abc", "abc1")


.filter(s -> isDigit(s.charAt(0)))
.collect(toList());
Streams - Map, FlatMap

List<String> uppered = List<String> words =


Stream.of("hello", “world”) Stream.of("hello word", "nice day")
.map(String::toUpperCase) .flatMap(s -> Stream.of(s.split(" ")))
.collect(toList()); .collect(toList());
Streams - Terminal Operations
Streams - Reduce

int sum = Stream.of(4, 5, 3, 9)


.reduce(0, (a, b) -> a + b);
Streams - Collectors

● Return value of terminal operations:


○ most terminal operations return a single value (boolean, int…)
○ collect() may return more complex results, like a collection...

● collect():
○ accumulates the stream elements into a container
○ requires a parameter which specifies how to accumulate them
■ this parameter is of type Collector
■ many predefined ones, in class java.util.stream.Collectors
● like: collect to a specific type of collection (toList, toSet), to a String
(joining), grouping elements (groupingBy)...
Stream Collectors - Examples

import static java.util.stream.Collectors.*;

List<Dish> vegiDishes = menu.stream()


.filter(dish -> dish.isVegetarian())
.collect(toList());

Set<Dish> vegiDishes = menu.stream()


.filter(dish -> dish.isVegetarian())
.collect(toSet());

Map<Dish.Type, List<Dish>> grouped = menu.stream()


.collect(groupingBy(dish -> dish.getType()));
String names = menu.stream()
.map(d -> d.getName())
.collect(joining(", ")); //join items into a single String
Streams - Primitive specializations

- Streams primarily work with objects (not primitives)


- Some primitive-specialized versions exist (mainly for performance reasons and only for
the 3 most used primitives): IntStream, LongStream, DoubleStream

- Compared to Stream, they also have some extra methods: min, max, sum, average

- Creation: Arrays.stream(int[ ]), IntStream.of(int…), IntStream.range(..)

- Boxing/unboxing: methods to convert a primitive stream to a regular one:


- boxed() - convert primitives to boxed values (wrapped)
- mapToInt / Long / Double() - similar to map() operation, but also converts the
resulting wrapper values to a primitive stream
- mapToObject() - similar to map(), but also converts to a regular stream
Streams - Primitive specializations - Examples

//creating primitive streams:


DoubleStream s1 = Arrays.stream(new double[]{1.1, 2, 3, 4, 5});
IntStream s2 = IntStream.of(1, 2, 3, 4, 5);
LongStream s3 = LongStream.rangeClosed(1, 5);

//using specific methods:


System.out.println(s1.min() + ", " + s1.max() + ", " + s1.average() + ", " + s1.sum());

//boxing (stream of primitives -> wrappers):


Stream<Double> s1boxed = s1.boxed();
Stream<Integer> s2boxed = s2.mapToObj(i -> i);

//unboxing (stream of wrappers -> primitives):


IntStream s2unboxed = s2boxed.mapToInt(i -> i);

List<Integer> evenInts = IntStream.rangeClosed(1, 10) //IntStream (new)


.filter(i -> i % 2 == 0) //IntStream (filtered)
.boxed() //Stream<Integer> (needed for collecting to List<Integer>)
.collect(toList()); //List<Integer>)
Streams - Usage

- Benefits:
- shorter, nicer code, more declarative/high level
- Risks:
- dense code, encourages use of anonymous functions
- may lead to cluttered code, hard to read and debug (‘stream-wrecks’)

- Recommendations:
- Write the stream pipeline with each step on a different line
- Prefer using method references
- Keep your lambda expressions short (1 statement); avoid writing “ -> { “, move
instead that block of code to a separate method (with a suggestive name)
- Prefer using the functional interfaces already defined in JDK (instead of inventing/
writing your own)
- Pay attention to order of operations (filter() before map() = more efficient..)
Optional

- Optional<T> - from java.util, is a wrapper over a value (of type T) which may
have only one of two possible states:
- contains a value (T)
- is empty

- Usage: intended to be used especially for the return type of functions, to


indicate that they sometimes may not return any value
- this is better than returning null, because it forces the client code to clearly think
about and handle both cases! (valid or missing value)

- Some stream operations return Optional: findAny, findFirst, max, reduce...


Optional

- Creating an instance of Optional:


- Optional.empty - creates an empty Optional (holds no value)
- Optional.of(x) - creates an Optional holding a value (x), requires x to be not null
- Optional.ofNullable(x) - creates either an empty optional (if x is null), or an Optional
holding a non-null value (if x is not null)

- Handling Optional instances:


- has some useful and safe methods to handle it: isPresent, orElse, map, filter, forEach …
- think of Optional like a mini-stream, over a collection of at most 1 element!

- also has some unsafe actions, like .get() - it forcefully tries to get the value from the
optional, throws an exception if the optional is actually empty;
- before calling get() you should always check the state with .isPresent()
- OR better yet: use it in a functional / stream-like way! (with map(), orElse()...)
Optional - Example

Optional<String> optResult = Stream.of("aa", "bb", "cc")


.filter(s -> s.startsWith("x"))
.findFirst(); //terminal op, returns Optional

boolean wasFound = optResult.isPresent();


String finalResult = optResult.orElse("(not found)"); //get the result or a default

Optional<String> opt1 = Optional.empty();


String s1 = opt1.orElse("default");

Optional<String> opt2 = Optional.of("abc");


opt2.map(String::toUpperCase)
.ifPresent(System.out::println);
Extra reading

● https://www.geeksforgeeks.org/instance-initialization-block-iib-java
● http://tutorials.jenkov.com/java/nested-classes.html#anonymous-classes

● http://tutorials.jenkov.com/java/lambda-expressions.html
● http://www.java2s.com/Tutorials/Java_Lambda/Lambda_Tutorial/Lambda/Java_Lambda_
Java_Lambda_Tutorial.htm

● http://tutorials.jenkov.com/java-collections/streams.html
● https://www.baeldung.com/java-inifinite-streams
● https://www.baeldung.com/java-8-primitive-streams
● https://www.baeldung.com/java-optional
● https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples

You might also like