Java 8 features

Important features of java 8 are:

  1. forEach() method in Iterable interface
  2. default and static methods in Interfaces
  3. Interface Changes
  4. Functional Interfaces and Lambda Expressions
  5. Java Stream API for Bulk Data Operations on Collections
  6. Java Time API
  7. Collection API improvements
  8. Concurrency API improvements
  9. Java IO improvements
  10. Miscellaneous Core API improvements
  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, which mean if we modify collection then we may get this exception.

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.

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

public class Example {

public static void main(String[] args) {

     List myList = new ArrayList();
     for(int i=0; i<10; i++)   myList.add(i);

    //traversing using Iterator (External for loop or iterator)
     Iterator 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() {

       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{

     public void accept(Integer t) {
        System.out.println(“Consumer impl Value::”+t);
     }

}

So as we have seen here we are achieving segregation and code is loosely coupled, 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.

2. 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);
     }
}

Now Question comes in mind how multiple inheritance will be taken care, 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. we will see this scenario with example

@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);
   }

//if we try 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”;
// }

}

@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.Below is scenario of diamond problem where we are trying to override log method which is present in both interfaces

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 so //it’s compulsory to override log method
  @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.

Why we can not make Object methods as default?

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

3. Interface changes

Java 8 interface changes include static methods and default methods in interfaces. Prior to Java 8, we could have only method declarations in the interfaces. But from Java 8, we can have default methods and static methods in the interfaces.

Important points about java interface default methods:

  1. Java interface default methods will help us in extending interfaces without having the fear of breaking implementation classes.
  2. Java interface default methods has bridge down the differences between interfaces and abstract classes.
  3. Java 8 interface default methods will help us in avoiding utility classes, such as all the Collections class method can be provided in the interfaces itself.
  4. Java interface default methods will help us in removing base implementation classes, we can provide default implementation and the implementation classes can chose which one to override.
  5. One of the major reason for introducing default methods in interfaces is to enhance the Collections API in Java 8 to support lambda expressions.
  6. If any class in the hierarchy has a method with same signature, then default methods become irrelevant. A default method cannot override a method from java.lang.Object. The reasoning is very simple, it’s because Object is the base class for all the java classes. So even if we have Object class methods defined as default methods in interfaces, it will be useless because Object class method will always be used. That’s why to avoid confusion, we can’t have default methods that are overriding Object class methods.
  7. Java interface default methods are also referred to as Defender Methods or Virtual extension methods.

Java Interface Static Method

Java interface static method is similar to default method except that we can’t override them in the implementation classes. This feature helps us in avoiding undesired results incase of poor implementation in implementation classes. Let’s look into this with a simple example.

public interface MyData {

   default void print(String str) {
       if (!isNull(str))
          System.out.println(“MyData Print::” + str);
   }

   static boolean isNull(String str) {
       System.out.println(“Interface Null Check”);

       return str == null ? true : “”.equals(str) ? true : false;
   }
}

Now let’s see an implementation class that is having isNull() method with poor implementation.

public class MyDataImpl implements MyData {

    public boolean isNull(String str) {
         System.out.println(“Impl Null Check”);

        return str == null ? true : false;
   }

  public static void main(String args[]){
      MyDataImpl obj = new MyDataImpl();
     obj.print(“”);
    obj.isNull(“abc”);
  }
}

Note that isNull(String str) is a simple class method, it’s not overriding the interface method. For example, if we will add @Override annotation to the isNull() method, it will result in compiler error.

Now when we will run the application, we get following output.

Interface Null Check
Impl Null Check

If we make the interface method from static to default, we will get following output.Impl Null Check
MyData Print::
Impl Null Check

Java interface static method is visible to interface methods only, if we remove the isNull() method from the MyDataImpl class, we won’t be able to use it for the MyDataImpl object. However like other static methods, we can use interface static methods using class name. For example, a valid statement will be:

boolean result = MyData.isNull(“abc”);

