Java 8 Functional Interfaces – When & How To Use Them?

Functional interfaces, lambda expressions and Stream API – these three features of Java 8 has turned Java programming into new style of programming called functional-style programming. Java is still an object-oriented programming language, but from Java 8, with the introduction of new features, most of the programming is done keeping functions in mind rather than objects. In this article, we will see Java 8 functional interfaces, @FunctionalInterface annotation, java.util.function package and how to use new Java 8 functional interfaces to compose lambda expressions with some simple examples.

Java 8 Functional Interfaces

1) Definition

Functional interfaces are the interfaces which has exactly one abstract method. They may have any number of default methods but must have only one abstract method. Functional interfaces provide only one functionality to implement.

There were functional interfaces exist before Java 8. It is not like that they are the whole new concept introduced only in Java 8. RunnableActionListenerCallable and Comaprator are some old functional interfaces which exist even before Java 8.

The new set of functional interfaces are introduced in Java 8 to make programmer’s job easy while writing lambda expressions. Your lambda expression must implement any one of these functional interfaces. These new functional interfaces are organised under java.util.function package.

2) @FunctionalInterface Annotation

@FunctionalInterface annotation is introduced in Java 8 to represent functional interfaces. Although, it is not compulsory to write functional interface using this annotation. But, if you are using @FunctionalInterface annotation then your interface should contain only one abstract method. If you try to write more than one abstract method, compiler will show the error.

3) java.util.function package

All Java 8 functional interfaces are organised in java.util.function package. Each functional interface in this package represents an operation that can be performed by the lambda expression.

Below table shows the list of all Java 8 functional interfaces along with their abstract method, which operation they represent and when to use them?

Java 8 functional interfaces

4) How To Use Java 8 Functional Interfaces In Real Time?

Let’s define Student class like below. We will be using this class in the subsequent examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Student
{
    int id;
    
    String name;
    
    double percentage;
    
    String specialization;
    
    public Student(int id, String name, double percentage, String specialization)
    {
        this.id = id;
        
        this.name = name;
        
        this.percentage = percentage;
        
        this.specialization = specialization;
    }
    
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public double getPercentage() {
        return percentage;
    }
    public String getSpecialization() {
        return specialization;
    }
    @Override
    public String toString()
    {
        return id+"-"+name+"-"+percentage+"-"+specialization;
    }
}

Let listOfStudents be the list of 10 students.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
List<Student> listOfStudents = new ArrayList<Student>();
        
listOfStudents.add(new Student(111, "John", 81.0, "Mathematics"));
        
listOfStudents.add(new Student(222, "Harsha", 79.5, "History"));
        
listOfStudents.add(new Student(333, "Ruth", 87.2, "Computers"));
        
listOfStudents.add(new Student(444, "Aroma", 63.2, "Mathematics"));
        
listOfStudents.add(new Student(555, "Zade", 83.5, "Computers"));
        
listOfStudents.add(new Student(666, "Xing", 58.5, "Geography"));
        
listOfStudents.add(new Student(777, "Richards", 72.6, "Banking"));
        
listOfStudents.add(new Student(888, "Sunil", 86.7, "History"));
        
listOfStudents.add(new Student(999, "Jordan", 58.6, "Finance"));
        
listOfStudents.add(new Student(101010, "Chris", 89.8, "Computers"));

Let’s see how to use 4 important functional interfaces – PredicateConsumerFunction and Supplier using above listOfStudents.

a) Predicate – Tests an object

Predicate represents an operation which takes an argument T and returns a boolean. Use this functional interface, if you want to define a lambda expression which performs some test on an argument and returns true or false depending upon outcome of the test.

For example,

Imagine an operation where you want only a list of “Mathematics” students from the above listOfStudents. Let’s see how to do it using Predicate.

Lambda expression implementing Predicate : Checking specialization of a Student

