Which are the new features of Java 8?

This article lists the main features added in Java 8 and their uses. like

  •  interfaces
    • default methods
    • static methods
  • forEach() method in iterable interface (and Consumers)
  • Lambda expressions
    • double colon : Method reference operator
    • functional interfaces
  • Java Stream API
  • Improvements in Collection API
  • Java Time API
  • Improvements in Concurrency API
  • Java IO
  • Core API
    • withInitial(Supplier supplier) in ThreadLocal static method to create instances.
    • default and static methods for Comparator interface
      • natural ordering
      • reverse order
      • more…
    • min(), max() and sum() methods in Integer, Long and Double wrapper classes.
    • logicalAnd(), logicalOr() and logicalXor() methods in Boolean class.
    • ZipFile.stream() method
      • to get an ordered Stream over the ZIP file entries.
      • Entries appear in the Stream in the order they appear in the central directory of the ZIP file.
    • New utility methods in Math class.
      • exact(), addExact(),substractExact(), incrementExact(), decrementExact(), multipyExact(), negateExact()
      • floorDiv(), modeDiv(), nextDown()
    • jjs command is added to invoke Nashorn Engine.
    • jdeps command is added to analyze class files
    • remove of JDBC-ODBC Bridge
    • remove of PermGen memory space
  • Nashorn
  • Repeating annotations
  • Statically-linked JNI libraries
  • Built-in Functional Interfaces
    • Predicates
    • Functions
    • Suppliers
    • Consumers
    • Comparators
    • Optionals

Method reference operator (double colon :: ) in Java

Method reference operator is used to call a method by referring to it with the help of its class directly. The behaviour is the same as the lambda expressions. But the method reference operator uses a direct reference to the method by name instead of providing a delegate to the method.

Basically, Method References are compact, easy-to-read lambda expressions for methods that already have a name. They can be used in cases where lambda expression does nothing but call an existing method.

Use of method reference operator equivalent to a lambda expression

// using lambda expression
stream.forEach( x-> System.out.println(x));

// using Method Reference Operator
stream.forEach( System.out::println);

A Class Example could be

package com.techstartingpoint.examples.java8.methodReference;

import java.util.List;
import java.util.Arrays;

public class MethodReferenceExampleReplaceLambda {
   public static void main(String[] args) 
      { 

      String[] anArray = {"one","two","three"};
      // Get the stream
      List<String> myListOfStrings = Arrays.asList(anArray);
      // Print the list 
      System.out.println(" Printing using lambda expression ");
      myListOfStrings.forEach(s -> System.out.println(s));
      System.out.println(" Printing using Method Reference ");
      myListOfStrings.forEach(System.out::println);
      }

}

About the parameters

Its formal parameter list must be the same used in the lambda function

e.g. :

x -> methodCalledInLambda(x)

(x,y) -> methodCalledInLambda(x,y)

both can be replaced by

ClassContainingMethod::methodCalledInLambda

Example of Method Reference Operator with 2 parameters

package com.techstartingpoint.examples.java8.methodReference;

import java.util.Arrays;
import java.util.List;
import java.util.function.BiPredicate;

public class MethodReferenceWithTwoParameters {
	
	  public static boolean isSumMultipleOf3(int i1, int i2) {
		    return (i1+i2)%3 == 0;
	  }
	
	  public static void listElementsByCondition(
			    List<Integer> initialList, BiPredicate<Integer, Integer> predicate) {
			      for(Integer i : initialList) {
			        if(predicate.test(i, i + 1)) {
			          System.out.println(i+"+"+(i+1)+" = true");
			        }
			      }
	  }
	
	
    public static void main(String[] args) 
    { 
    	
    	List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7);
    	
    	System.out.println("using lambda expression with 2 parameters");
    	listElementsByCondition(numberList, (n1, n2) -> MethodReferenceWithTwoParameters.isSumMultipleOf3(n1, n2));
    	
    	System.out.println("using method reference with 2 parameters");
    	listElementsByCondition(numberList,MethodReferenceWithTwoParameters::isSumMultipleOf3);
}   }
}}

Static method

// static method
(ClassName::staticMethodName)


// instanceMethod
(objectName::instanceMethodName)
(new ClassName())::instanceMethodName

// super method
SuperClassName::superMethodName

// Class constructor
ClassName::new

Example of Method Reference Operator with Constructor

