Java 8 - Yash-777/MyWorld GitHub Wiki

Java 8 ships with several new features but the most significant are the following:

Feature Name Description
Lambda expression A function that can be shared or referred to as an object.
a new language feature allowing treating actions as objects
Functional Interfaces Single abstract method interface.
an interface with maximum one abstract method, implementation can be provided using a Lambda Expression
Method References Uses function as a parameter to invoke a method.
enable defining Lambda Expressions by referring to methods directly using their names
Default method It provides an implementation of methods within interfaces enabling 'Interface evolution' facilities.
give us the ability to add full implementations in interfaces besides abstract methods
Stream API Abstract layer that provides pipeline processing of the data.
a special iterator class that allows processing collections of objects in a functional manner
Date Time API New improved joda-time inspired APIs to overcome the drawbacks in previous versions
an improved, immutable JodaTime-inspired Date API
Optional Wrapper class to check the null values and helps in further processing based on the value.
special wrapper class used for expressing optionality
Nashorn, JavaScript Engine An improvised version of JavaScript Engine that enables JavaScript executions in Java, to replace Rhino.
Java-based engine for executing and evaluating JavaScript code Example
StringJoiner StringJoiner joinNames = new StringJoiner(",", "[", "]");
Java 8 new features

All functional interfaces are recommended to have an informative @FunctionalInterface annotation.

Any Interface with single abstract method is called Functional Interface. Since there is only one abstract method, there is no confusion in applying the lambda expression to that method.

A functional interface SAM(Single Abstract Method)

  • default methods do not count
  • not matching a public method of java.lang.Object

Where its implementation may be treated as lambda expressions.

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
      Objects.requireNonNull(after);
      return (T t, U u) -> after.apply(apply(t, u));
    }
}

Functional interfaces, which are gathered in the java.util.function package, satisfy most developers’ needs in providing target types for lambda expressions and method references. Each of these interfaces is general and abstract, making them easy to adapt to almost any lambda expression. There are a lot of functional interfaces in the java.util.function package, the more common ones include but not limited to:

  • Function – it takes one argument and returns a result
  • Consumer – it takes one argument and returns no result (represents a side effect)
  • Supplier – it takes not argument and returns a result
  • Predicate – it takes one argument and returns a boolean
  • BiFunction – it takes two arguments and returns a result
  • BinaryOperator – it is similar to a BiFunction, taking two arguments and returning a result. The two arguments and the result are all of the same types
  • UnaryOperator – it is similar to a Function, taking a single argument and returning a result of the same type
class Method_References {
    main() {
        BiFunction<Integer, Integer, Integer> adder = Method_References::add;
        Integer apply = adder.apply(10, 20);
          System.out.println("Apply : "+ apply);
    }
    public static int add(int a, int b){
        return a+b;
    }
}

Method References

Method references help to point to methods by their names. A method reference is described using :: (double colon) symbol. A method reference can be used to point the following types of methods −

  • Static methods (Containing-Class::staticMethodName)
    System.out::println method is a static method reference to println method of out object of System class.
Class System {
    static PrintStream out;
}
Class PrintStream {
    println();
}

public static void staticCalls() {
    Random random = new Random();
    random.ints().limit(10).forEach(System.out::println);
    
    // Static Reference @FunctionalInterface « <arg1, arg2, return>
    BiFunction<Integer, Integer, Integer> args2WithRetunVal = LambdaExpression::add;
    Integer applyArgs = args2WithRetunVal.apply(10, 20);
    System.out.println("Accepts two arguments and produces a result : "+ applyArgs);
    
    Function<Integer, Integer> args1WithRetunVal = LambdaExpression::add10;
    Integer applyArgument = args1WithRetunVal.apply(10);
    System.out.println("Accepts one argument and produces a result : "+ applyArgument);

    Consumer<Integer> args1WithNoReturnVal = LambdaExpression::printVlaue;
    args1WithNoReturnVal.accept(10);
}
  • Instance methods (containingObject::instanceMethodName)
// Instance Reference
Consumer<Integer> args1WithNoReturnVal_OBJ = new LambdaExpression()::printVlaue_obj;
args1WithNoReturnVal_OBJ.accept(20);
  • Constructors using new operator (TreeSet::new)