Important points about java interface static method:

  1. Java interface static method is part of interface, we can’t use it for implementation class objects.
  2. Java interface static methods are good for providing utility methods, for example null check, collection sorting etc.
  3. Java interface static method helps us in providing security by not allowing implementation classes to override them.
  4. We can’t define interface static method for Object class methods, we will get compiler error as “This static method cannot hide the instance method from Object”. This is because it’s not allowed in java, since
  5. Object is the base class for all the classes and we can’t have one class level static method and another instance method with same signature.
  6. We can use java interface static methods to remove utility classes such as Collections and move all of it’s static methods to the corresponding interface, that would be easy to find and use.

Java Functional Interfaces

An interface with exactly one abstract method is known as Functional Interface.

A new annotation @FunctionalInterface has been introduced to mark an interface as Functional Interface. @FunctionalInterface annotation is a facility to avoid accidental addition of abstract methods in the functional interfaces. It’s optional but good practice to use it.

Functional interfaces are long awaited and much sought out feature of Java 8 because it enables us to use lambda expressions to instantiate them. A new package java.util.function with bunch of functional interfaces are added to provide target types for lambda expressions and method references. We will look into functional interfaces and lambda expressions in the future posts.

4. 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 annotation and 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 a 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 a 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 the future.

5. 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 features 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

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

    public static void main(String[] args) {

      List myList = new ArrayList<>();
      for(int i=0; i<100; i++) myList.add(i);

      //sequential stream
      Stream sequentialStream = myList.stream();

    //parallel stream
     Stream parallelStream = myList.parallelStream();

    //using lambda with Stream API, filter example
     Stream highNums = parallelStream.filter(p -> p > 90);
   //using lambda in forEach
   highNums.forEach(p -> System.out.println(“High Nums parallel=”+p));

   Stream highNumsSeq = sequentialStream.filter(p -> p > 90);
   highNumsSeq.forEach(p -> System.out.println(“High Nums       sequential=”+p));

 }

}

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

6.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.time package 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.

Issues with the Existing Date/Time APIs

  1. Thread Safety – The Date and Calendar classes are not thread safe, leaving developers to deal with the headache of hard to debug concurrency issues and to write additional code to handle thread safety. On the contrary the new Date and Time APIs introduced in Java 8 are immutable and thread safe, thus taking that concurrency headache away from developers.
  2. APIs Design and Ease of Understanding – The Date and Calendar APIs are poorly designed with inadequate methods to perform day-to-day operations. The new Date/Time APIs is ISO centric and follows consistent domain models for date, time, duration and periods. There are a wide variety of utility methods that support the commonest operations.
  3. ZonedDate and Time – Developers had to write additional logic to handle timezone logic with the old APIs, whereas with the new APIs, handling of timezone can be done with Local and ZonedDate/Time APIs.

Working With LocalDate

The LocalDate represents a date in ISO format (yyyy-MM-dd) without time.

It can be used to store dates like birthdays and paydays.

An instance of current date can be created from the system clock as below:

LocalDate localDate = LocalDate.now();

The LocalDate representing a specific day, month and year can be obtained using the “of” method or by using the “parse” method. For example the below code snippets represents the LocalDate for 20 February 2015:

LocalDate.of(2015, 02, 20);LocalDate.parse("2015-02-20");

The LocalDate provides various utility methods to obtain a variety of information. Let’s have a quick peek at some of these APIs methods.

The following code snippet gets the current local date and adds one day:

LocalDate tomorrow = LocalDate.now().plusDays(1);

This example obtains the current date and subtracts one month. Note how it accepts an enum as the time unit:

LocalDate previousMonthSameDay = LocalDate.now().minus(1, ChronoUnit.MONTHS);

In the following two code examples we parse the date “2016-06-12” and get the day of the week and the day of the month respectively. Note the return values, the first is an object representing the DayOfWeek while the second in an int representing the ordinal value of the month:

DayOfWeek sunday = LocalDate.parse("2016-06-12").getDayOfWeek();int twelve = LocalDate.parse("2016-06-12").getDayOfMonth();

We can test if a date occurs in a leap year. In this example we test if the current date occurs is a leap year:

boolean leapYear = LocalDate.now().isLeapYear();

The relationship of a date to another can be determined to occur before or after another date:

boolean notBefore = LocalDate.parse("2016-06-12")  .isBefore(LocalDate.parse("2016-06-11"));boolean isAfter = LocalDate.parse("2016-06-12")  .isAfter(LocalDate.parse("2016-06-11"));

