Java 8

Revision as of 09:55, 11 August 2018 by Rasimsen (talk | contribs)

What are the new features introduced in JAVA 8?

There are dozens of features added to Java 8, the most significant ones are mentioned below −

  • Lambda expression − Adds functional processing capability to Java.
  • Method references − Referencing functions by their names instead of invoking them directly. Using functions as parameter.
  • Default method − Interface to have default method implementation.
  • New tools − New compiler tools and utilities are added like 'jdeps' to figure out dependencies.
  • Stream API − New stream API to facilitate pipeline processing.
  • Date Time API − Improved date time API.
  • Optional − Emphasis on best practices to handle null values properly.
  • Nashorn, JavaScript Engine − A Java-based engine to execute JavaScript code.

Along with these new featuers, lots of feature enhancements are done under-the-hood, at both compiler and JVM level.

Java 8 & Dependency Injection/Annotation Enhancement

TYPE ANNOTATIONS

The place for an annnotation before Java 8 is only before a declaration. In Java 8, the place for an annotation could be where a type is used. This kind of annotation is often called type annotation. For example, you can annotate the return type of a method, generic types, including generic type parameter bounds and generic type arguments. Type annotations are important because they enhance Java type system and enable tools to perform additional checks on type systems to help prevent errors during compilation.


A type annotation must include ElementType.TYPE_USE or ElementType.TYPE_PARAM as a target. An example to declare type annotation is:

@Target(ElementType.TYPE_USE)
@interface typeAnnotation { ... }

When applying typeAnnotation, it must be placed before the type annotated.

void method() throws @typeAnnotation NullPointerException {...}

An example for annotation of type parameter is

@Target(ElementType.TYPE_PARAMETER)
@interface typeParameterAnnotation { ... }

Now typeParameterAnnotation can apply to a type parameter:

class typeAnnotationClass<@typeParameterAnnotation T> {...}

REPEATABLE ANNOTATIONS

Java 8 also provides a new annotation feature, which enables an annotation to be repeated on the same element. This is called repeatable annotations and must be annotated with the @Repeatable annotation defined in java.lang.annotation. Its value field specifies the container type for the repeatable annotation. The container is specified as an annotation for which the value field is an array of the repeatable annotation type. An example of a repeatable annotation is:

@Repeatable(myRepeatedAnnotations.class)
@interface myAnnotation {
    String str();
    int val();
}
// container annotation.
@Retention(RetentionPolicy.RUNTIME)
@interface myRepeatedAnnotations {
    myAnnotation[] value();
}
class repeatAnnotation {
    @myAnnotation(str = "First", val = 1)
    @myAnnotation(str = "Second", val = 2)
    public static void method() {}
}

To retrieve the value of repeated myAnnotation, we must first retrieve the container annotation myRepeatedAnnotations, and then extract each repeated annotation from its value array.

FUNCTIONAL INTERFACE ANNOTATION

Java 8 addsa new general purpose built-in annotation @FunctionalInterface, which is an informative annotation type used to indicate that an interface type declaration is intended to be a functional interface, that is, an interface only has single abstract method. An example using @FunctionalInterface is:

@FunctionalInterface
public interface myFunctionalInterface{
   void myFunction();
}


Functional Interfaces In Java

Any interface with a SAM(Single Abstract Method) is a functional interface.

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, Comparable are some of the examples of functional interfaces. Before Java 8, we had to create anonymous inner class objects or implement these interfaces.

// Java program to demonstrate functional interface
 
class Test
{
    public static void main(String args[])
    {
        // create anonymous inner class object
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                System.out.println("New thread created");
            }
        }).start();
    }
}

@FunctionalInterface Annotation

@FunctionalInterface annotation is used to ensure that the functional interface can’t have more than one abstract method. In case more than one abstract methods are present, the compiler flags an ‘Unexpected @FunctionalInterface annotation’ message. However, it is not mandatory to use this annotation.


// Java program to demonstrate lamda expressions to implement
// a user defined functional interface.
 
@FunctionalInterface
interface Square
{
    int calculate(int x);
}
 
class Test
{
    public static void main(String args[])
    {
        int a = 5;
 
        // lambda expression to define the calculate method
        Square s = (int x)->x*x;
 
        // parameter passed and return type must be
        // same as defined in the prototype
        int ans = s.calculate(a);
        System.out.println(ans);
    }
}

Output:

25

java.util.function Package:

The java.util.function package in Java 8 contains many builtin functional interfaces like:

