[go: up one dir, main page]

0% found this document useful (0 votes)
40 views49 pages

JAVA Development: Anonymous Classes, Class Initializers, Lambdas, Streams

The document discusses anonymous classes, class initializers, lambdas, and streams in Java. It provides examples of using anonymous classes to implement interfaces only once, and explains how class and instance initializer blocks are executed. It also demonstrates how lambda expressions can be used to define functions concisely and the syntax for lambda expressions. Finally, it outlines some predefined functional interfaces in Java and how method references can refer to existing methods.

Uploaded by

Cosmin Grosu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views49 pages

JAVA Development: Anonymous Classes, Class Initializers, Lambdas, Streams

The document discusses anonymous classes, class initializers, lambdas, and streams in Java. It provides examples of using anonymous classes to implement interfaces only once, and explains how class and instance initializer blocks are executed. It also demonstrates how lambda expressions can be used to define functions concisely and the syntax for lambda expressions. Finally, it outlines some predefined functional interfaces in Java and how method references can refer to existing methods.

Uploaded by

Cosmin Grosu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 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