Friday, May 19, 2017

Java 8 new features

A lot of new features were introduced with Java 8. Let's review most important of them:
 - Default and static methods for interfaces
 - Functional interfaces and lambdas
 - Method references
 - Optionals
 - Streams
 - Fork/Join pull


0. Default and static methods for interfaces

In Java 8,  Abstract class and Interface are getting closer and closer.
Default methods enable us to add new functionalities to interfaces without breaking the classes that implements that interface. Static methods - are working as regular static methods for classes:

interface Checker {
    boolean check(int value);

    default void sayHi() {
        System.out.println("Hi");
    }

    static void sayHello() {
        System.out.println("Say hello");
    }
}

static class PositiveChecker implements Checker {
    @Override    public boolean check(int value) {
        return value > 0;
    }
}

public static void main(String[] args) {
    Checker.sayHello();
    Checker checker = new PositiveChecker();
    if (checker.check(5)) {
        checker.sayHi();
    }
}

1. Functional interfaces and lambdas

Java defines a functional interface as an interface that contains a single abstract method.
@FunctionalInterfacepublic interface MyCommand {
    void execute();
}

Functional interfaces are used as the basis for lambda expressions in functional programming.
A lambda expression is a block of code that gets passed around, like an anonymous method.

From Oracle documentation:

Lambda expressions address the bulkiness of anonymous inner classes by converting five lines of code into a single statement. This simple horizontal solution solves the "vertical problem" presented by inner classes.
A lambda expression is composed of three parts.
Argument ListArrow TokenBody
(int x, int y)->x + y
The body can be either a single expression or a statement block. In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body and a return statement returns control to the caller of the anonymous method. The break and continue keywords are illegal at the top level, but are permitted within loops. If the body produces a result, every control path must return something or throw an exception.


Example of usage:
@FunctionalInterface
public interface MyCommand0Args {
    void execute();
}

@FunctionalInterface
public interface MyCommand1Arg {
    void execute(String arg1);
}

@FunctionalInterface
public interface MyCommand2Args {
    void execute(String arg1, String arg2);
}

public void runLambda0Args(MyCommand0Args command) {
    command.execute();
}

public void runLambda1Arg(MyCommand1Arg command, String arg1) {
    command.execute(arg1);
}

public void runLambda2Args(MyCommand2Args command, String arg1, String arg2) {
    command.execute(arg1, arg2);
}

public void runLambdaTest() {

    runLambda0Args(() -> System.out.println("Hello World"));
    runLambda1Arg(name -> System.out.println("Hello " + name), "Joe");
    runLambda2Args((greetingString, name) -> {
                String result = greetingString + ", " + name + "!!!";
                System.out.println(result);
            }
            , "Hi", "Black");
}

As you can see from function runLambdaTest - instead of writing the implementation of function interface, we can very quick write it in "lambda-way".

More realistic example:
@FunctionalInterface
public interface BinaryOperation<T> {
    T calculate(T x, T y);
}

public int intOperation(BinaryOperation<Integer> operation, int x, int y) {
    return operation.calculate(x, y);
}

public int intAdd(int x, int y) {
    return intOperation((op1, op2) -> op1 + op2, x, y);
}

public int intSub(int x, int y) {
    return intOperation((op1, op2) -> op1 - op2, x, y);
}


2. Built in functional interfaces

In Java 8 there are several built in interfaces for covering most common cases. The are listed here.
In shorts, there are several types:
 - Supplier: returning element
 - Consumer: accepting element
 - Predicate: accepting element and returning boolean
 - Function: accepting element and returning(transforming) another element of different type
 - UnaryOperator: accepting element and returning another element of the same type
Examples of usage by lambdas:
Supplier<String> stringSupplier = () -> "hello world";
Consumer<String> stringConsumer = (name) -> System.out.println("Hello, " + name);
BiConsumer<String, Integer> stringIntegerBiConsumer = (name, age) -> System.out.println(name + " is " + age + " years old");
Predicate<String> stringPredicate = (name) -> name == null;
BiPredicate<String, Integer> stringIntegerBiPredicate = (name, age) -> name != null && name.length() > 0 && age != null && age > 0;
Function<String, String> stringFunction = (name) -> "Hello, " + name;
BiFunction<String, Integer, String> stringIntegerStringBiFunction = (name, age) -> name + " is " + age + " years old";
UnaryOperator<String> stringUnaryOperator = (name) -> name.toUpperCase();
BinaryOperation<String> stringBinaryOperation = (name, surname) -> name + " " + surname;