Predicate

The Predicate interface has an abstract method test which gives a Boolean value as a result for the specified argument. Its prototype is

public Predicate
{
   public boolean test(T  t);
}

BinaryOperator

The BinaryOperator interface has an abstract method apply which takes two argument and returns a result of same type. Its prototype is

public interface BinaryOperator 
{
     public T apply(T x, T y);
}

Function

The Function interface has an abstract method apply which takes argument of type T and returns a result of type R. Its prototype is

public interface Function 
{
   public R apply(T t);
}

A simple program to demonstrate the use of predicate interface

import java.util.*;
import java.util.function.Predicate;
 
class Test
{
    public static void main(String args[])
    {
 
        // create a list of strings
        List<String> names =
            Arrays.asList("Geek","GeeksQuiz","g1","QA","Geek2");
 
        // declare the predicate type as string and use
        // lambda expression to create object
        Predicate<String> p = (s)->s.startsWith("G");
 
        // Iterate through the list
        for (String st:names)
        {
            // call the test method
            if (p.test(st))
                System.out.println(st);
        }
    }
}

Output:

Geek
GeeksQuiz
Geek2

Suppliers

The Supplier functional interface is yet another Function specialization that does not take any arguments. It is typically used for lazy generation of values. For instance, let’s define a function that squares a double value. It will receive not a value itself, but a Supplier of this value:

public double squareLazy(Supplier<Double> lazyValue) {
    return Math.pow(lazyValue.get(), 2);
}

This allows us to lazily generate the argument for invocation of this function using a Supplier implementation. This can be useful if the generation of this argument takes a considerable amount of time. We’ll simulate that using Guava’s sleepUninterruptibly method:

Supplier<Double> lazyValue = () -> {
    Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);
    return 9d;
};
 
Double valueSquared = squareLazy(lazyValue);

Another use case for the Supplier is defining a logic for sequence generation. To demonstrate it, let’s use a static Stream.generate method to create a Stream of Fibonacci numbers:

int[] fibs = {0, 1};
Stream<Integer> fibonacci = Stream.generate(() -> {
    int result = fibs[1];
    int fib3 = fibs[0] + fibs[1];
    fibs[0] = fibs[1];
    fibs[1] = fib3;
    return result;
});

The function that is passed to the Stream.generate method implements the Supplier functional interface. Notice that to be useful as a generator, the Supplier usually needs some sort of external state. In this case, its state is comprised of two last Fibonacci sequence numbers.

To implement this state, we use an array instead of a couple of variables, because all external variables used inside the lambda have to be effectively final.

Other specializations of Supplier functional interface include BooleanSupplier, DoubleSupplier, LongSupplier and IntSupplier, whose return types are corresponding primitives.

Consumers

As opposed to the Supplier, the Consumer accepts a generified argument and returns nothing. It is a function that is representing side effects.

For instance, let’s greet everybody in a list of names by printing the greeting in the console. The lambda passed to the List.forEach method implements the Consumer functional interface:

List<String> names = Arrays.asList("John", "Freddy", "Samuel");
names.forEach(name -> System.out.println("Hello, " + name));

There are also specialized versions of the Consumer — DoubleConsumer, IntConsumer and LongConsumer — that receive primitive values as arguments. More interesting is the BiConsumer interface. One of its use cases is iterating through the entries of a map:

Map<String, Integer> ages = new HashMap<>();
ages.put("John", 25);
ages.put("Freddy", 24);
ages.put("Samuel", 30);
 
ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));

Another set of specialized BiConsumer versions is comprised of ObjDoubleConsumer, ObjIntConsumer, and ObjLongConsumer which receive two arguments one of which is generified, and another is a primitive type.



Important Points/Observations:

  • A functional interface has only one abstract method but it can have multiple default methods.
  • @FunctionalInterface annotation is used to ensure an interface can’t have more than one abstract method. The use of this annotation is optional.
  • The java.util.function package contains many builtin functional interfaces in Java 8.

StreamAPI - Parallelism

Stream source : Streams can be ceated from Collections, Lists, Sets, ints, longs, doubles, lines of a file.

Stream operation are either intermediate or terminal.

Intermediate Operations

Intermediate Operations such as filter, map, sort return a stream so we can chain multiple intermediate operations.

  • anyMatch()
  • distinct()
  • filter()
  • findFirst()
  • FlatMap()
  • map()
  • skip()
  • sorted()