Stream<Integer> intStream = Stream.of(1,2,3,4);
Integer[] intArray = intStream.toArray(Integer[]::new);

Reference Class:

public class LambdaExpression {
    public static int add(int a, int b){
        return a+b;
    }
    public static int add10(int a){
        return a+10;
    }
    public static void printVlaue(int a){
        System.out.println("accepts a single input argument and returns no result."+ a);
    }
    public void printVlaue_obj(int a){
        System.out.println("OBJ « accepts a single input argument and returns no result."+ a);
    }
}

Lambda Expressions

Java 8 provide support for lambda expressions only with functional interfaces. Functional Interface is also a new feature introduced in Java 8. Any Interface with single abstract method is called Functional Interface. Since there is only one abstract method, there is no confusion in applying the lambda expression to that method.

[Note](https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html#Parameter Types): If you want to pass a method into a method, then use a lambda expression or a method reference.

Syntax:

(parameters) -> expression
    (or) 
(parameters) -> { statements; }
alt text

Example :

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

// Anonymous InnerClass
Runnable r = new Runnable() {
    public void run() {
        System.out.println("Anonymous InnerClass");
    }
};
Thread t = new Thread(r);
t.start();

// Runnable using Lambda expression.
Runnable r2 = () -> System.out.println("Runnable using Lambda expression.");
Thread t2 = new Thread( r2 );
t2.start();

// Iterating over collection elements.
List<String> stringList = new ArrayList<String>();
stringList.add("Yash");
stringList.add("M");

for (String string : stringList) {
    System.out.println("Contents of List : "+string);
}

stringList.stream().forEach( (string) -> { System.out.println("Contents : "+string); } );

Lambda Expression and Objects

In Java, any lambda expression is an object as is an instance of a functional interface. We can assign a lambda expression to any variable and pass it like any other object. See the example below on how a lambda expression is assigned to a variable, and how it is invoked.

@FunctionalInterface 
interface TaskComparator {
    public boolean compareTasks (int al, int a2); 
}
public class TaskComparatcrImpL {
    public static void main (String[] args) {
        TaskComparator myTasComparator = (int al, int a2) -> {return al > a2;};
        
        boolean result = myTasComparator.compareTasks(5, 2);
        System.out.println ( result );
        
        boolean result2 = myTasComparator.compareTasks(5, 10);
        System.out.println ( result2 );
    }
}

Stream API

The Streams API will internally decompose your query to leverage the multiple cores on your computer.

To summarize what we’ve learned so far, working with streams, in general, involves three things:

  • A datasource (such as a collection) on which to perform a query
  • A chain of intermediate operations, which form a stream pipeline
  • One terminal operation, which executes the stream pipeline and produces a result
String lastItem = Stream.of(str.split("-")).reduce((first,last)->last).get();

Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
Collections.sort( empList, Comparator.comparing( Employee::getCreationTime )
                                     .thenComparing( Employee::getName ));

Example: Lesson: Aggregate Operations

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;
    
    // ...

    public int getAge() {
        // ...
    }

    public String getName() {
        // ...
    }
}

Pipelines and Streams « A pipeline is a sequence of aggregate operations. The following example prints the male members contained in the collection roster with a pipeline that consists of the aggregate operations filter and forEach:

roster
    .stream()
    .filter(e -> e.getGender() == Person.Sex.MALE)
    .forEach(e -> System.out.println(e.getName()));

Compare this example to the following that prints the male members contained in the collection roster with a for-each loop:

for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}

parallelism – there is an overhead in the added complexity of identifying and breaking out the units of work and then coordinating the parallel processing. Then there is also the issue of latency in reading the data – the savings found in using parallel cores for processing is to try to keep each CPU core busy all the time – which requires reading data as and when is needed without delay.

Spliterator interface A new interface added to java.util is the Spliterator, which as the name implies, is a new special kind of Iterator that can traverse a Collection
Spliterator itself does not provide the parallel processing behaviour. Instead, the Spliterator is there to support parallel traversal of the suitably partitioned parts of a Collection.

List<String> stringList = new ArrayList<String>();
        stringList.add("Yash");
        stringList.add("M");