package com.techstartingpoint.examples.java8.methodReference;

import java.util.Arrays;
import java.util.List;

public class MethodReferenceConstructorExample {

	public MethodReferenceConstructorExample(String aValue) {
				System.out.println("Creating an ExampleClass Instance :"+aValue);
	}

	    public static void main(String[] args) 
	    { 
	    	String[] anArray = {"one","two","three"};
	        // Get the stream
	    	List<String> myListOfStrings = Arrays.asList(anArray);
	        // Print the list 
	        System.out.println(" Printing using lambda expression ");
	        myListOfStrings.forEach(s -> new MethodReferenceConstructorExample(s));
	        System.out.println(" Printing using Method Reference ");
	        myListOfStrings.forEach(MethodReferenceConstructorExample::new);
	    } 
}



Example calling super method using Reference Method Operator

ParentClassExample.java

package com.techstartingpoint.examples.java8.methodReference.util;

public class ParentClassExample {

   public String printAString(String str) {
      String returnValue="Parent Class print implementation " + str ;
      return returnValue;
   }
}

ChildClassExample -> here is the super::method operator

package com.techstartingpoint.examples.java8.methodReference.util;

import java.util.function.Function;

public class ChildClassExample extends ParentClassExample {
@Override
public String printAString(String str) 
   { 
   String resultValueFromChildMethod="Child Class print implementation " + str ;
   System.out.println("child Value:"+resultValueFromChildMethod);
   System.out.println("calling to the parent class method"); 
   Function<String,String> theParentMethod= super::printAString;
   String resultValueFromParentMethod = theParentMethod.apply(str);
   System.out.println("child Value:"+resultValueFromParentMethod);

   return resultValueFromChildMethod; 
   } 
}



Class to execute the solution

package com.techstartingpoint.examples.java8.methodReference;

import java.util.Arrays;
import java.util.List;
import com.techstartingpoint.examples.java8.methodReference.util.ChildClassExample;

public class MethodReferenceSuperClassExample {

public static void main(String[] args) 
   { 
      String[] anArray = {"one","two","three"};
      // Get the stream
      List<String> myListOfStrings = Arrays.asList(anArray);

     // call the instance method 
     // using double colon operator 
     myListOfStrings.forEach((new ChildClassExample())::printAString); 
   } 
}

 

 

Reference: https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Interfaces in Java 8

Default method

A default method on an interface that can be implemented.

Differences with abstract classes

  • method of abstract classes can be private
  • method of an abstract class can be inherited only by its subclasses
  • default interface methods can be implemented only in the terms of calls to other interface methods
  • default interface methods syntax allows extending the Collections Framework interfaces with lambda-oriented methods without breaking any existing implementations
  • default interface methods allow providing a default implementation for defined contracts (Remember that an interface is a contract)
  • A default method cannot override a method from java.lang.Object

What about implementing interfaces with default methods with the same signature?

In the case a class implements at least two interfaces which have a method with the same signature, the class must override (implement) that method. If the class does not implement that method it will be thrown a “Duplicated default method” at compilation time.

Example:

InterfaceA

package com.techstartingpoint.examples.java8.defaultMethod;

@FunctionalInterface
public interface InterfaceA {

   void method1(String str);

   // the method with repeated signature
   default void repeatedMethodName(String str){
       System.out.println("I1 logging::"+str);
   }

   static void print(String str){
      System.out.println("Printing "+str);
   }
}

InterfaceB : Also with a declaration of repeatedMethodName

public interface InterfaceB {
	default void repeatedMethodName(String str){
		System.out.println("Another repeated method:"+str);
	}

}

The class implementing both Interfaces

package com.techstartingpoint.examples.java8.defaultMethod;

public class ImplementationAandBClass implements InterfaceA,InterfaceB {

	@Override
	public void method1(String str) {
		// TODO Auto-generated method stub
		
	}

}


This class throws a DuplicatedDefaultMethod error at compilation time

 

Static methods in Java 8 Interfaces

Static methods are implemented in interfaces. They have a body. The difference with interface default methods is that static methods can not be overridden. They cannot be implemented in classes.

interface static methods can only be called by methods declared in the same interface. They can be called by an interface default method, an interface static method or an interface method implemented in the class.

What about implementing interfaces which have static methods with the same signature.

There is no special consideration at all, as there is no ambiguity between them. The way to call a static interface method is

