By Mukesh Kumar
Some History
- Java was invented in the 90’s as an Object-Oriented language.
- Largest change was in ~2005 with Java 5.
- Generics.
- Enhanced for loops.
- Annotations.
- Type-safe enumerations.
- Concurrency enhancements (AtomicInteger).
Java 8 is Massive
- 10 years later, Java 8 is packed with new features.
- Core changes are to incorporate functional language features.
- We can’t cover everything today.
- Will focus on some of the more distributive features.
Functional Languages
- Declarative (“what not how”).
- The only state is held in parameters.
- Traditionally popular in academia and AI.
- Well-suited for event-driven/concurrent (“reactive”) programs.
Lambda Expressions
A block of code that you can pass around so it can be executed later, once or multiple times.
- Anonymous methods.
- Reduce verbosity caused by anonymous classes.
– Alonzo Church in his invention of the lambda calculus in 1936. – Lisp 1958 – Supported in C#, JavaScript, Python, Ruby, C++
A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.
Expressions are limited. They have to immediately return a value, and they cannot contain variables, assignments or statements such as if or for. In order to do more complex operations, a code block can be used with curly braces. If the lambda expression needs to return a value, then the code block should have a return statement.
Lambda expressions can be stored in variables if the variable’s type is an interface which has only one method. The lambda expression should have the same number of parameters and the same return type as that method. Java has many of these kinds of interfaces built in, such as the Consumer interface (found in the java.util package) used by lists.
Lambdas
(int x, int y) -> x + y
() -> 42
(String s) -> {System.out.println(s);}
– Omission of brackets and return statement – Return type deduction
Lambdas
Examples
Before
Button btn = new Button();
final PrintStream pStream = …;
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
pStream.println(“Button Clicked!”);
}
});
After
Button btn = new Button();
final PrintStream pStream = …;
btn.setOnAction(e -> pStream.println(“Button Clicked!”));
– Argument type deduction – In-place implementation. – Local variable capturing – Multiple lines lambdas
Lambdas
Examples
List<String> strs = …;
Collections.sort(strs, (s1, s2) ->
Integer.compare(s1.length(), s2.length()));
new Thread(() -> {
connectToService();
sendNotification();
}).start();
Functional Interfaces
A functional interface is an interface that contains only one abstract method. They can have only one functionality to exhibit. From Java 8 onwards, lambda expressions can be used to represent the instance of a functional interface. A functional interface can have any number of default methods. Runnable, ActionListener, and Comparable are some of the examples of functional interfaces.
Functional Interface is additionally recognized as Single Abstract Method Interfaces. In short, they are also known as SAM interfaces. Functional interfaces in Java are the new feature that provides users with the approach of fundamental programming.
Example
@FunctionalInterface
public interface Runnable {
public void run();
}
Runnable r = () -> System.out.println(“Hello World!”);
– @FunctionalInterface – May be omitted – Generates error when there is more than one abstract method – Attributing a lambda expression to a variable – Returning a lambda expression is also possible
java.util.function
- Predicate<T> –> a boolean-valued property of an object
- Consumer<T> – an action to be performed on an object
- Function<T,R> – a function transforming a T to a R
- Supplier<T> – provide an instance of a T (such as a factory)
- UnaryOperator<T> – a function from T to T
- BinaryOperator<T> – a function from (T,T) to T- More meaningful type names
Method References
Treating an existing method as an instance of a Functional Interface
– Object oriented way of attributing a method to a variable
Java provides a new feature called method reference in Java 8. Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference. In this tutorial, we are explaining method reference concept in detail.
Examples
class Person {
private String name;
private int age;
public int getAge() {return this.age;}
public String getName() {return this.name;}
}
Person[] people = …;
Comparator<Person> byName = Comparator.comparing(Person::getName);
Arrays.sort(people, byName);
– :: operator
Method References
Kinds of method references
- A static method (ClassName::methName)
- An instance method of a particular object (instanceRef::methName)
- A super method of a particular object (super::methName)
- An instance method of an arbitrary object of a particular type (ClassName::methName)
- A class constructor reference (ClassName::new)
- An array constructor reference (TypeName[]::new)- “Instance method of an arbitrary object” adds an argument of that type which becomes the receiver of the invocation
More Examples
Consumer<Integer> b1 = System::exit;
Consumer<String[]> b2 = Arrays::sort;
Consumer<String> b3 = MyProgram::main;
Runnable r = MyProgram::main;
Default Methods
Before Java 8, interfaces could have only abstract methods. The implementation of these methods has to be provided in a separate class. So, if a new method is to be added in an interface, then its implementation code has to be provided in the class implementing the same interface. To overcome this issue, Java 8 has introduced the concept of default methods which allow the interfaces to have methods with implementation without affecting the classes that implement the interface.
Why default methods?
Java 8 has lambda expressions. We want to start using them:
List<?> list = …
list.forEach(…); // lambda code goes here
There’s a problem
The forEach method isn’t declared by java.util.List nor the java.util.Collection interface because doing so would break existing implementations.
Default Methods
We have lambdas, but we can’t force new behaviours into the current libraries.
Solution: default methods.
Default Methods
- Traditionally, interfaces can’t have method definitions (just declarations).
- Default methods supply default implementations of interface methods.
Examples
Example 1
Basics
public interface A {
default void foo() {
System.out.println(“Calling A.foo()”);
}
}
public class Clazz implements A {
}
Client code
Clazz clazz = new Clazz();
clazz.foo();
Output
“Calling A.foo()”
Example 2
Getting messy
public interface A {
default void foo(){
System.out.println(“Calling A.foo()”);
}
}
public interface B {
default void foo(){
System.out.println(“Calling B.foo()”);
}
}
public class Clazz implements A, B {
}
Does this code compile?
Of course not (Java is not C++)!
class Clazz inherits defaults for foo() from both types A and B
How do we fix it?
Option A:
public class Clazz implements A, B {
public void foo(){/* … */}
}
We resolve it manually by overriding the conflicting method.
Option B:
public class Clazz implements A, B {
public void foo(){
A.super.foo(); // or B.super.foo()
}
}
We call the default implementation of method foo() from either interface A or B instead of implementing our own.
Going back to the example of forEach method, how can we force it’s default implementation all the iterable collections?
Let’s take a look at the Java UML for all the iterable Collections:
In order to add a default behaviour to all the iterable collections, a default forEach method was added to the Iterable<E> interface.
We can find its default implementation in java.lang.Iterable interface:
@FunctionalInterface
public interface Iterable {
Iterator iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
The forEach method takes a java.util.function.Consumer functional interface type as a parameter, which enables us to pass in a lambda or a method reference as follows:
List<?> list = …
list.forEach(System.out::println);
This is also valid for Sets and Queues, for example, since both classes implement the Iterable interface.
Summary
- Default methods can be seen as a bridge between lambdas and JDK libraries.
- Can be used in interfaces to provide default implementations of otherwise abstract methods.
- Clients can optionally implement (override) them.
- static methods are now also allowed in interfaces.
- Can eliminate the need for utility classes like Collections.
Accumulators
Scalable updatable variables
Accumulators
The continual evolution of uses of concurrency and parallelism in applications requires continual evolution in library support. For this purpose, the Accumulators were introduced.
Accumulators
Maintaining a single count, sum, etc., that is updated by possibly many threads is a common scalability problem.
A set of new classes were created for that purpose:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
Accumulators
Package java.util.concurrent mechanisms synchronized operations between threads, however if all you want to do is increment a variable across threads, it was overkill and then some.
These classes are usually preferable to alternatives when multiple threads update a common value that is used for purposes such as summary statistics that are frequently updated but less frequently read.
In Java based solutions, it is a very common scenario to use numeric counters which are accessed by multiple threads. In earlier Java versions it was difficult to modify the counter values. Java 8 resolved this issue with its concurrent accumulator classes, where the value can be increased/ decreased effectively in a thread safe method. Some concurrent API enhancements are mentioned as follows:
ConcurrentHashMap – compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() and search() methods.
CompletableFuture – that may be explicitly completed (setting its value and status).
Executors newWorkStealingPool() – method to create a work-stealing thread pool using all available processors as its target parallelism level.
Accumulators
Both the DoubleAdder and LongAdder classes can be seen as specific subsets of the DoubleAccumulator and LongAccumulator functionality.
The call new DoubleAdder() is equivalent to:
new DoubleAccumulator((x, y) -> x + y, 0.0).
The call new LongAdder() is equivalent to:
new LongAccumulator((x, y) -> x + y, 0L).
Examples
DoubleAccumulator da = new DoubleAccumulator((x,y) -> x + y, 0.0);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0, 4.0, 10.0);
doubles.forEach(da::accumulate);
System.out.println(“Result: ” + da.doubleValue());
Output:
Result: 20
LongAdder la = new LongAdder();
List<Long> longs = Arrays.asList(10L, 20L, 30L, 40L, 100L);
longs.forEach(la::add);
System.out.println(“Result: ” + la.longValue());
Output:
Result: 200
java.util.stream
Introduced in Java 8, Stream API is used to process collections of objects. A stream in Java is a sequence of objects that supports various methods that can be pipelined to produce the desired result.
Use of Stream in Java:
The uses of Stream in Java are mentioned below:
Stream API is a way to express and process collections of objects.
Enable us to perform operations like filtering, mapping, reducing, and sorting.
How to Create Java Stream?
Java Stream Creation is one of the most basic steps before considering the functionalities of the Java Stream. Below is the syntax given for declaring Java Stream.
Streams in Java make data processing more efficient by supporting functional-style operations. To understand how to use streams effectively in different scenarios, the Java Programming Course provides a complete guide with examples of stream operations and optimizations.
Syntax:
Stream<T> stream;
Here T is either a class, object, or data type depending upon the declaration.
Java Stream Features
The features of Java stream are mentioned below:
A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
Streams don’t change the original data structure, they only provide the result as per the pipelined methods.
Each intermediate operation is lazily executed and returns a stream as a result, hence various intermediate operations can be pipelined. Terminal operations mark the end of the stream and return the result.
java.util.stream
List<Student> students = …;
Stream stream = students.stream(); // sequential version
// parallel version
Stream parallelStream = students.parallelStream();
- traversed once
- infinite
- lazy
java.util.stream
Stream sources
Collections
Set<Student> set = new LinkedHashSet<>();
Stream<Student> stream = set.stream();
Generators
Random random = new Random();
Stream<Integer> randomNumbers = Stream.generate(random::nextInt);
From other streams
Stream newStream = Stream.concat(stream, randomNumbers);
java.util.stream
Intermediate operations
- .filter – ava stream provides a method filter() to filter stream elements on the basis of given predicate. Suppose you want to get only even elements of your list then you can do this easily with the help of filter method.
- .map – Stream map(Function mapper) returns a stream consisting of the results of applying the given function to the elements of this stream. Stream map(Function mapper) is an intermediate operation. These operations are always lazy. Intermediate operations are invoked on a Stream instance and after they finish their processing, they give a Stream instance as output.
- .flatMap – Stream flatMap(Function mapper) returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Stream flatMap(Function mapper) is an intermediate operation. These operations are always lazy. Intermediate operations are invoked on a Stream instance and after they finish their processing, they give a Stream instance as output.
- .peek – performs some action on each element. In Java, Stream provides an powerful alternative to process data where here we will be discussing one of the very frequently used methods named peek() which being a consumer action basically returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream. This is an intermediate operation, as it creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.
- .distinct – excludes all duplicate elements (equals()) distinct() returns a stream consisting of distinct elements in a stream. distinct() is the method of Stream interface. This method uses hashCode() and equals() methods to get distinct elements. In case of ordered streams, the selection of distinct elements is stable. But, in case of unordered streams, the selection of distinct elements is not necessarily stable and can change. distinct() performs stateful intermediate operation i.e, it maintains some state internally to accomplish the operation.
- .sorted – orderered elements (Comparator). Stream sorted() returns a stream consisting of the elements of this stream, sorted according to natural order. For ordered streams, the sort method is stable but for unordered streams, no stability is guaranteed. It is a stateful intermediate operation i.e, it may incorporate state from previously seen elements when processing new elements.
- .limit – maximum number of elements. The limit(long N) is a method of java.util.stream.Stream object. This method takes one (long N) as an argument and returns a stream of size no more than N. limit() can be quite expensive on ordered parallel pipelines, if the value of N is large, because limit(N) is constrained to return the first N elements in the encounter order and not just any n elements.
- .substream – range (by index) of elements
List<Person> persons = …;
Stream<Person> tenPersonsOver18 = persons.stream()
.filter(p -> p.getAge() > 18)
.limit(10);
java.util.stream
Terminating operations
Obtain a stream from some sources Perform one or more intermidate operations Perform one terminal operation
- reducers like reduce(), count(), findAny(), findFirst()
- collectors (collect())
- forEach
- iterators
List<Person> persons = ..;
List<Student> students = persons.stream()
.filter(p -> p.getAge() > 18)
.map(Student::new)
.collect(Collectors.toList());
java.util.stream
Parallel & Sequential
List<Person> persons = ..;
List<Student> students = persons.stream()
.parallel()
.filter(p -> p.getAge() > 18)
.sequential()
.map(Student::new)
.collect(Collectors.toCollection(ArrayList::new));
java.time
Yet Another Java Date/Time API
java.time
Current Time
Clock clockUTC = Clock.systemUTC();
Clock clockDefault = Clock.systemDefaultZone();
– Different Time Zones
java.time
Time Zones
ZoneId zone = ZoneId.systemDefault();
ZoneId zoneBerlin = ZoneId.of(“Europe/Berlin”);
Clock clock = Clock.system(zoneBerlin);
– Constructing a Clock using a ZoneId
java.time
Human Readable Time
class LocalDate {
public static LocalDate now() { … }
public static LocalDate now(ZoneId zone) { … }
public static LocalDate now(Clock clock) { … }
}
LocalDate date = LocalDate.now();
System.out.printf(“%s-%s-%s”,
date.getYear(), date.getMonthValue(), date.getDayOfMonth());
– now() accepts a Clock or a ZoneId
java.time
Doing Calculations
LocalTime lt = LocalTime.now();
lt.plus(5, ChronoUnit.HOURS);
lt.plusHours(5);
Duration dur = Duration.of(5, ChronoUnit.HOURS);
lt.plus(dur);