Stream<String> stream = stringList.stream();
stream.forEach( (string) -> { System.out.println("Contents : "+string); } );

// Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
Spliterator<String> splitIttr = stringList.spliterator();
System.out.println("Spliterator Char : "+ splitIttr.characteristics());

java.util.Collections « java.util.Spliterator « Example:

public interface Iterable<T> {
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

public interface Collection<E> extends Iterable<E> {

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
}

public interface List<E> extends Collection<E> {
    @snice1.8
    replaceAll(UnaryOperator<E> operator) {}
    sort(Comparator<? super E> c) {}
    
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

public class ArrayList<E> extends AbstractList<E>
                          implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    @Override
    public Spliterator<E> spliterator() {
        return new ArrayListSpliterator<>(this, 0, -1, 0);
    }

    /** Index-based split-by-two, lazily initialized Spliterator */
    static final class ArrayListSpliterator<E> implements Spliterator<E> {
     //...
    }
}
List<String> listItems = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date API", "Lambdas");

System.out.println("Functional interface Predicate.");
Predicate<String> startsWith = names -> names.startsWith("D");

listItems.stream()
    .filter( endsWith )
    .forEach( System.out::println ); // .collect(Collectors.toList());

Sorting Techniques (Comparable-CompareTo(obj), Comparator-comapre(obj1, obj2) )

public interface Comparable<T> {
    public int compareTo(T o);
}

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }
}

Nashorn is a JavaScript engine developed in the Java programming language by Oracle. It is based on the Da Vinci Machine and has been released with Java 8.

Example

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

// Script Variables « expose java objects as variable to script.
engine.put("strJS", str);

engine.eval("print('Script Variables « expose java objects as variable to script.', strJS)");
  • LocalDate – Day, month, year
  • LocalTime – Time of day only
  • LocalDateTime – Both date and time
  • ZonedDateTime - timezone specific
  • Duration is a time-based amount of time, such as '34.5 seconds'.
  • Period is a date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
// Preior to Java8
Date startDate = Calendar.getInstance().getTime();
System.out.println("Calendar : "+ startDate ); // Thu Apr 26 15:42:03 IST 2018

LocalTime java8 = LocalTime.now();
System.out.println("Java 8 : "+ java8); // 15:42:04.080

Instant and Duration: Example for method execution time.

Instant start = Instant.now();
//...
Thread.sleep(5000);
//...
Instant end = Instant.now();
System.out.println(Duration.between(start, end));

Examples:

Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 };
List<Integer> listOfIntegers =
    new ArrayList<>(Arrays.asList(intArray));
    System.out.println("Sum of integers: " +
        listOfIntegers
            .stream()
            .reduce(Integer::sum).get());

Figure 1 illustrates the Java SE 8 code. First, we obtain a stream from the list of transactions (the data) using the stream() method available on List. Next, several operations (filter, sorted, map, collect) are chained together to form a pipeline, which can be seen as forming a query on the data.

http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2179048.jpg

Getting Started with Streams

Let’s start with a bit of theory. What’s the definition of a stream? A short definition is “a sequence of elements from a source that supports aggregate operations.” Let’s break it down:

  • Sequence of elements: A stream provides an interface to a sequenced set of values of a specific element type. However, streams don’t actually store elements; they are computed on demand. Source: Streams consume from a data-providing source such as collections, arrays, or I/O resources.

  • Aggregate operations: Streams support SQL-like operations and common operations from functional programing languages, such as filter, map, reduce, find, match, sorted, and so on. Furthermore, stream operations have two fundamental characteristics that make them very different from collection operations:

  • Pipelining: Many stream operations return a stream themselves. This allows operations to be chained to form a larger pipeline. This enables certain optimizations, such as laziness and short-circuiting, which we explore later.

  • Internal iteration: In contrast to collections, which are iterated explicitly (external iteration), stream operations do the iteration behind the scenes for you.

http://www.oracle.com/ocom/groups/public/@otn/documents/digitalasset/2179051.jpg


java.util.stream.Collectors

You can just use the Collectors.groupingBy() by passing the grouping logic as function parameter and you will get the splitted list with the key parameter mapping. Note that using Optional is used to avoid the unwanted NPE when the provided list is null