notes:

  • Zero or more intermediate operations are allowed.
  • Order matters for large datasets: filter first, then sort or map
  • for very large datasets use ParallelStream to enable multiple threads


Terminal Operations

Terminal Operations such as forEach, collect or reduce are either void or return a non-stream result.

  • one terminal operation is allowed
  • forEach applies the same function to each element
  • collect saves the elements into a Collection
  • other options reduce the stream to a single summary element
    • count()
    • max()
    • min()
    • reduce()
    • summaryStatistics()


Parallelism

Parallel computing involves dividing a problem into subproblems, solving those problems simultaneously (in parallel, with each subproblem running in a separate thread), and then combining the results of the solutions to the subproblems.

One difficulty in implementing parallelism in applications that use collections is that collections are not thread-safe, which means that multiple threads cannot manipulate a collection without introducing thread interference or memory consistency errors. The Collections Framework provides synchronization wrappers, which add automatic synchronization to an arbitrary collection, making it thread-safe. However, synchronization introduces thread contention. You want to avoid thread contention because it prevents threads from running in parallel. Aggregate operations and parallel streams enable you to implement parallelism with non-thread-safe collections provided that you do not modify the collection while you are operating on it.

Note that parallelism is not automatically faster than performing operations serially, although it can be if you have enough data and processor cores. While aggregate operations enable you to more easily implement parallelism, it is still your responsibility to determine if your application is suitable for parallelism.


private long countPrimes(int max) {
    return range(1, max).parallel().filter(this::isPrime).count();
}
private boolean isPrime(long n) {
    return n > 1 && rangeClosed(2, (long) sqrt(n)).noneMatch(divisor -> n % divisor == 0);
}
private List<StockInfo> getStockInfo(Stream<String> symbols) {
     return symbols.parallel()
            .map(this::getStockInfo) //slow network operation
            .collect(toList());
}

Executing Streams in Parallel

You can execute streams in serial or in parallel. When a stream executes in parallel, the Java runtime partitions the stream into multiple substreams. Aggregate operations iterate over and process these substreams in parallel and then combine the results.

When you create a stream, it is always a serial stream unless otherwise specified. To create a parallel stream, invoke the operation Collection.parallelStream. Alternatively, invoke the operation BaseStream.parallel. For example, the following statement calculates the average age of all male members in parallel:

double average = roster
    .parallelStream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

Concurrent Reduction

Consider again the following example that groups members by gender. This example invokes the collect operation, which reduces the collection roster into a Map:

Map<Person.Sex, List<Person>> byGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(Person::getGender));

The following is the parallel equivalent:

ConcurrentMap<Person.Sex, List<Person>> byGender =
    roster
        .parallelStream()
        .collect(
            Collectors.groupingByConcurrent(Person::getGender));

This is called a concurrent reduction. The Java runtime performs a concurrent reduction if all of the the following are true for a particular pipeline that contains the collect operation:

  • The stream is parallel.
  • The parameter of the collect operation, the collector, has the characteristic Collector.Characteristics.CONCURRENT. To determine the characteristics of a collector, invoke the Collector.characteristics method.
  • Either the stream is unordered, or the collector has the characteristic Collector.Characteristics.UNORDERED. To ensure that the stream is unordered, invoke the BaseStream.unordered operation.

Note: This example returns an instance of ConcurrentMap instead of Map and invokes the groupingByConcurrent operation instead of groupingBy. Unlike the operation groupingByConcurrent, the operation groupingBy performs poorly with parallel streams. (This is because it operates by merging two maps by key, which is computationally expensive.) Similarly, the operation Collectors.toConcurrentMap performs better with parallel streams than the operation Collectors.toMap.


Ordering

The order in which a pipeline processes the elements of a stream depends on whether the stream is executed in serial or in parallel, the source of the stream, and intermediate operations. For example, consider the following example that prints the elements of an instance of ArrayList with the forEach operation several times:

Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 };
List<Integer> listOfIntegers =
    new ArrayList<>(Arrays.asList(intArray));

System.out.println("listOfIntegers:");
listOfIntegers
    .stream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("listOfIntegers sorted in reverse order:");
Comparator<Integer> normal = Integer::compare;
Comparator<Integer> reversed = normal.reversed(); 
Collections.sort(listOfIntegers, reversed);  
listOfIntegers
    .stream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");
     
System.out.println("Parallel stream");
listOfIntegers
    .parallelStream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");
    