InterfaceName.staticMethodName(parameters);

then if there are two interfaces with a method with the same signature they will be called as

InterfaceName1.repeatedMethodName(parameter1);
InterfaceName2.repeatedMethodName(parameter2);

Lambda Expressions in Java 8

Lambda expressions allow expressing instances of single-method classes more compactly.

The general syntax is

<parameter list> -> <body>

Where parameter list is a list of parameters enclosed in parenthesis. The list can be empty.

<body> it is a set of expressions and sentences enclosed in braces that define the behaviour of the lambda expression using the parameters.

Examples:

() -> {System.out.println("something")}

(s) -> {System.out.println("I got the parameter "+s)}

((int) x, (int) y) ->  {System.out.println(x+y)}

In lambda expressions, there are optional elements. Lambda expression can have

  • optional type declaration in the parameters list
  • optional parenthesis in parameters list (when there is only one parameter)
  • optional braces in body section (when body contains only one statement)
  • optional return keyword in body section (when it is implemented only by a “return” statement)

Check this meaningful piece of code to check lambda expressions syntax and behaviour

package com.techstartingpoint.examples.java8.lambda;

public class LambdaExpressionExamples {

	// functional interfaces to be used as base types of resulting lambda
	// expressions

	public interface StringFunctionExample {
		String makeSomething(String firstString, String secondString);

	}

	public interface StringFunctionWithOneParameter {
		Integer makeSomethingWithOneParameter(String firstString);
	}

	// a functional interface to show Strings
	public interface PrintingService {
		void show(String s);
	}

	// a functional interface to show Integers
	public interface IntPrintingService {
		void show(Integer s);
	}

	// methods to run the resulting functional interfaces
	private static String runDoubleOperation(String s1, String s2, StringFunctionExample theFunction) {
		return theFunction.makeSomething(s1, s2);
	}

	private static Integer runSingleOperation(String s1, StringFunctionWithOneParameter theFunction) {
		return theFunction.makeSomethingWithOneParameter(s1);
	}

	public static void main(String args[]) {

		// with type declaration
		StringFunctionExample concatStrings = (String a, String b) -> a + b;

		// without type declaration
		StringFunctionExample getFirstString = (a, b) -> a;

		// with return keyword
		StringFunctionExample getFirstString2 = (a, b) -> {
			return a;
		};

		// with several sentences in body
		StringFunctionExample sayHelloToBoth = (a, b) -> {
			String s1 = "Hello a";
			String s2 = "Hello b";
			String result = s1 + " and " + s2;
			return result;
		};

		// one parameter with parenthesis - without return keyword
		StringFunctionWithOneParameter showOnlyTwo = (x) -> 2;

		// one parameter without parenthesis - without return keyword
		StringFunctionWithOneParameter getLength = x -> x.length();

		// lambda expression to print results
		PrintingService thisSimplePrintingService = x -> System.out.println(x);

		IntPrintingService thisIntPrintingService = x -> System.out.println(x);

		thisSimplePrintingService.show(runDoubleOperation("Yellow", "World", concatStrings));
		thisSimplePrintingService.show(runDoubleOperation("Only the First", "Only the second", getFirstString));
		thisSimplePrintingService.show(runDoubleOperation("Only the First", "Only the second", getFirstString2));
		thisSimplePrintingService.show(runDoubleOperation("Lisa", "Ann", sayHelloToBoth));

		thisIntPrintingService.show(runSingleOperation("This string will not be shown", showOnlyTwo));
		thisIntPrintingService.show(runSingleOperation("This string will not be shown but the length", getLength));

		thisSimplePrintingService.show("Hello World");

	}

}

Lambda expressions can be used where Functional Interfaces are allowed. That’s why you should define a Functional Interface or use an existing Functional interface to “store” a lambda expression.

Lambda expression variable scope

Declarations in a lambda expression are interpreted just as they are in the enclosing environment.

package com.techstartingpoint.examples.java8.lambda;

import java.util.function.Consumer;

public class LambdaExpressionsScopeExample {

    public String s1 = "main value";

    class InnerClassExample {

        public String s1 = "inner value";