Optional.ofNullable(list)
            .orElseGet(ArrayList::new)
            .stream()
@lombok.Data
@lombok.AllArgsConstructor
class Emp {
    String name; int age;
    String country, state, district;
}
public class Test4 {
    public static void main(String[] args) {
        List<Emp> empList = 
                Arrays.asList( 
                        (new Emp("A", 19, "IND", "TS", "HYD")),
                        (new Emp("B", 9, "IND", "TS", "KNR")),
                        (new Emp("AC", 21, "IND", "TS", "PDPL")),
                        (new Emp("Ay", 21, "IND", "AP", "VZG")),
                        (new Emp("C", 21, "IND", "AP", "KRNL")),
                        (new Emp("Cg", 21, "IND", "UP", "MP")),
                        (new Emp("yC", 21, "IND", "KL", "TRNPM")),
                        (new Emp("po", 21, "IND", "TN", "CN")),
                        (new Emp("yt", 21, "US", "NC", "NC"))
                        );
        
        Map<String, List<Emp>> res = empList.stream()
                .collect(Collectors.groupingBy(Emp::getCountry));
                //.collect(Collectors.groupingBy(i -> i.getCountry()));
        System.out.println(res);
        
        Map<String, Map<String, List<Emp>>> res2 = empList.stream()
                .collect( Collectors.groupingBy(Emp::getCountry, Collectors.groupingBy(Emp::getState) ) );
        System.out.println(res2);
    }
}
{US=[Emp(name=yt, age=21, country=US, state=NC, district=NC)], IND=[Emp(name=A, age=19, country=IND, state=TS, district=HYD), Emp(name=B, age=9, country=IND, state=TS, district=KNR), Emp(name=AC, age=21, country=IND, state=TS, district=PDPL), Emp(name=Ay, age=21, country=IND, state=AP, district=VZG), Emp(name=C, age=21, country=IND, state=AP, district=KRNL), Emp(name=Cg, age=21, country=IND, state=UP, district=MP), Emp(name=yC, age=21, country=IND, state=KL, district=TRNPM), Emp(name=po, age=21, country=IND, state=TN, district=CN)]}
{US={NC=[Emp(name=yt, age=21, country=US, state=NC, district=NC)]}, IND={KL=[Emp(name=yC, age=21, country=IND, state=KL, district=TRNPM)], TN=[Emp(name=po, age=21, country=IND, state=TN, district=CN)], UP=[Emp(name=Cg, age=21, country=IND, state=UP, district=MP)], AP=[Emp(name=Ay, age=21, country=IND, state=AP, district=VZG), Emp(name=C, age=21, country=IND, state=AP, district=KRNL)], TS=[Emp(name=A, age=19, country=IND, state=TS, district=HYD), Emp(name=B, age=9, country=IND, state=TS, district=KNR), Emp(name=AC, age=21, country=IND, state=TS, district=PDPL)]}}

In Multiple Inheritance: two Interfaces are having the same default print():void

Interface 1 default print():void Interface 2 default print():void

interface I1 {
    static void i1() {
        System.out.println("I1 static function");
    }
    default void print() {
        System.out.println("I1 print");
    }
}

interface I2 {
    static void i2() {
        System.out.println("I2 static function");
    }
    default void print() {
        System.out.println("I2 print");
    }
}

public class MultipleInheritance implements I1, I2 {
    public static void main(String[] args) {
        System.out.println("Main Thread start...");
        (new MultipleInheritance()).print();
    }
    @Override
    public void print() {
        I1.super.print(); // Calling print of I1 interface
    }
}

What is MetaSpace? How does it differ from PermGen?

  • PremGen: MetaData information of classes was stored in PremGen (Permanent-Generation) memory type before Java 8. PremGen is fixed in size and cannot be dynamically resized. It was a contiguous Java Heap Memory.

  • MetaSpace: Java 8 stores the MetaData of classes in native memory called 'MetaSpace'. It is not a contiguous Heap Memory and hence can be grown dynamically which helps to overcome the size constraints. This improves the garbage collection, auto-tuning, and de-allocation of metadata.


What are the core API classes for date and time in Java 8?