System.out.println("Another parallel stream:");
listOfIntegers
    .parallelStream()
    .forEach(e -> System.out.print(e + " "));
System.out.println("");
     
System.out.println("With forEachOrdered:");
listOfIntegers
    .parallelStream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");

This example consists of five pipelines. It prints output similar to the following:

listOfIntegers:
1 2 3 4 5 6 7 8
listOfIntegers sorted in reverse order:
8 7 6 5 4 3 2 1
Parallel stream:
3 4 1 6 2 5 7 8
Another parallel stream:
6 3 1 5 7 8 4 2
With forEachOrdered:
8 7 6 5 4 3 2 1

This example does the following:

  • The first pipeline prints the elements of the list listOfIntegers in the order that they were added to the list.
  • The second pipeline prints the elements of listOfIntegers after it was sorted by the method Collections.sort.
  • The third and fourth pipelines print the elements of the list in an apparently random order. Remember that stream operations use internal iteration when processing elements of a stream. Consequently, when you execute a stream in parallel, the Java compiler and runtime determine the order in which to process the stream's elements to maximize the benefits of parallel computing unless otherwise specified by the stream operation.
  • The fifth pipeline uses the method forEachOrdered, which processes the elements of the stream in the order specified by its source, regardless of whether you executed the stream in serial or parallel. Note that you may lose the benefits of parallelism if you use operations like forEachOrdered with parallel streams.

Side Effects

A method or an expression has a side effect if, in addition to returning or producing a value, it also modifies the state of the computer. Examples include mutable reductions (operations that use the collect operation; see the section Reduction for more information) as well as invoking the System.out.println method for debugging. The JDK handles certain side effects in pipelines well. In particular, the collect method is designed to perform the most common stream operations that have side effects in a parallel-safe manner. Operations like forEach and peek are designed for side effects; a lambda expression that returns void, such as one that invokes System.out.println, can do nothing but have side effects. Even so, you should use the forEach and peek operations with care; if you use one of these operations with a parallel stream, then the Java runtime may invoke the lambda expression that you specified as its parameter concurrently from multiple threads. In addition, never pass as parameters lambda expressions that have side effects in operations such as filter and map. The following sections discuss interference and stateful lambda expressions, both of which can be sources of side effects and can return inconsistent or unpredictable results, especially in parallel streams. However, the concept of laziness is discussed first, because it has a direct effect on interference.

A side effect is an action taken from a stream operation which changes something externally.

The change may be changing some variable values in the program or it can be sending a JMS message, sending email, printing with System.out.println or in a UI application changing the state of a button from enabled to disabled etc.


What operations should apply side-effects? The pipeline operations ForEach(), ForEachOrdered() and peek() which returns void, are meant to produce side effects.

The intermediate operations with behavioral parameters which usually return a non-void value should entirely be avoided to apply side effects e.g. filter()' , map() etc.

Intermediate operation peek() should be limited to side-effects like logging and debugging only.

Applying side-effect via peek() If we don't care about order, using peek for printing is ok:

public class SideEffectWithPeek {
   public static void main (String[] args) {
       IntStream.range(0, 5)
                .unordered()
                .parallel()
                .map(x -> x * 2)
                .peek(System.out::println)
                .count();
   }
}

another example

public class SideEffectWrongUseFix {
    public static void main (String[] args) {
        IntStream stream = IntStream.range(0, 1000);
        List<Integer> list = stream.parallel()
                                      .filter(s -> s % 2 == 0)
                                      .boxed()
                                      .collect(Collectors.toList());
        System.out.println(list);
    }
}

Laziness

All intermediate operations are lazy. An expression, method, or algorithm is lazy if its value is evaluated only when it is required. (An algorithm is eager if it is evaluated or processed immediately.) Intermediate operations are lazy because they do not start processing the contents of the stream until the terminal operation commences. Processing streams lazily enables the Java compiler and runtime to optimize how they process streams. For example, in a pipeline such as the filter-mapToInt-average example described in the section Aggregate Operations, the average operation could obtain the first several integers from the stream created by the mapToInt operation, which obtains elements from the filter operation. The average operation would repeat this process until it had obtained all required elements from the stream, and then it would calculate the average.

Interference

Lambda expressions in stream operations should not interfere. Interference occurs when the source of a stream is modified while a pipeline processes the stream. For example, the following code attempts to concatenate the strings contained in the List listOfStrings. However, it throws a ConcurrentModificationException:

try {
    List<String> listOfStrings =
        new ArrayList<>(Arrays.asList("one", "two"));
         
    // This will fail as the peek operation will attempt to add the
    // string "three" to the source after the terminal operation has
    // commenced. 
             
    String concatenatedString = listOfStrings
        .stream()
        
        // Don't do this! Interference occurs here.
        .peek(s -> listOfStrings.add("three"))
        
        .reduce((a, b) -> a + " " + b)
        .get();
                 
    System.out.println("Concatenated string: " + concatenatedString);
         
} catch (Exception e) {
    System.out.println("Exception caught: " + e.toString());
}

This example concatenates the strings contained in listOfStrings into an Optional<String> value with the reduce operation, which is a terminal operation. However, the pipeline here invokes the intermediate operation peek, which attempts to add a new element to listOfStrings. Remember, all intermediate operations are lazy. This means that the pipeline in this example begins execution when the operation get is invoked, and ends execution when the get operation completes. The argument of the peek operation attempts to modify the stream source during the execution of the pipeline, which causes the Java runtime to throw a ConcurrentModificationException.

Stateful Lambda Expressions

Avoid using stateful lambda expressions as parameters in stream operations. A stateful lambda expression is one whose result depends on any state that might change during the execution of a pipeline. The following example adds elements from the List listOfIntegers to a new List instance with the map intermediate operation. It does this twice, first with a serial stream and then with a parallel stream:

List<Integer> serialStorage = new ArrayList<>();
     
System.out.println("Serial stream:");
listOfIntegers
    .stream()
    
    // Don't do this! It uses a stateful lambda expression.
    .map(e -> { serialStorage.add(e); return e; })
    
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");
     
serialStorage
    .stream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");

System.out.println("Parallel stream:");
List<Integer> parallelStorage = Collections.synchronizedList(
    new ArrayList<>());
listOfIntegers
    .parallelStream()
    
    // Don't do this! It uses a stateful lambda expression.
    .map(e -> { parallelStorage.add(e); return e; })
    
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");
     
parallelStorage
    .stream()
    .forEachOrdered(e -> System.out.print(e + " "));
System.out.println("");

The lambda expression e -> { parallelStorage.add(e); return e; } is a stateful lambda expression. Its result can vary every time the code is run. This example prints the following:

Serial stream:
8 7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
Parallel stream:
8 7 6 5 4 3 2 1
1 3 6 2 4 5 8 7

The operation forEachOrdered processes elements in the order specified by the stream, regardless of whether the stream is executed in serial or parallel. However, when a stream is executed in parallel, the map operation processes elements of the stream specified by the Java runtime and compiler. Consequently, the order in which the lambda expression e -> { parallelStorage.add(e); return e; } adds elements to the List parallelStorage can vary every time the code is run. For deterministic and predictable results, ensure that lambda expression parameters in stream operations are not stateful.

Note: This example invokes the method synchronizedList so that the List parallelStorage is thread-safe. Remember that collections are not thread-safe. This means that multiple threads should not access a particular collection at the same time. Suppose that you do not invoke the method synchronizedList when creating parallelStorage:

List<Integer> parallelStorage = new ArrayList<>();

The example behaves erratically because multiple threads access and modify parallelStorage without a mechanism like synchronization to schedule when a particular thread may access the List instance. Consequently, the example could print output similar to the following:

Parallel stream:

8 7 6 5 4 3 2 1
null 3 5 4 7 8 1 2

https://www.logicbig.com/tutorials/core-java-tutorial/java-util-stream/side-effects.html


Java Type Inference

Type inference is a feature of Java which provides ability to compiler to look at each method invocation and corresponding declaration to determine the type of arguments.

Java provides improved version of type inference in Java 8. the following example explains, how we can use type inference in our code:

Here, we are creating arraylist by mentioning integer type explicitly at both side. The following approach is used earlier versions of Java.

List<Integer> list = new ArrayList<Integer>();

In the following declaration, we are mentioning type of arraylist at one side. This approach was introduce in Java 7. Here, you can left second side as blank diamond and compiler will infer type of it by type of reference variable.

List<Integer> list2 = new ArrayList<>();

Improved Type Inference

In Java 8, you can call specialized method without explicitly mentioning of type of arguments.

showList(new ArrayList<>());

Java Type Inference Example

You can use type inference with generic classes and methods.