        // declaration of a parameter with the same name as the property (s1)
        void methodInInnerClass(String s1) {
            
            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // s1 = "another value";
            
            Consumer<String> myConsumer = (receivedString) -> 
            {
            	
            	// in lambda expressions body only can be referenced final variables or those that do not change
                System.out.println("s1: " + s1); // Statement A
                System.out.println("receivedString: " + receivedString);
                System.out.println("this.s1: " + this.s1);
                System.out.println("LambdaExpressionsScopeExample.this.s1: " +
                		LambdaExpressionsScopeExample.this.s1);
            };

            myConsumer.accept(s1);

        }
    }

    public static void main(String... args) {
    	LambdaExpressionsScopeExample st = new LambdaExpressionsScopeExample();
    	LambdaExpressionsScopeExample.InnerClassExample fl = st.new InnerClassExample();
        fl.methodInInnerClass("sample value");
    }

    /*
     * Result:
     * 
     * s1: sample value
     * receivedString: sample value
     * this.s1: inner value
     * LambdaExpressionsScopeExample.this.s1: main value
     */
    
	
}

 

Functional interfaces in Java 8

They are interfaces which can be used as the assignment target for a lambda expression or method reference.

The general interface body can contain

  • abstract methods
  • default methods
  • static methods

This is also true for functional interfaces except that about abstract methods they can have only one.  Any interface with a Single Abstract Method (SAM)  is a functional interface, and its implementation may be treated as lambda expressions.

To be more precise, there is one kind of abstract method which does not count to this “only one limitation”. They are methods that have the same signature as the public methods of java.lang.Object class. A clear example is the Comparator interface. It has two abstract methods: compare and equals. But equals has the same signature of the equals method of java.lang.Object. Then it doesn’t count for the “only one abstract method restriction”. For that purpose “Comparator” has only one abstract method: compare.

Having only one abstract method, the basic implementation (not overriding any method) would be to implement the unique abstract method that exists. Then, can be implemented using a lambda expression.

Additionally, it is also recommended that functional interfaces have a @FunctionalInterface declaration

Example:

The Functional interface

package com.techstartingpoint.examples.java8.functionalInterface;

@FunctionalInterface
public interface OwnFunctionalInterface {

	// it is functional because it has only one abstract method (doSomething)
    public String doSomething(String base, int aNumber);
    
    default String duplicateDoSomething(String aBase) {
    	return doSomething(aBase,8).concat(doSomething(aBase,10));
    }
    
    static void doAnotherThing() {
    	System.out.println("What do you expect?");
    }

}

The lambda expression that uses the functional interface

package com.techstartingpoint.examples.java8.functionalInterface;

public class ClassUsingTheFunctionalInterface {
	   public static void main(String args[]) {
	        // lambda expression
		   // lambda expression implementing my own Functional Interface
		   // a is expected to be a String and b is expected to be an int as is declared in the unique asbtract method 
		   // of my OwnFunctionalInterface
	    	OwnFunctionalInterface ownInterfaceImplementedObject = (a, b) -> a.concat(":").concat(Integer.toString(b));
	    	// then the created object implementing the interface can be used 
	        System.out.println("Result: "+ownInterfaceImplementedObject.duplicateDoSomething("other string --"));
	    }

}

The result of running the class is

Result: other string --:8other string --:10

Predefined functional interfaces

Java 8 provides a set of generic functional interfaces ready to be used. So, before to write down your own functional interface it’s a best practice to check if already exist one that fulfils the purpose you want in the default package.

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

To know how to use each of the pre-defined Functional Interfaces, it’s interesting to check what does the unique abstract method of each of them.

 

forEach() method in Iterable interface in Java

See – https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html

https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html

Iterable adds the forEach method that gets a “Consumer” argument

forEach(Consumer<? super T> action)

Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.
“The given action” is to execute the method “accept” of the Container for each element.

Consumer:

Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects. Basically a Consumer is “something” that makes “something” on a value. What does it do? It does what is defined in the method accept.
The functional method of Consumer is accept(Object).
forEach is generally used instead of “Iterator” combined with a “for”
See the example
package com.techstartingpoint.examples.java8;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;


public class ForEachExample {