There are three main core API classes for date and time in Java 8 as given below:

  • LocalDate LocalDate currentDate = LocalDate.now();
  • LocalTime LocalTime currentTime = LocalTime.now();
  • LocalDateTime

What are the main components of a Stream?

Components of the stream are:

  • A data source
  • Set of Intermediate Operations to process the data source
  • Single Terminal Operation that produces the result

image



Method References oracle tutorial

You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

There are four kinds of method references:

Kind Syntax Examples
Reference to a static method ContainingClass::staticMethodName Person::compareByAgeMethodReferencesExamples::appendStrings
Reference to an instance method of a particular object containingObject::instanceMethodName myComparisonProvider::compareByNamemyApp::appendStrings2
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName String::compareToIgnoreCaseString::concat
Reference to a constructor ClassName::new HashSet::new



Lambda Expressions (also called closures)oracle article part1, part2

Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression.

Lambda expression is an anonymous function that takes in parameters and returns a value. It is called an anonymous function because it doesn't require a name.

Why use Lambda Expression

  • To provide the implementation of Functional interface.
  • Less coding.

Syntax. A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body, which can either be a single expression or a block of Java code.

(parameter1, parameter2, ...) -> expression
(argument-list) -> { body }  

Lambda expressions. Funda-men-tally, a lambda expression is just a shorter way of writing an implementation of a method for later execution. Thus, while we used to define a Runnable as shown in Listing 2, which uses the anonymous inner class syntax and clearly suffers from a “vertical problem” (meaning that the code takes too many lines to express the basic concept), the Java 8 lambda syntax allows us to write the code as shown in Listing 3.

Comparator and Comparable

//Listing 2
public class Lambdas {
  public static void main(String... args) {
    Runnable r = new Runnable() {
      public void run() {
        System.out.println("Howdy, world!");
      }
    };
    r.run();
  }
}
//Listing 3
public static void main(String... args) {
    Runnable r2 = () -> System.out.println("Howdy, world!");
    r2.run();
  }

// ----------------------------------------
// Sort with Inner Class
Collections.sort(personList, new Comparator<Person>() {
  public int compare(Person p1, Person p2) {
    return p1.getSurName().compareTo(p2.getSurName());
  }
});

(Person p1, Person p2) -> p1.getSurName().compareTo(p2.getSurName())


Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b); // Integer.compare(a,b) internally Calls - a.compareTo(b)
//Comparable<Integer> comparable = (i,j) -> i.compareTo(j); // Comparable single sort sequence, Not allow inner class implementation
// Lambda expression's signature does not match the signature of the functional interface method compareTo(Integer)
Comparator<Integer> comparable = (i,j) -> i.compareTo(j);

Functional interfaces. The Runnable interface—like the Callable interface, the Comparator interface, and a whole host of other interfaces already defined within Java—is what Java 8 calls a functional interface: it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface. This is how the syntax achieves its brevity, because there is no ambiguity around which method of the interface the lambda is trying to define.

Example:

interface Addable{  
    int add(int a,int b);  
}  
  
public class LambdaExpressionExample {  
    public static void main(String[] args) {  
          
        // Lambda expression without return keyword.  
        Addable ad1=(a,b)->(a+b);  
        System.out.println(ad1.add(10,20));  
          
        // Lambda expression with return keyword.    
        Addable ad2=(int a,int b)->{  
                            return (a+b);   
                            };  
        System.out.println(ad2.add(100,200));  
        
        List<Integer> list = Arrays.asList( 0, 1, 2, 5, 4, 5, 8, 1, 7, 4, 1, 9, 5, 0 );
        // Lambda Expression inside Foreach Loop
        list.forEach(  
                (n)->System.out.println(n)  
            ); 
            
        // Lambda Expression with Comparator
        List<Product> list=new ArrayList<Product>();  
        //Adding Products   class Product{ int id; String name; float price; }
        list.add(new Product(1,"HP Laptop",25000f));  
        list.add(new Product(3,"Keyboard",300f));  
        list.add(new Product(2,"Dell Mouse",150f));  
          
        System.out.println("Sorting on the basis of name...");  
  
        // implementing lambda expression  
        Collections.sort( list,(p1,p2)->{  
            return p1.name.compareTo(p2.name);  
        } );  
    }  
}