import java.util.ArrayList;  
import java.util.List;  
public class TypeInferenceExample {  
    public static void showList(List<Integer>list){  
        if(!list.isEmpty()){  
            list.forEach(System.out::println);  
        }else System.out.println("list is empty");  
    }  
    public static void main(String[] args) {  
        // An old approach(prior to Java 7) to create a list  
        List<Integer> list1 = new ArrayList<Integer>();  
        list1.add(11);  
        showList(list1);  
        // Java 7    
        List<Integer> list2 = new ArrayList<>(); // You can left it blank, compiler can infer type  
        list2.add(12);  
        showList(list2);  
        // Compiler infers type of ArrayList, in Java 8  
        showList(new ArrayList<>());  
    }  
}

Output:

11
12
list is empty

You can also create your own custom generic class and methods. In the following example, we are creating our own generic class and method.

Java Type Inference Example 2

class GenericClass<X> {  
      X name;  
    public void setName(X name){  
        this.name = name;  
      }  
    public X getName(){  
        returnname;  
      }  
    public String genericMethod(GenericClass<String> x){  
        x.setName("John");  
        returnx.name;  
      }  
}  
  
public class TypeInferenceExample {  
    public static void main(String[] args) {  
        GenericClass<String> genericClass = new GenericClass<String>();  
        genericClass.setName("Peter");  
        System.out.println(genericClass.getName());  
          
        GenericClass<String> genericClass2 = new GenericClass<>();  
        genericClass2.setName("peter");  
        System.out.println(genericClass2.getName());  
      
        // New improved type inference  
        System.out.println(genericClass2.genericMethod(new GenericClass<>()));  
    }  
}

Output:

Peter
peter
John


Java with Questions

What are default methods?

With java 8, an interface can have default implementation of a function in interfaces.

What are static default methods?

An interface can also have static helper methods from Java 8 onwards.

public interface vehicle {
   default void print() {
      System.out.println("I am a vehicle!");
   }
 
   static void blowHorn() {
      System.out.println("Blowing horn!!!");
   }
}


How will you call a default method of an interface in a class?

Using super keyword along with interface name.

interface Vehicle {
   default void print() {
      System.out.println("I am a vehicle!");
   }
}
class Car implements Vehicle {
   public void print() {
      Vehicle.super.print();                  
   }
}

How will you call a static method of an interface in a class?

Using name of the interface.

interface Vehicle {
   static void blowHorn() {
      System.out.println("Blowing horn!!!");
   }
}
class Car implements Vehicle {
   public void print() {
      Vehicle.blowHorn();                  
   }
}

What is streams in Java 8?

Stream represents a sequence of objects from a source, which supports aggregate operations.

What is stream pipelining in Java 8?

Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target. collect() method is a terminal operation which is normally present at the end of the pipelining operation to mark the end of the stream.

What is the difference between Collections and Stream in Java8 ?

Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.

What is Parallel Processing in Java 8?

parallelStream is the alternative of stream for parallel processing. Take a look at the following code segment that prints a count of empty strings using parallelStream.

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
int count = strings.parallelStream().filter(string > string.isEmpty()).count();
//It is very easy to switch between sequential and parallel streams.

What are collectors in Java 8?

Collectors are used to combine the result of processing on the elements of a stream. Collectors can be used to return a list or a string.

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("Filtered List: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("Merged String: " + mergedString);

What are Statistics collectors in Java 8?

With Java 8, statistics collectors are introduced to calculate all statistics when stream processing is being done.

Following code will print the highest number present in a list.

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = integers.stream().mapToInt((x) > x).summaryStatistics();
System.out.println("Highest number in List : " + stats.getMax());

What is Optional in Java8?

Optional is a container object which is used to contain not-null objects. Optional object is used to represent null with absent value. This class has various utility methods to facilitate code to handle values as 'available' or 'not available' instead of checking null values. It is introduced in Java 8 and is similar to what Optional is in Guava.

What is Nashorn in Java8?

With Java 8, Nashorn, a much improved javascript engine is introduced, to replace the existing Rhino. Nashorn provides 2 to 10 times better performance, as it directly compiles the code in memory and passes the bytecode to JVM. Nashorn uses invokedynamics feature, introduced in Java 7 to improve performance.

What is jjs in JAVA8?

For Nashorn engine, JAVA 8 introduces a new command line tool, jjs, to execute javascript codes at console.

Can you execute javascript code from java 8 code base?

Yes! Using ScriptEngineManager, JavaScript code can be called and interpreted in Java.

What is local datetime API in JAVA8?

Local − Simplified date-time API with no complexity of timezone handling.