3. Method references

Method references are a way to make the code shorter by reducing some of the code that
can be inferred and simply mentioning the name of the method. Like lambdas, it takes time
to get used to the new syntax.

There are four kinds of method references:
KindExample
Reference to a static methodContainingClass::staticMethodName
Reference to an instance method of a particular objectcontainingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular typeContainingType::methodName
Reference to a constructorClassName::new

For example we can rewrite implementation of some built in interfaces little bit shorter:
Supplier<String> stringSupplier = new Date()::toString;
Consumer<String> stringConsumer = System.out::println;
Predicate<List<String>> stringPredicate = List::isEmpty;

Another example from documetation: 
class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);



4. Optionals

Prior to Java 8 we had to return NULL if we have nothing to return. With Java 8 we got another option:

public static class MyDBEntity {
    Long id;
    String value;

    public MyDBEntity(Long id, String value) {
        this.id = id;
        this.value = value;
    }
}

public Optional<List<MyDBEntity>> optionalTest(String userId) {
    if (userId.equals("ADMIN")) {
        List<MyDBEntity> result = new ArrayList<>();
        result.add(new MyDBEntity(1L, "My Admin"));
        return Optional.of(result);
    } else {
        return Optional.empty();
    }

}

5. Streams

What is Stream?
From documentation: 
Streams - classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections.
The key abstraction introduced in this package is stream. The classes StreamIntStreamLongStream, and DoubleStream are streams over objects and the primitive intlong anddouble types. 

We can create a stream in different ways: 

//Stream.of
Stream<String> stringStream = Stream.of("one", "two", "three");
Stream<Integer> integerStream = Stream.of(1,2,3);

//from list
List<Integer> list = Arrays.asList(1,2,3);
Stream<Integer> fromList = list.stream();

//generate iterate
Stream<Double> generated =  Stream.generate(Math::random);
Stream<Integer> iterated = Stream.iterate(1, n->n+1);

After creation we can perform on it operations:

Aggregations

System.out.println(stringStream.count());
stringStream.max((x,y)->x.length()-y.length()).ifPresent(System.out::println);

Find

stringStream.findAny().ifPresent(System.out::println); stringStream.findFirst().ifPresent(System.out::println); System.out.println(stringStream.allMatch(s->s.length()>5)); System.out.println(stringStream.anyMatch(s->s.equals("one"))); System.out.println(stringStream.noneMatch(s->s.equals("ten")));

forEach

integerStream.forEach(e-> System.out.println(e));
Reduce method is producing from stream elements one result element:

Reduce

StringStream.reduce((a,b)->a+","+b ).ifPresent(System.out::println);
integerStream.reduce(1, (x,y)->x*y);

Collect

Collect method used for transforming stream to another form.


Stream<Integer> stream = Stream.of(1, 3, 2, 4, 5);
TreeSet<Integer> set = stream.collect(TreeSet::new, TreeSet::add,
        TreeSet::addAll);
System.out.println(set); // [1, 2, 3, 4, 5]

The collector has three parts. The supplier creates an empty TreeSet. The accumulator adds a single String from the Stream to the TreeSet. The combiner adds all of the elements of one TreeSet to another in case the operations were done in parallel and need to be merged. 


Filter

According to it name, filter method is filtering stream content by provided predicate:
integerStream.filter(e -> e>0);
stringStream.filter(s->s.startsWith("A"));

Distinct

Removing duplicated from stream:
integerStream.distinct();



Skip and Limit

Sometimes we need to starting from some element - we can use skip() method for that. Also we can limit processing elements count by limit() method:
integerStream.skip(2).limit(1);


Map

Provides "mapping" one-to-one from one form to another.
stringStream.map(s->s.length());
- string stream is being transformed to integer stream.

FlatMap

Can be useful if we are working with stream of lists - to transform all lists to one "flat" list:
List<String> list1 = Arrays.asList("One", "Two");
List<String> list2 = Arrays.asList("Three", "Four");
Stream<List<String>> listStream = Stream.of(list1, list2);
listStream.flatMap(l->l.stream()).forEach(System.out::println);