Package java.util.function

A functional interface is an interface that contains exactly one abstract method. It is also called Single Abstract Method (SAM) Interfaces. In Java, an interface is made functional by annotating it with @FunctionalInterface.

  • Since default methods have an implementation, they are not abstract.
  • If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

It can have any number of default, static methods but can contain only one abstract method. It can contain any number of Object class methods.

@FunctionalInterface  
interface sayable{  
    void say(String msg);   // abstract method  
    // It can contain any number of Object class methods.  
    int hashCode();  
    String toString();  
    boolean equals(Object obj);  
}  

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj); //does not count toward the interface's abstract method

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    } // ...
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }
}

public interface Comparable<T> {
    public int compareTo(T o);
}

Invalid Functional Interface compile-time error A functional interface can extends another interface only when it does not have any abstract method.

interface sayable{  
    void say(String msg);   // abstract method  
}  
@FunctionalInterface  
interface Doable extends sayable{  
    // Invalid '@FunctionalInterface' annotation; Doable is not a functional interface  
    void doIt();  
}

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

// Assignment context
Predicate<String> p = String::isEmpty;

// Method invocation context
stream.filter(e -> e.getSize() > 10)...

// Cast context
stream.map((ToIntFunction) e -> e.getSize())...

Streams stackabuse.com oracle article part1, part2

A Stream is a sequence of objects that supports many different methods that can be combined to produce the desired result.

Consider Java stream as a pipeline that consists of a stream source followed by zero or more intermediate operations and a terminal operation. Stream is not a collection or a data structure where we can store data.

Types of Streams

Streams can be sequential (created with stream()), or parallel (created with parallelStream()). Parallel streams can operate on multiple threads, while sequential streams can't.

List<Integer> list = Arrays.asList( 0, 1, 2, 5, 4, 5, 8, 1, 7, 4, 1, 9, 5, 0 );
System.out.println(list);

list.parallelStream().forEach(i -> System.out.print(" "+i));
System.out.println(); // 7 4 1 0 9 1 1 2 8 4 5 5 0 5
list.parallelStream().forEach(i -> System.out.print(" "+i));
System.out.println(); // 7 4 1 4 9 1 5 5 2 8 0 0 1 5
list.parallelStream().forEachOrdered(i -> System.out.print(" "+i));
System.out.println(); // 0 1 2 5 4 5 8 1 7 4 1 9 5 0


list.stream().forEach(i -> System.out.print(" "+i));
System.out.println(); // 0 1 2 5 4 5 8 1 7 4 1 9 5 0
list.stream().forEach(i -> System.out.print(" "+i));
System.out.println(); // 0 1 2 5 4 5 8 1 7 4 1 9 5 0

From the output, we can see that the forEach() method doesn't maintain the order of the stream, whereas the forEachOrdered() method maintains the order of the parallel-stream. collect() The collect() method performs a mutable reduction operation on the elements of the stream using a Collector.

Parallel Stream

By default, all stream operations are sequential in Java unless explicitly specified as parallel. Parallel streams are created in Java in two ways.

  • Calling the parallel() method on a sequential stream.
  • Calling parallelStream() method on a collection.

Parallel streams are useful when we have many independent tasks that can be processed simultaneously to minimize the running time of the program.

list.stream().parallel().forEach(i -> System.out.print(" "+i));
System.out.println(); // 7 4 1 4 9 1 5 5 2 8 0 0 1 5

The stream instance is converted to parallel stream by calling stream.parallel(). Since the forEach() method is called on a parallel stream, the output order will not be same as the input order because the elements are processed parallel.

Different Operations on Streams

Stream provides various operations that can be chained together to produce results. Stream operations can be classified into two types.

  • Intermediate Operations: Intermediate operations are used to perform actions on stream data and return another stream as output.
  • Terminal Operations: Terminal operations produce the result of the stream after all the intermediate operations are applied.