	public static void main(String[] args) {
		
		//creating sample Collection
		// Remember List implements Iterable
		
		List iterableList = new ArrayList();
		for(int i=0; i<10; i++) iterableList.add("element:"+i);
		
		// Previous way : using Iterator
		// 1st example
		System.out.println("1st Example using iterator");
		Iterator it = iterableList.iterator();
		while(it.hasNext()){
			String i = it.next();
			System.out.println("Element Value::"+i);
		}
		System.out.println();
		
		// Iterable.forEach method with anonymous class
		// Using "String" because is the type used in our list
		// 2nd example
		System.out.println("2nd Example using forEach with anonymous Consumer class");
		iterableList.forEach(new Consumer() {

			public void accept(String element) {
				System.out.println("forEach anonymous element value::"+element);
			}

		});
		System.out.println();
		
		// Implement declaring our own class implementing Consumer
		// 3rd Example
		System.out.println("3rd Example using forEach with a Consumer implementation");
		ConsumerImplementation action = new ConsumerImplementation();
		iterableList.forEach(action);
		System.out.println();

		
	}

}

// Consumer implementation 
// supporting 3rd example
class ConsumerImplementation implements Consumer{
	
	
	public void accept(String element) {
		System.out.println("forEach element value::"+element);
	}

}

 

Java Stream API

Java generally provides collections to store and manipulate sets of data. The data in collections can be organized and retrieved in different ways. That’s possible because Java provides many classes and interfaces related to collections.

Streams provide another way to access data sources like Collections. Streams allow us to make bulk processing convenient and fast.  Streams do not provide elements to store data but to read it and process it, but not changing the original source.

Java Stream pipelines

Streams allow organizing work in a pipeline way. Methods related to streams generally return a stream. Then we can apply another stream method to a result of a stream method. Operations provided by streams can be easily chained generating a pipeline.

A stream pipeline consists of

  • a source (which might be an array, a collection, a generator function, an I/O channel, etc)
  • zero or more intermediate operations (which transform a stream into another stream)
  • a terminal operation (which produces a result or side-effect, such as count() or forEach(Consumer)).

Each intermediate operation in the stream pipeline is lazily executed and returns a stream as a result. The last method call on the pipeline marks the end of the stream and returns the final result.

Java Streams (Aggregate operations)  vs External Iterators

Despite operations made using streams can be done using iterators there are several differences between them.

Streams use internal Iterations

Stream operations do not contain a method like next.  The JDK determines how to iterate the collection.

Using iterations can only iterate over the elements of a collection sequentially. Streams operations can more easily take advantage of parallel computing.

Streams do not modify the source Collection

Streams support functionality as parameters via lambda expressions.

Possibly unbounded

The elements of a stream do not need to be finite.

Stream Creation

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

There are several ways to create a Stream. As stream operations results are streams then the operations allow creating new streams.

Some examples of creating streams from no stream sources are:

  • Static Factory methods of Stream class
    • Stream.of(…)
    • Stream.iterate(…)
    • Stream.generate(…)
    • Stream.empty()
  • stream methods of Collections and Arrays
    • Arrays.stream()
    • aListObject.stream()
    • aListObject.parallelStream()
    • from Iterator via Spliterator

See the Java Code Stream Examples

Stream operations

The full list of stream operations can be found at https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

Some of them are terminal operations (which do not produce a stream).

  • Terminal operations
    • forEach
    • forEachOrdered:  processes the items in the stream in the order they appear. It does not process them in the order established by comparable
    • count
    • toArray
    • reduce: summarizes elements of streams based on a Collector Method or custom defined behavior
    • collect
      • Collectors Methods
        • toList
        • joining
        • toSet
        • toCollection
        • summarizingDouble
        • partitioningBy
        • groupingBy
        • mapping
        • reducing
    • returning Optional
      • min
      • max
      • findFirst
      • findAny
    • Returning boolean
      • allMatch
      • anyMatch
      • noneMatch
  • Intermediate Operations (returning  Stream)
    • map
    • filter
    • flatMap
    • peek
    • sorted
    • distinct
  • Specialized Operations
    • sum
    • average
    • range
    • summaryStatistics
  • Parallel Streams
    • parallel
  • Infinite Streams
    • generate
    • iterate

There are some similar functionality but conceptually different. For example stream.map(). is a intermediate operation but collect(Collectors.mapping)  is a terminal opration. The same happens between stream.reduce() and collect(Collectors.reducing)

Source code example of several stream operations

 