1
2
3
4
5
6
7
8
9
10
11
Predicate<Student> mathematicsPredicate = (Student student) -> student.getSpecialization().equals("Mathematics");
        
List<Student> mathematicsStudents = new ArrayList<Student>();
        
for (Student student : listOfStudents)
{
    if (mathematicsPredicate.test(student))
    {
        mathematicsStudents.add(student);
    }
}

b) Consumer – Consumes an object

Consumer represents an operation which takes an argument and returns nothing. Use this functional interface If you want to compose a lambda expression which performs some operations on an object.

For example, displaying all students with their percentage.

Lambda expression implementing Consumer : Displaying all students with their percentage

1
2
3
4
5
6
7
8
Consumer<Student> percentageConsumer = (Student student) -> {
        System.out.println(student.getName()+" : "+student.getPercentage());
    };
        
for (Student student : listOfStudents)
{
    percentageConsumer.accept(student);
}

c) Function – Applies to an object

Function represents an operation which takes an argument of type T and returns a result of type R. Use this functional interface if you want to extract some data from an existing data.

For example, extracting only the names from listOfStudents.

Lambda expression implementing Function : Extracting only the names of all students

1
2
3
4
5
6
7
8
Function<Student, String> nameFunction = (Student Student) -> Student.getName();
        
List<String> studentNames = new ArrayList<String>();
        
for (Student student : listOfStudents)
{
    studentNames.add(nameFunction.apply(student));
}

d) Supplier – Supplies the objects

Supplier represents an operation which takes no argument and returns the results of type R. Use this functional interface when you want to create new objects.

Lambda expression implementing Supplier : Creating a new Student

1
2
3
Supplier<Student> studentSupplier = () -> new Student(111111, "New Student", 92.9, "Java 8");
        
listOfStudents.add(studentSupplier.get());

5) Functional Interfaces Supporting Primitive Type

Java 8 has also introduced functional interfaces which support primitive types. For example IntPredicateDoublePredicateLongConsumer etc… (See above table).

If an input or output is a primitive type then using these functional interfaces will enhance the performance of your code. For example, if input to a Predicate is primitive type intthen using intPredicate instead of Predicate will remove unnecessary boxing of input.

One Method to Rule Them All: Map.merge()

 I don’t often explain a single method in JDK, but when I do, it’s about Map.merge(). This is probably the most versatile operation in the key-value universe. But it’s also rather obscure and rarely used.

merge() can be explained as follows: it either puts new value under the given key (if absent) or updates existing key with a given value (UPSERT). Let’s start with the most basic example: counting unique word occurrences. Pre-Java 8 (read: pre-2014!) code was quite messy and the essence was lost in implementation details:

var map = new HashMap<String, Integer>();
words.forEach(word -> {
    var prev = map.get(word);
    if (prev == null) {
        map.put(word, 1);
    } else {
        map.put(word, prev + 1);
    }
});

However, it works, and for the given input, it produces the desired output:

var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz");
//...
{Bar=1, Fizz=2, Foo=3, Buzz=2}

OK, but let’s try to refactor it to avoid conditional logic:

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.put(word, map.get(word) + 1);
});

That’s nice!

putIfAbsent()  is a necessary evil; otherwise, the code breaks on the first occurrence of a previously unknown word. Also, I find map.get(word) insidemap.put() to be a bit awkward. Let’s get rid of it as well!

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.computeIfPresent(word, (w, prev) -> prev + 1);
});

computeIfPresent() invokes the given transformation only if the key in question (word) exists. Otherwise, it does nothing. We made sure the key exists by initializing it to zero, so incrementation always works. Can we do better? Well, we can cut the extra initialization, but I wouldn’t recommend it:

words.forEach(word ->
        map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1)
);

compute ()is likecomputeIfPresent(), but it is invoked irrespective to the existence of the given key. If the value for the key does not exist, the prevargument is null. Moving a simpleif to a ternary expression hidden in lambda is far from optimal. This is where themerge()operator shines. Before I show you the final version, let’s see a slightly simplified default implementation ofMap.merge():