Intermediate operations are used to perform actions on stream data and return another stream as output. Intermediate operations include the following methods:

  • map(op) - Returns a new stream in which the provided op function is applied to each of the elements in the original stream

        final List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
    
        final List<Integer> ans = list.stream()
                .map(value -> value * 10)
                .collect(Collectors.toList());
    
        System.out.println(Arrays.toString(ans.toArray())); //[10, 20, 30, 40, 50]
  • filter(cond) - Returns a new stream which only contains the elements from the original stream that satisfy the condition cond, specified by a predicate

  • sorted() - The sorted() method returns a stream with the elements of the stream sorted according to natural order or the provided Comparator.

    final List<Integer> list = new ArrayList<>(Arrays.asList(5, 1, 3, 4, 2));
    
    System.out.println("Ascending Order");
    list.stream().sorted()
        .forEach(System.out::println);
    
    System.out.println("\nDescending Order");
    list.stream().sorted(Comparator.reverseOrder())
        .forEach(System.out::println);
processElements Action Aggregate Operation
Obtain a source of objects Stream stream()
Filter objects that match a Predicate object Stream filter(Predicate<? super T> predicate)
Map objects to another value as specified by a Function object Stream map(Function<? super T,? extends R> mapper)
Perform an action as specified by a Consumer object void forEach(Consumer<? super T> action)

Terminal operations produce the result of the stream after all the intermediate operations are applied. Some of the terminal operations are:

  • collect() - Returns the result of the intermediate operations performed on the original stream
  • forEach() - A void method used to iterate through the stream
  • reduce() - Returns a single result produced from an entire sequence of elements in the original stream
Stream.of(1, 2, 3, 4, 5)          // Stream source
    .filter(x -> x % 2 == 0)      // Intermediate operation
    .collect(Collectors.toList()) // Terminal operation

Stream.Filter()

The filter() method is an intermediate operation of the Stream interface that allows us to filter elements of a stream that match a given Predicate:

Predicate and BiPredicate are usually used to apply in a filter for a collection of objects.
Returns: true if the input arguments match the predicate, otherwise false

@FunctionalInterface public interface Predicate<T> @FunctionalInterface Interface BiPredicate<T,U>
Predicate is a functional interface in Java that accepts a single input and can return a boolean value. BiPredicate is a functional interface in Java that accepts two inputs and can return a boolean value.
Represents a predicate (boolean-valued function) of one argument. Represents a predicate (boolean-valued function) of two arguments.
Predicate<Integer> isEven = (value) -> value % 2 == 0;
Predicate<Integer> isNotZero = (value) -> value != 0;
BiPredicate<Integer, Integer> isDivisible = (a, b) -> a % b == 0;
Predicate<Integer> isEven = (value) -> {
        try {
            return value % 2 == 0;
        } catch (Exception e) {
            //handle exception
        }
        return false;
    };
Predicate<Integer> isEven = (value) -> value % 2 == 0;
List<Integer> evenList = list.stream()
        .filter(isEven)
        .collect(Collectors.toList());
System.out.println(evenList); //[0, 2, 4, 8, 4, 0]

Stream.map()

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.

List<String> alpha = Arrays.asList("a", "b", "c", "d");
List<String> collect = alpha.stream()
        .map(String::toUpperCase)
        .collect(Collectors.toList());
                //.forEach( n -> System.out.println( n.toUpperCase() ) ); //.forEach( System.out::println );
System.out.println(collect); //[A, B, C, D]

The most-common methods you will use to convert a stream to a specialized version are mapToInt, mapToDouble, and mapToLong.

Stream.reduce(), Stream.count()

The count() method returns the total number of elements in the stream, 5.

The Stream.reduce(Integer identity, BinaryOperator<Integer> accumulator) method performs a reduction on the elements of the stream and returns the value.

identity - the identity value for the accumulating function
accumulator - an associative, non-interfering, statelessfunction for combining two values

Integer sum = integers.reduce(0, (a, b) -> a+b);
// or
Integer sum = integers.reduce(0, Integer::sum);
List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 6));

//reduceClosureFunciton
int sum = 0;
for (int x : numbers) {
    sum += x; 
}
//We essentially “reduced” the list of numbers into one number. There are two parameters in this code: the initial value of the sum variable,
// in this case 0, and the operation for combining all the elements of the list, in this case +.
int reduceSum = list2.stream().reduce(0, (value, sum) -> sum += value);
System.out.println("reduce Sum: " + reduceSum);  // 27