package com.techstartingpoint.examples.java8.streams;
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamOperationExamples {
static Product[] arrayOfProducts = {
new Product(4, 12.50,"Marking Knife", "Cutters & Stencils",new String[] { 
"part 1", "part 2", "part3" , "part4" })    , 
new Product(1, 12.50,"Gauge", "John & John",new String[] { 
"part A", "part B", }), 
new Product(2, 10.20,"Hammer", "John & John",new String[] { 
"part A1", "part B1", }), 
new Product(3, 3.40,"Rule", "Measures Inc.",new String[] { 
"part A", "part B2", })
};
static Product[][] arrayOfArraysOfProducts = {arrayOfProducts,arrayOfProducts,arrayOfProducts};
private static class Product implements Comparable<Product> {
int id;
double price;
String name;
String brand;
String[] parts;
public Product(int id, double price, String name, String brand,String[] parts) {
super();
this.id = id;
this.price = price;
this.name = name;
this.brand = brand;
this.parts = parts;
}
@Override
public int compareTo(Product arg0) {
return this.name.compareTo(arg0.name);
}
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
System.out.println("Products ----- forEach"); // use of forEach
Stream<Product> productsStream = Stream.of(arrayOfProducts);
productsStream.forEach(x -> System.out.println(x.name));
System.out.println();
System.out.println("Products ----- forEachOrdered"); // use of forEach
// if stream is not created again it throws "stream has already been operated upon or closed"
productsStream = Stream.of(arrayOfProducts);
// forEachOrdered process the items in the stream in the order they appear
// it does not process them in a order established by comparable
productsStream.forEachOrdered(x -> System.out.println(x.name));
System.out.println();
System.out.println("products count");
System.out.println(Stream.of(arrayOfProducts).count());
System.out.println();
System.out.println("products map and reduce example - Generate product names joined by ;");
System.out.println(Stream.of(arrayOfProducts).map(p1 -> p1.name).reduce((name1,name2) -> name1+ "-" +name2));
System.out.println();
System.out.println("Reducer with 3 arguments(Identity, Accumulator, Combiner) -> Combiner only works with paralell stream");
System.out.println(Stream.of(arrayOfProducts).parallel().reduce("",
(summaryString,product) -> summaryString+ " - " +product.name,
(x,y) -> x + "//" + y));
System.out.println();
System.out.println("Stream map and collect example using Collectors.joining with ,");
System.out.println(Stream.of(arrayOfProducts).map(p1 -> p1.name).collect(Collectors.joining(",")));
Stream fromMap = Stream.of(arrayOfArraysOfProducts).map(x -> Arrays.stream(x));
Stream fromFlatMap = Stream.of(arrayOfArraysOfProducts).flatMap(x -> Arrays.stream(x));
System.out.println("from map -----------------------------------");
fromMap.forEach(x -> System.out.println(x.getClass()));
System.out.println("from flatMap -----------------------------------");
fromFlatMap.forEach(x -> System.out.println(x.getClass()));
// in this case each element is mapped to a stream
fromMap = Stream.of(arrayOfProducts).map(x -> Arrays.stream(x.parts));
// in this case each element is mapped to n elements present in the generated stream
fromFlatMap = Stream.of(arrayOfProducts).flatMap(x -> Arrays.stream(x.parts));
System.out.println("from map -> parts-----------------------------------");
fromMap.forEach(System.out::println);
System.out.println("from flatMap -> parts -----------------------------------");
fromFlatMap.forEach(System.out::println);
System.out.println();
System.out.println("Partitioning by brand = John & John");
System.out.println(Stream.of(arrayOfProducts).collect(Collectors.partitioningBy(x -> x.brand.contentEquals("John & John"))));
System.out.println();
System.out.println("Grouping by brand");
System.out.println(Stream.of(arrayOfProducts).collect(Collectors.groupingBy(x -> x.brand,Collectors.counting())));
System.out.println();
System.out.println("Filter byby brand = John & John");
System.out.println(Stream.of(arrayOfProducts).filter(x -> x.brand.contentEquals("John & John")));
} 
}

Date Time API in Java 8

Why improvement was needed  on Date Time API in Java?