Sorted

Also we can sort stream content if we need. It can be done using natural ordering or by defined comparator:
stringStream.sorted();
stringStream.sorted( (a, b) -> a.length()-b.length()  );

Peek 

This function is mostly needed for debug. Then we have to out or store elements from stream.
It behave like consumer, but without actual "consuming": element consumed by peek is not disappearing from stream and moving to next chain.

integerStream.peek(System.out::println).forEach(System.out::println);  // 112233
- elements were consumed by peek and after that by forEach

stringStream.peek(System.out::println).map(s -> s.length()).forEach(System.out::println);   // one 3   two 3  three 5
- peek used for debugging, to out words which will be transformed to numbers.


6. Primitive streams

There are also additional types of streams for some numeric primitive types:

  • IntStream: Used for the primitive types int, short, byte, and char 
  • LongStream: Used for the primitive type long 
  • DoubleStream: Used for the primitive types double and float

With primitive streams we can more simple calculate numeric operations like sum, avg, min, max.

For creation such type of streams:

 IntStream intStream = IntStream.of(1, 2, 3, 4, 5);


Also we can transform regular steam to primitive stream:

intStream = Stream.of(1, 2, 3, 4, 5).mapToInt(e -> e);
intStream = Stream.of("one", "two", "three").mapToInt(e -> e.length());

Operations:

int sum = intStream.sum();
OptionalDouble avg = intStream.average();
OptionalInt max =  intStream.max();


And of course, we can perform operations from "common" streams:

IntStream intStream = IntStream.iterate(1, e -> e + 1);
intStream.skip(10).limit(5).forEach(System.out::println);

Summary statistics

There are also "statistic" classes which can be useful if we need a lot of statistic information about stream. Such information can be calculated "in one shot" and used later:

IntSummaryStatistics statistics = intStream.summaryStatistics();
System.out.println(statistics.getAverage() + " " + statistics.getCount() + 
                   statistics.getSum() + statistics.getMax());


7. Streams - advanced

Optional as stream

Some stream operation also available for Optionals. For example, lest's get some Optional value from stream:
Optional opt=integerStream.findAny();

What if we need to do something with this optional only with some condition? For example if value greater that 5? Of curse we can use standard IF construction. Another option is operating optional "in stream style":
opt.filter(e->(int)e>5).ifPresent(System.out::println);

Collecting results

In Collectors class there are a lot of tools for transforming stream to another forms. In can easily transformed to another collections:
To TreeSet:

TreeSet<String> treeSet = stringStream.filter(e->e.length()>2)
.collect(Collectors.toCollection(TreeSet::new));
To Map:
Map<String, Integer> map = stringStream
        .collect(Collectors.toMap(e -> e, String::length)); // {one:3}, {two:3}, {three:5}

Also we can group by:
Map<Integer,List<String>> map2 = stringStream.collect(
        Collectors.groupingBy(String::length)); // {3:[one, two], {5:[three]}}

If we just need counts we can:
Map<Integer,Long> map3 = stringStream.collect(
        Collectors.groupingBy(String::length, Collectors.counting())); // {3:2, {5:1}}


8.Dates

For working with dates new classes were introduced:

  • LocalDate Contains just a date—no time and no time zone.  
  • LocalTime Contains just a time—no date and no time zone. 
  • LocalDateTime Contains both a date and time but no time zone.  
  • ZonedDateTime Contains a date, time, and time zone. 

Examples of usage:
out.println(LocalDate.now() + "\n"        + LocalTime.now() + "\n"        + LocalDateTime.now() + "\n"        + ZonedDateTime.now());
//2017-05-01//11:53:08.669//2017-05-01T11:53:08.669//2017-05-01T11:53:08.671+02:00[Europe/Warsaw]


To create the specific date we can use "of" method of corresponding class:

out.println(LocalDate.of(2017, 11, 30));
out.println(LocalTime.of(23, 59, 59));
out.println(LocalDateTime.of(2017, 11, 30, 23, 59, 59));
out.println(ZonedDateTime.of(2017, 11, 30, 23, 59, 59, 0, ZoneId.of("Europe/Warsaw")));