int mapSum = list2.stream().mapToInt(i -> i).sum();
System.out.println("mapToInt Sum: " + mapSum);  // 27

long count = list2.stream().count();
System.out.println("Count: " + count); // 7

Stream.max(), Stream.min()

The min() and max() methods return an Optional that contains the minimum and maximum elements of the stream, respectively, according to the provided comparator.

List<Integer> list = Arrays.asList( 0, 1, 2, 5, 4, 5, 8, 1, 7, 4, 1, 9, 5, 0 );
System.out.println(list);

Integer maxVal = list.stream().max( (i,j) -> i.compareTo(j) ).get();
System.out.println("Max value: "+maxVal); //9
Integer minVal = list.stream().min( (a, b) -> Integer.compare(a, b) ).get();
System.out.println("Min Value: "+minVal); //0

int maxValue = list.stream().reduce(1, Integer::max);
System.out.println("maxValue: "+maxValue);
int minValue = list.stream().reduce(1, Integer::min);
System.out.println("minValue: "+minValue);

Stream.distinct()

The distinct() method is a stateful method (keeps the state of previous elements in mind) and compares elements using the equals() method. If they are distinct/unique, they're returned back, which we can populate into another list.

List<String> list = new ArrayList(Arrays.asList("A", "B", "C", "D", "A", "B", "C", "A", "F", "C"));

List<String> distinctElementList = list.stream()
        .distinct()
        .collect(Collectors.toList());

numeric ranges

Finally, another useful form of numeric streams is numeric ranges. For example, you might want to generate all numbers between 1 and 100. Java SE 8 introduces two static methods available on IntStream, DoubleStream, and LongStream to help generate such ranges: range and rangeClosed.

IntStream oddNumbers = 
    IntStream.rangeClosed(10, 30)
             .filter(n -> n % 2 == 1);

Collectors.toMap()

Alternatively, you can also count the occurances of duplicate elements and keep that information in a map that contains the duplicate elements as keys and their frequency as values.

Map<Integer, Long> map = list.stream()
        .collect(Collectors.toMap(Function.identity(), value -> 1, Integer::sum));
System.out.println(map);

Collectors.groupingBy(Function.identity(), Collectors.counting())

In our case, the method receives two parameters - Function.identity(), that always returns its input arguments and Collectors.counting(), that counts the elements passed in the stream.

Map<Integer, Long> map = list.stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(map);

Collections.frequency() Collections.frequency() is another method that comes from Java Collections class that counts the occurrences of a specified element in the input stream by traversing each element. It takes two parameters, the collection and the element whose frequency is to be determined.

Now, we'll filter() the stream for each element that has a frequency() larger than 1:

Set<Integer> repeatedMoreThanOnce = list.stream()
        .filter(i -> Collections.frequency(list, i) > 1)
        .collect(Collectors.toSet());
Stream Stream in Detail oracle article

List transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());
image

Files.lines(Paths.get("stuff.txt"))
            .map(line -> line.split("\\s+")) // Stream
            .flatMap(Arrays::stream) // Stream
            .distinct() // Stream
            .forEach(System.out::println);
image

Map> transactionsByCurrencies =
    transactions.stream().collect(groupingBy(
     Transaction::getCurrency));
image

Java StringJoiner

Java added a new final class StringJoiner in java.util package. It is used to construct a sequence of characters separated by a delimiter. Now, you can create string by passing delimiters like comma(,), hyphen(-) etc. You can also pass prefix and suffix to the char sequence.

StringJoiner joinNames = new StringJoiner(",", "[", "]");   // passing comma(,) and square-brackets as delimiter   
joinNames.add("Yash");
joinNames.add("Yash777");

StringJoiner joinNames2 = new StringJoiner(":", "[", "]");   // passing comma(,) and square-brackets as delimiter   
joinNames2.add("Yash7");
joinNames2.add("Yash777");

System.out.println(joinNames);  // [Yash,Yash777]
System.out.println(joinNames2); // [Yash7:Yash777]
		
StringJoiner merge = joinNames.merge(joinNames2);
System.out.println(merge); // [Yash,Yash777,Yash7:Yash777]
⚠️ **GitHub.com Fallback** ⚠️