default V merge(K key, V value, BiFunction<V, V, V> remappingFunction) {
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if (newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

The code snippet is worth a thousand words. merge() works in two scenarios. If the given key is not present, it simply becomes put(key, value). However, if the said key already holds some value, our remappingFunction may merge (duh!) the old and the one. This function is free to:

  • overwrite old value by simply returning the new one: (old, new) -> new
  • keep the old value by simply returning the old one: (old, new) -> old
  • somehow merge the two, e.g.: (old, new) -> old + new
  • or even remove old value: (old, new) -> null

As you can see, merge() is quite versatile. So, how does our academic problem look like with merge()? It’s quite pleasing:

words.forEach(word ->
        map.merge(word, 1, (prev, one) -> prev + one)
);

You can read it as follows: put 1 under the word key if absent; otherwise, add 1 to the existing value. I named one of the parameters “ one” because in our example it’s always…  1.

Sadly, remappingFunctiontakes two parameters, where the second one is the value we are about to upsert (insert or update). Technically, we know this value already, so (word, 1, prev -> prev + 1)  would be much easier to digest. But there’s no such API.

All right, but is merge() really useful? Imagine you have an account operation (constructor, getters, and other useful properties omitted):

class Operation {
    private final String accNo;
    private final BigDecimal amount;
}

And a bunch of operations for different accounts:

var operations = List.of(
    new Operation("123", new BigDecimal("10")),
    new Operation("456", new BigDecimal("1200")),
    new Operation("123", new BigDecimal("-4")),
    new Operation("123", new BigDecimal("8")),
    new Operation("456", new BigDecimal("800")),
    new Operation("456", new BigDecimal("-1500")),
    new Operation("123", new BigDecimal("2")),
    new Operation("123", new BigDecimal("-6.5")),
    new Operation("456", new BigDecimal("-600"))
);

We would like to compute balance (total over operations’ amounts) for each account. Without merge(), this is quite cumbersome:

var balances = new HashMap<String, BigDecimal>();
operations.forEach(op -> {
    var key = op.getAccNo();
    balances.putIfAbsent(key, BigDecimal.ZERO);
    balances.computeIfPresent(key, (accNo, prev) -> prev.add(op.getAmount()));
});

But with a little help of merge():

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), 
                (soFar, amount) -> soFar.add(amount))
);

Do you see a method reference opportunity here?

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);

I find this astoundingly readable. For each operation, add the given amount to the given accNo. The results are as expected:

{123=9.5, 456=-100}

ConcurrentHashMap

Map.merge() shines even brighter when you realize it’s properly implemented inConcurrentHashMap. This means we can atomically perform an insert-or-update operation — single line and thread-safe.

ConcurrentHashMap is obviously thread-safe, but not across many operations, e.g. get() and thenput(). However, merge() makes sure no updates are lost.

Life Beyond Java 8

New versions of Java are coming out every six months. What has changed, should we upgrade, and if so, how?

Abstract

Wasn’t Java 8 a fantastic update to the language? Lambdas and streams were a huge change and have helped to improve Java developers’ productivity and introduce some functional ideas to the language. Then came Java 9… and although the module system is really interesting for certain types of applications, the lack of exciting language features and uncertainty around how painful it might be to migrate to Java 9 left many applications taking a wait-and-see approach, happy with Java 8.

But now, Java has a new version every six months, and suddenly, Java 12 is here. But we’re all still on Java 8, wondering whether we should move to a later version, which one to choose, and how painful it might be to upgrade.

In this session, we’ll look at:

  • Why upgrade from Java 8, including language features from Java 9, 10, 11, and 12
  • What sorts of issues might we run into if we do choose to upgrade
  • How the support and license changes that came in with Java 11 might impact us.

Resources

Updates, Licenses, and Support