Date boundaries can be obtained from a given date. In the following two examples we get the LocalDateTime that represents the beginning of the day (2016-06-12T00:00) of the given date and the LocalDate that represents the beginning of the month (2016-06-01) respectively:

LocalDateTime beginningOfDay = LocalDate.parse("2016-06-12").atStartOfDay();LocalDate firstDayOfMonth = LocalDate.parse("2016-06-12")  .with(TemporalAdjusters.firstDayOfMonth());

Working With LocalTime

The LocalTime represents time without a date.

Similar to LocalDate an instance of LocalTime can be created from system clock or by using “parse” and “of” method. Quick look at some of the commonly used APIs below.

An instance of current LocalTime can be created from the system clock as below:

LocalTime now = LocalTime.now();

In the below code sample, we create a LocalTime representing 06:30 AM by parsing a string representation:

LocalTime sixThirty = LocalTime.parse("06:30");

The Factory method “of” can be used to create a LocalTime. For example the below code creates LocalTime representing 06:30 AM using the factory method:

LocalTime sixThirty = LocalTime.of(6, 30);

The below example creates a LocalTime by parsing a string and adds an hour to it by using the “plus” API. The result would be LocalTime representing 07:30 AM:

LocalTime sevenThirty = LocalTime.parse("06:30").plus(1, ChronoUnit.HOURS);

Various getter methods are available which can be used to get specific units of time like hour, min and secs like below:

int six = LocalTime.parse("06:30").getHour();

We can also check if a specific time is before or after another specific time. The below code sample compares two LocalTime for which the result would be true:

boolean isbefore = LocalTime.parse("06:30").isBefore(LocalTime.parse("07:30"));

The max, min and noon time of a day can be obtained by constants in LocalTime class. This is very useful when performing database queries to find records within a given span of time. For example, the below code represents 23:59:59.99:

LocalTime maxTime = LocalTime.MAX

Working With LocalDateTime

The LocalDateTime is used to represent a combination of date and time.

This is the most commonly used class when we need a combination of date and time. The class offers a variety of APIs and we will look at some of the most commonly used ones.

An instance of LocalDateTime can be obtained from the system clock similar to LocalDate and LocalTime:

LocalDateTime.now();

The below code samples explain how to create an instance using the factory “of” and “parse” methods. The result would be a LocalDateTime instance representing 20 February 2015, 06:30 AM:

LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
LocalDateTime.parse("2015-02-20T06:30:00");

There are utility APIs to support addition and subtraction of specific units of time like days, months, year and minutes are available. The below code samples demonstrates the usage of “plus” and “minus” methods. These APIs behave exactly like their counterparts in LocalDate and LocalTime:

localDateTime.plusDays(1);
localDateTime.minusHours(2);

Getter methods are available to extract specific units similar to the date and time classes. Given the above instance of LocalDateTime, the below code sample will return the month February:

localDateTime.getMonth();

Using ZonedDateTime API

The LocalDateTime can be converted to a specific zone:

ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);

The ZonedDateTime provides parse method to get time zone specific date time:

ZonedDateTime.parse("2015-05-03T10:15:30+01:00[Europe/Paris]");

Another way to work with time zone is by using OffsetDateTime. The OffsetDateTime is an immutable representation of a date-time with an offset. This class stores all date and time fields, to a precision of nanoseconds, as well as the offset from UTC/Greenwich.

The OffSetDateTime instance can be created as below using ZoneOffset. Here we create a LocalDateTime representing 6:30 am on 20th February 2015:

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);

Then we add two hours to the time by creating a ZoneOffset and setting for the localDateTime instance:

ZoneOffset offset = ZoneOffset.of("+02:00");OffsetDateTime offSetByTwo = OffsetDateTime  .of(localDateTime, offset);

We now have a localDateTime of 2015-02-20 06:30 +02:00. Now let’s move on to how to modify date and time values using the Period and Duration classes.

Working With Period

The Period class is widely used to modify values of given a date or to obtain the difference between two dates:

LocalDate initialDate = LocalDate.parse("2007-05-10");

The Date can be manipulated using Period as shown in the following code snippet:

LocalDate finalDate = initialDate.plus(Period.ofDays(5));

The Period class has various getter methods such as getYears, getMonths and getDays to get values from a Period object. The below code example returns an int value of 5 as we try to get difference in terms of days:

int five = Period.between(initialDate, finalDate).getDays();

The Period between two dates can be obtained in a specific unit such as days or month or years, using ChronoUnit.between:

long five = ChronoUnit.DAYS.between(initialDate, finalDate);

This code example returns five days. Let’s continue by taking a look at the Duration class.

Java 8 provides ZonedDateTime when we need to deal with time zone specific date and time. The ZoneId is an identifier used to represent different zones. There are about 40 different time zones and the ZoneId are used to represent them as follows.

In this code snippet we create a Zone for Paris:

ZoneId zoneId = ZoneId.of("Europe/Paris");

A set of all zone ids can be obtained as below:

Set<String> allZoneIds = ZoneId.getAvailableZoneIds();

Working With Duration

Similar to Period, the Duration class is use to deal with Time. In the following code we create a LocalTime of 6:30 am and then add a duration of 30 seconds to make a LocalTime of 06:30:30am:

LocalTime initialTime = LocalTime.of(6, 30, 0);LocalTime finalTime = initialTime.plus(Duration.ofSeconds(30));

The Duration between two instants can be obtained either as a Duration or as a specific unit. In the first code snippet we use the between() method of the Duration class to find the time difference between finalTime and initialTime and return the difference in seconds:

long thirty = Duration.between(initialTime, finalTime).getSeconds();

In the second example we use the between() method of the ChronoUnit class to perform the same operation:

long thirty = ChronoUnit.SECONDS.between(initialTime, finalTime);

Now we will look at how to convert existing Date and Calendar to new Date/Time.

Compatibility with Date and Calendar

Java 8 has added the toInstant() method which helps to convert existing Date and Calendar instance to new Date Time API as in the following code snippet:

LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault());

The LocalDateTime can be constructed from epoch seconds as below. The result of the below code would be a LocalDateTime representing 2016-06-13T11:34:50:

LocalDateTime.ofEpochSecond(1465817690, 0, ZoneOffset.UTC);

Date and Time Formatting

Java 8 provides APIs for the easy formatting of Date and Time:

LocalDateTime localDateTime = LocalDateTime.of(2015, Month.JANUARY, 25, 6, 30);

The below code passes an ISO date format to format the local date. The result would be 2015-01-25 :

String localDateString = localDateTime.format(DateTimeFormatter.ISO_DATE);

The DateTimeFormatter provides various standard formatting options. Custom patterns can be provided to format method as well, like below, which would return a LocalDate as 2015/01/25:

localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

We can pass in formatting style either as SHORTLONG or MEDIUM as part of the formatting option. The below code sample would give an output representing LocalDateTime in 25-Jan-2015, 06:30:00:

localDateTime  .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM))  .withLocale(Locale.UK);

Let us take a look at alternatives available to Java 8 Core Date/Time APIs.

7.Collection API improvements

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

  1. 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.
  2. Collection default method removeIf(Predicate filter) to remove all of the elements of this collection that satisfy the given predicate.
  3. Collection spliterator() method returning Spliterator instance that can be used to traverse elements sequentially or parallel.
  4. Map replaceAll(), compute(), merge() methods.
  5. Performance Improvement for HashMap class with Key Collisions

8.Concurrency API improvements

Some important concurrent API enhancements are:

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

9.Java IO improvements

Some IO improvements known to me are:

  1. Files.list(Path dir) that returns a lazily populated Stream, the elements of which are the entries in the directory.
  2. 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.
  3. BufferedReader.lines() that return a Stream, the elements of which are lines read from this BufferedReader.

10.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.
    min(), max() and sum() methods in Integer, Long and Double wrapper classes.
  3. 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.
  4. Several utility methods in Math class.
  5. jjs command is added to invoke Nashorn Engine.
  6. jdeps command is added to analyze class files
    JDBC-ODBC Bridge has been removed.
  7. PermGen memory space has been removed

That’s all for Java 8 features with example programs.

Popular posts from this blog

Window function in PySpark with Joins example using 2 Dataframes (inner join)

Complex SQL: fetch the users who logged in consecutively 3 or more times (lead perfect example)

Credit Card Data Analysis using PySpark (how to use auto broadcast join after disabling it)