Drawbacks of the previous version

  • Not consistent definition
    • Previous
      • Different behaviour between java.util and java.sql
    • Java 8
      • java.time is the base package
        • LocalDate
        • LocalTime
        • LocalDateTime
        • Instant: An instantaneous point on the time-line;  it stores date time in unix timestamp
        • Period
        • Duration
    • Lack of formatting and parsing unified classes (java.util.text)
  • Different meaning
    • java.uitl.Date -> Date and Time
    • java.sql.Date -> only Date
    • Same name for both
  • Formating and Parsing
    • Previous:
      • Confusing definitions of Time / Timestamp / Formating / Parsing
      • DateFormat / SimpleDateFormat
    • Java 8
      • All of them are in the java.time.format package
      • Main classes provide formatting and parsing methods
  • Concurrency
    • Previous
      • No thread Safe (all classes are mutable)
    • Java 8
      • Most classes are immutable and thread-safe
  • Lack of essential features
    • Previous
      • no i18n
      • no TimeZone support
      • java.util.Calendar and java.util.Timezone is also no thread-safe
    • Java 8
      • All of them are in java.time.zone package
  • Additional Features
    • java.time.chrono:  Generic API for calendar systems other than the default ISO.
    • java.time.temporal: Access to date and time using fields and units, and date time adjusters.

Previous to Java 8 Joda-time arose to solve several of those problems. Most of them are solved by Java 8 now.

 

Improvements in Collections API

Iterator.forEachReamaining(…)

 

It is equivalent to forEach but performs the given action for each remaining element until all elements have been processed or the action throws an exception.

It’s equivalent to


while (hasNext())
action.accept(next());

using the forEach call syntax.

Like in previous versions,  iterators allow removing elements while iterating.

Collection.removeIf(Predicate…)

Removes all of the elements of this collection that satisfy the given predicate.

Example

        collection.removeIf( x->(x<=123));

 

Spliterator

An object for traversing and partitioning elements of a source

Differences between Iterator and Spliterator

  • Used with:
    • Spliterator
      • Streams in Java 8.
        • Parallel
        • Sequential
      • Internal Iteration
    • Iterator
      • Collection.
        • Only Sequential
      • External Iteration
  • Traversing
    • Spliterator
      • Individually
      • Bulk
    • Iterator
      • Individually

Main methods

tryAdvance

boolean tryAdvance(Consumer<? super T> action)

If a remaining element exists, performs the given action on it, returning true; else returns false.

Example

    while (spliterator.tryAdvance(x -> x.setAddress("A fixed Value or a calculation")) {         operations++;     }

 

trySplit

it tries to split the splititerator in two parts if it can be performed. Near half of the elements are in the result after successful execution and the other half in the initial Splititerator

The causes that make not possible to split a stream could be:

  • empty stream
  • inability to split after traversal has commenced
  • data structure constraints
  • efficiency considerations

Source code example

package com.techstartingpoint.examples.java8.SpliteratorExample;
import java.util.Arrays;
import java.util.List;
import java.util.Spliterator;
public class TrySplitExample {
public static void main (String[] args) {  
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8);  
Spliterator<Integer> initialSpliterator = list.spliterator();  
Spliterator<Integer> generatedAfterFirstSplit = initialSpliterator.trySplit();  
System.out.println("Traversing: initial");  
initialSpliterator.forEachRemaining(System.out::println);  
System.out.println("Traversing: generated by trySplit");  
generatedAfterFirstSplit.forEachRemaining(System.out::println);  
}  
}

Using Spliterator elements can be partitioned and iterated in parallel. Spliterator itself does not provide parallel processing behaviour. Instead, the Spliterator provides an easy way to partition the stream in 2 parts that could be processed independently.

Map Methods

replaceAll

Syntax
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

Replaces each entry’s value with the result of invoking the given function on that entry until all entries have been processed or the function throws an exception.

The functionality is equivalent to


for (Map.Entry<K, V> entry : map.entrySet())
entry.setValue(function.apply(entry.getKey(), entry.getValue()));
Source Code Example
        myMap.replaceAll((key, oldValue)-> oldValue + 2); 

compute

It replaces the value only for the specified key

Syntax
default V compute(K key,
BiFunction<? super K,? super V,? extends V> remappingFunction)

Source Code Example

 myMap.compute(key, (k, v) -> "New Value");

merge

If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function, or removes if the result is null. This method may be of use when combining multiple mapped values for a key. For example, to either create or append a String msg to a value mapping:

Syntax
default V merge(K key,
V value,
BiFunction<? super V,? super V,? extends V> remappingFunction)

Source Code Example

myMap.merge(key, value,                (s1, s2) -> s1.equals(s2)                             ? s1.toUpperCase()                             : s1 + ", " + s2)

Improvements in Concurrency APIs

 

 

 

Leave a Reply