Where to Get Your JDK From

Migrating From Java 8

Features

Java 11

Java 10

Java 9

Java 12

Java Future

Performance

Garbage Collectors

String Performance

Other

Overview of Method References

Method references are a feature of Java 8. They are effectively a subset of lambda expressions, because if a lambda expression can be used, then it might be possible to use a method reference, but not always. They can only be used to call a singular method, which obviously reduces the possible places they can be used, unless your code is written to cater for them.

It would be a good idea if you knew the notation for a method reference. In fact, you have probably already seen it assuming you read the title. If not then just look below.

Person::getName 

The example above is the equivalent of writing person.getName(), where person is an instance of Person. Let me tell you a bit more about when you can use method references and show some examples as it makes a lot more sense with them.

Types of Method References

Type Syntax Method Reference Lambda expression
Reference to a static method Class::staticMethod String::valueOf  s -> String.valueOf(s)
Reference to an instance method
of a particular object
instance::instanceMethod s::toString  () -> “string”.toString()
Reference to an instance method
of an arbitrary object of a particular type
Class:instanceMethod String::toString  s -> s.toString()
Reference to a constructor Class::new String::new  () -> new String()

Reference to a Static Method

public class StaticMethodReference{
    public static void main(String args[]) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // Method reference
        list.forEach(StaticMethodReference::print);
        // Lambda expression
        list.forEach(number -> StaticMethodReference.print(number));
        // normal
        for(int number : list) {
            StaticMethodReference.print(number);
        }
    }
    public static void print(final int number) {
        System.out.println("I am printing: " + number);
    }
}

Here, it calls the static method StaticMethodReference.print. This example is pretty simple. There is a static method, and for each element in the list, it calls this method using the element as the input.

Reference to an Instance Method of a Particular Object

public class ParticularInstanceMethodReference {
    public static void main(String args[]) {
        final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        final MyComparator myComparator = new MyComparator();
        // Method reference
        Collections.sort(list, myComparator::compare);
        // Lambda expression
        Collections.sort(list, (a,b) -> myComparator.compare(a,b));
    }

    private static class MyComparator {
        public int compare(final Integer a, final Integer b) {
            return a.compareTo(b);
        }
    }
}

Here, it calls the instance method myComparator.compare, where myComparator is a particular instance of MyComparator.

Reference to an Instance Method of an Arbitrary Object of a Particular Type

public class ArbitraryInstanceMethodReference {
    public static void main(String args[]) {
        final List<Person> people = Arrays.asList(new Person("dan"), new Person("laura"));
        // Method reference
        people.forEach(Person::printName);
        // Lambda expression
        people.forEach(person -> person.printName());
        // normal
        for (final Person person : people) {
            person.printName();
        }
    }
 private static class Person {
        private String name;
        public Person(final String name) {
            this.name = name;
        }
        public void printName() {
            System.out.println(name);
        }
    }
}

This calls the method Person.getName for each Person object in the list. Person is the particular type, and the arbitrary object is the instance of Person that is used during each loop. This looks very similar to a reference to a static method, but the difference is how the object is passed to the method reference. Remember, a static reference passes the current object into the method, whereas an arbitrary method reference invokes a method onto the current object.

Reference to a Constructor

public class ConstructorMethodReference {
    public static void main(String args[]) {
        final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // Method Reference
        copyElements(null, ArrayList<Integer>::new);
        // Lambda expression
        copyElements(list, () -> new ArrayList<Integer>());
    }
    private static void copyElements(final List<Integer> list, final Supplier<Collection<Integer>> targetCollection) {
        // Method reference to a particular instance
        list.forEach(targetCollection.get()::add);
    }
}

This is the example I had the most trouble trying to make, as no matter how hard I thought, I couldn’t think of a way this could be used in something complicated. I am sure my opinion would change if I used Java 8 while at work, but for now, I do not see why this type of method reference is particularly useful. The example uses the Supplier functional interface to pass Integer::new into the copyElements method.