//2017-11-30//23:59:59//2017-11-30T23:59:59//2017-11-30T23:59:59+01:00[Europe/Warsaw]    


For date manipulations there are methods: plus/minusYears/Months/Weeks/Days:

out.println(LocalDate.of(2017, 11, 30).plusYears(1).minusMonths(1).plusDays(5).minusWeeks(1)
        .atTime(23, 59, 59));
//2018-10-28T23:59:59


Class Period can be useful for some periodic date operations. There are several types of periods:
Period.ofDays(1);
Period.ofMonths(2);
Period.ofWeeks(3);
Period.ofYears(4);
// Y M DPeriod.of(1, 2, 3);

Example of usage:
LocalDate startDate = LocalDate.of(2017, 1, 1);
LocalDate endDate = LocalDate.of(2017, 1, 31);
Period period = Period.ofDays(7);

while (startDate.isBefore(endDate)) {
    out.println(startDate);
    startDate = startDate.plus(period);
}
//2017-01-01//2017-01-08//2017-01-15//2017-01-22//2017-01-29


For smaller periods there is another class: Duration:
Duration.ofDays(5);
Duration.ofHours(4);
Duration.ofMinutes(3);
Duration.ofSeconds(2);
Duration.ofMillis(1);
Duration.ofNanos(0);

- usage the same a for periods.

For working with GMT dates we have class Instant:
ZonedDateTime now = ZonedDateTime.now();
Instant instant = now.toInstant();
out.println(now);
out.println(instant);

//2017-05-01T11:22:30.827+02:00[Europe/Warsaw]//2017-05-01T09:22:30.827


9. Concurrency

Parallel streams

If we need to operate stream data by several processes, we can use parallel streams by just applying "parallel" method on regular stream.

Stream<Integer> intStream  = Stream.iterate(0, (t)->t+1).limit(10);
intStream.forEach(System.out::print); //0123456789

Stream<Integer> pStream  = Stream.iterate(0, (t)->t+1).limit(10).parallel();
pStream.forEach(System.out::print); //6589724310

As you can see, results are not subsequent, because they were processed by several threads.


Fork/Join Framework

This framework was created for working with lightweight threads. More common usage is for some recursive tasks: then one task create another child sub-task by using FORK method. If we need to operate with results of this tasks  -we can execute on them JOIN method.

Main components:
ForkJoinPool: An instance of this class is used to run all your fork-join tasks in the whole program.
RecursiveTask<V>: You run a subclass of this in a pool and have it return a result; see the examples below.
RecursiveAction: just like RecursiveTask except it does not return a result


In the example below - comparison of regular task processing in loop and parallel processing using Fork/Join. Of course parallel execution is faster, and result is 7 seconds instead of 12:

public static void main(String[] args) {

    List<Long> numbers = Arrays.asList(41L, 42L, 43L, 44L, 45L);
    long result = 0;

    LocalDateTime before = LocalDateTime.now();
    for (Long number : numbers) {
        result = result + fib(number);
    }
    LocalDateTime after = LocalDateTime.now();
    System.out.println("funcResult=" + result + " took " + after.until(before, ChronoUnit.SECONDS));


    before = LocalDateTime.now();
    FibArrayTask fibTask = new FibArrayTask(numbers);
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    long taskResult = forkJoinPool.invoke(fibTask);
    after = LocalDateTime.now();
    System.out.println("taskResult=" + taskResult + " took " + after.until(before, ChronoUnit.SECONDS));


}

public static long fib(long n) {
    if (n == 0 || n == 1) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}

public static class FibArrayTask extends RecursiveTask<Long> {

    private List<Long> numbers;

    public FibArrayTask(List<Long> numbers) {
        this.numbers = numbers;
    }

    @Override    protected Long compute() {
        if (numbers.size() == 1) {
            return fib(numbers.get(0));
        }

        List<FibArrayTask> tasks = new ArrayList<>();
        for (Long each : numbers) {
            FibArrayTask task = new FibArrayTask(Arrays.asList(each));
            task.fork();
            tasks.add(task);
        }

        long result = 0;
        for (FibArrayTask task : tasks) {
            result = result + task.join();
        }
        return result;
    }
}





No comments:

Post a Comment