Conclusion

In conclusion, method references can be used to make your code even more concise, but they have some restrictions on when they can be used for and what they can do. If you simplify your code by using a lambda expression, then you might be able to make it even shorter by using a method reference. Eventually, your code will be so short your bosses will wonder what you have even been doing as you have only written a few lines of code!

Java 8 Features

Java 8 was released in 18th March 2014, so it’s high time to look into Java 8 Features. In this tutorial, we will look into Java 8 features with examples.

Some of the important Java 8 features are:

  1. forEach() method in Iterable interface
  2. default and static methods in Interfaces
  3. Functional Interfaces and Lambda Expressions
  4. Java Stream API for Bulk Data Operations on Collections
  5. Java Time API
  6. Collection API improvements
  7. Concurrency API improvements
  8. Java IO improvements
  9. Miscellaneous Core API improvements

Let’s have a brief look on these Java 8 features. I will provide some code snippets for better understanding, so if you want to run programs in Java 8, you will have to setup Java 8 environment by following steps.

  • Download JDK8 and install it. Installation is simple like other java versions. JDK installation is required to write, compile and run the program in Java.
  • Download latest Eclipse IDE, it provides support for java 8 now. Make sure your projects build path is using Java 8 library.
    1. forEach() method in Iterable interface

      Whenever we need to traverse through a Collection, we need to create an Iterator whose whole purpose is to iterate over and then we have business logic in a loop for each of the elements in the Collection. We might get ConcurrentModificationException if iterator is not used properly.

      Java 8 has introduced forEach method in java.lang.Iterable interface so that while writing code we focus on business logic only. forEach method takes java.util.function.Consumer object as argument, so it helps in having our business logic at a separate location that we can reuse. Let’s see forEach usage with simple example.

      package com.journaldev.java8.foreach;
      
      import java.util.ArrayList;
      import java.util.Iterator;
      import java.util.List;
      import java.util.function.Consumer;
      import java.lang.Integer;
      
      public class Java8ForEachExample {
      
      	public static void main(String[] args) {
      		
      		//creating sample Collection
      		List<Integer> myList = new ArrayList<Integer>();
      		for(int i=0; i<10; i++) myList.add(i);
      		
      		//traversing using Iterator
      		Iterator<Integer> it = myList.iterator();
      		while(it.hasNext()){
      			Integer i = it.next();
      			System.out.println("Iterator Value::"+i);
      		}
      		
      		//traversing through forEach method of Iterable with anonymous class
      		myList.forEach(new Consumer<Integer>() {
      
      			public void accept(Integer t) {
      				System.out.println("forEach anonymous class Value::"+t);
      			}
      
      		});
      		
      		//traversing with Consumer interface implementation
      		MyConsumer action = new MyConsumer();
      		myList.forEach(action);
      		
      	}
      
      }
      
      //Consumer implementation that can be reused
      class MyConsumer implements Consumer<Integer>{
      
      	public void accept(Integer t) {
      		System.out.println("Consumer impl Value::"+t);
      	}
      
      
      }

      The number of lines might increase but forEach method helps in having the logic for iteration and business logic at separate place resulting in higher separation of concern and cleaner code.

    1. default and static methods in Interfaces

      If you read forEach method details carefully, you will notice that it’s defined in Iterable interface but we know that interfaces can’t have method body. From Java 8, interfaces are enhanced to have method with implementation. We can use default and static keyword to create interfaces with method implementation. forEach method implementation in Iterable interface is:

      	default void forEach(Consumer<? super T> action) {
              Objects.requireNonNull(action);
              for (T t : this) {
                  action.accept(t);
              }
          }

      We know that Java doesn’t provide multiple inheritance in Classes because it leads to Diamond Problem. So how it will be handled with interfaces now, since interfaces are now similar to abstract classes. The solution is that compiler will throw exception in this scenario and we will have to provide implementation logic in the class implementing the interfaces.

      package com.journaldev.java8.defaultmethod;
      
      @FunctionalInterface
      public interface Interface1 {
      
      	void method1(String str);
      	
      	default void log(String str){
      		System.out.println("I1 logging::"+str);
      	}
      	
      	static void print(String str){
      		System.out.println("Printing "+str);
      	}
      	
      	//trying to override Object method gives compile time error as
      	//"A default method cannot override a method from java.lang.Object"
      	
      //	default String toString(){
      //		return "i1";
      //	}
      	
      }
      package com.journaldev.java8.defaultmethod;
      
      @FunctionalInterface
      public interface Interface2 {
      
      	void method2();
      	
      	default void log(String str){
      		System.out.println("I2 logging::"+str);
      	}
      
      }

      Notice that both the interfaces have a common method log() with implementation logic.

      package com.journaldev.java8.defaultmethod;
      
      public class MyClass implements Interface1, Interface2 {
      
      	@Override
      	public void method2() {
      	}
      
      	@Override
      	public void method1(String str) {
      	}
      
      	//MyClass won't compile without having it's own log() implementation
      	@Override
      	public void log(String str){
      		System.out.println("MyClass logging::"+str);
      		Interface1.print("abc");
      	}
      	
      }

      As you can see that Interface1 has static method implementation that is used in MyClass.log()method implementation. Java 8 uses default and static methods heavily in Collection API and default methods are added so that our code remains backward compatible.

      If any class in the hierarchy has a method with same signature, then default methods become irrelevant. Since any class implementing an interface already has Object as superclass, if we have equals(), hashCode() default methods in interface, it will become irrelevant. Thats why for better clarity, interfaces are not allowed to have Object class default methods.

      For complete details of interface changes in Java 8, please read Java 8 interface changes.

    1. Functional Interfaces and Lambda Expressions

      If you notice above interfaces code, you will notice @FunctionalInterface annotation. Functional interfaces are new concept introduced in Java 8. An interface with exactly one abstract method becomes Functional Interface. We don’t need to use @FunctionalInterface annotation to mark an interface as Functional Interface. @FunctionalInterface annotation is a facility to avoid accidental addition of abstract methods in the functional interfaces. You can think of it like @Override annotationand it’s best practice to use it. java.lang.Runnable with single abstract method run() is a great example of functional interface.

      One of the major benefits of functional interface is the possibility to use lambda expressions to instantiate them. We can instantiate an interface with anonymous class but the code looks bulky.

      Runnable r = new Runnable(){
      			@Override
      			public void run() {
      				System.out.println("My Runnable");
      			}};

      Since functional interfaces have only one method, lambda expressions can easily provide the method implementation. We just need to provide method arguments and business logic. For example, we can write above implementation using lambda expression as:

      Runnable r1 = () -> {
      			System.out.println("My Runnable");
      		};

      If you have single statement in method implementation, we don’t need curly braces also. For example above Interface1 anonymous class can be instantiated using lambda as follows:

      Interface1 i1 = (s) -> System.out.println(s);
      		
      i1.method1("abc");

      So lambda expressions are means to create anonymous classes of functional interfaces easily. There are no runtime benefits of using lambda expressions, so I will use it cautiously because I don’t mind writing few extra lines of code.

      A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in future.

      You can read complete tutorial at Java 8 Lambda Expressions Tutorial.

    1. Java Stream API for Bulk Data Operations on Collections

      A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best feature for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

      Collection interface has been extended with stream() and parallelStream() default methods to get the Stream for sequential and parallel execution. Let’s see their usage with simple example.

      package com.journaldev.java8.stream;
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.stream.Stream;
      
      public class StreamExample {
      
      	public static void main(String[] args) {
      		
      		List<Integer> myList = new ArrayList<>();
      		for(int i=0; i<100; i++) myList.add(i);
      		
      		//sequential stream
      		Stream<Integer> sequentialStream = myList.stream();
      		
      		//parallel stream
      		Stream<Integer> parallelStream = myList.parallelStream();
      		
      		//using lambda with Stream API, filter example
      		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
      		//using lambda in forEach
      		highNums.forEach(p -> System.out.println("High Nums parallel="+p));
      		
      		Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
      		highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));
      
      	}
      
      }

      If you will run above example code, you will get output like this:

      High Nums parallel=91
      High Nums parallel=96
      High Nums parallel=93
      High Nums parallel=98
      High Nums parallel=94
      High Nums parallel=95
      High Nums parallel=97
      High Nums parallel=92
      High Nums parallel=99
      High Nums sequential=91
      High Nums sequential=92
      High Nums sequential=93
      High Nums sequential=94
      High Nums sequential=95
      High Nums sequential=96
      High Nums sequential=97
      High Nums sequential=98
      High Nums sequential=99

      Notice that parallel processing values are not in order, so parallel processing will be very helpful while working with huge collections.
      Covering everything about Stream API is not possible in this post, you can read everything about Stream API at Java 8 Stream API Example Tutorial.

    1. Java Time API

      It has always been hard to work with Date, Time and Time Zones in java. There was no standard approach or API in java for date and time in Java. One of the nice addition in Java 8 is the java.timepackage that will streamline the process of working with time in java.

      Just by looking at Java Time API packages, I can sense that it will be very easy to use. It has some sub-packages java.time.format that provides classes to print and parse dates and times and java.time.zone provides support for time-zones and their rules.

      The new Time API prefers enums over integer constants for months and days of the week. One of the useful class is DateTimeFormatter for converting datetime objects to strings.

      For complete tutorial, head over to Java Date Time API Example Tutorial.

    1. Collection API improvements

      We have already seen forEach() method and Stream API for collections. Some new methods added in Collection API are:

      • Iterator default method forEachRemaining(Consumer action) to perform the given action for each remaining element until all elements have been processed or the action throws an exception.
      • Collection default method removeIf(Predicate filter) to remove all of the elements of this collection that satisfy the given predicate.
      • Collection spliterator() method returning Spliterator instance that can be used to traverse elements sequentially or parallel.
      • Map replaceAll()compute()merge() methods.
      • Performance Improvement for HashMap class with Key Collisions
    1. Concurrency API improvements

      Some important concurrent API enhancements are:

      • ConcurrentHashMap compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() and search() methods.
      • CompletableFuture that may be explicitly completed (setting its value and status).
      • Executors newWorkStealingPool() method to create a work-stealing thread pool using all available processors as its target parallelism level.
    1. Java IO improvements

      Some IO improvements known to me are:

      • Files.list(Path dir) that returns a lazily populated Stream, the elements of which are the entries in the directory.
      • Files.lines(Path path) that reads all lines from a file as a Stream.
      • Files.find() that returns a Stream that is lazily populated with Path by searching for files in a file tree rooted at a given starting file.
      • BufferedReader.lines() that return a Stream, the elements of which are lines read from this BufferedReader.
  1. Miscellaneous Core API improvements

    Some misc API improvements that might come handy are:

    1. ThreadLocal static method withInitial(Supplier supplier) to create instance easily.
    2. Comparator interface has been extended with a lot of default and static methods for natural ordering, reverse order etc.
    3. min(), max() and sum() methods in Integer, Long and Double wrapper classes.
    4. logicalAnd(), logicalOr() and logicalXor() methods in Boolean class.
    5. 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.
    6. Several utility methods in Math class.
    7. jjs command is added to invoke Nashorn Engine.
    8. jdeps command is added to analyze class files
    9. JDBC-ODBC Bridge has been removed.
    10. PermGen memory space has been removed

That’s all for Java 8 features with example programs. If I have missed some important features of Java 8, please let me know through comments.