You are here
Home > Java 8 >

Java 8 Features

Java 8 New Features
 Java 8 New Features

Java 8 was released by Oracle after 2 Years 7 Months 18 Days of release of java 7 on March 18th, 2014.  However, after Java 1.5 version, Java 8 is the next major release version. Before Java 8, Sun community focused majorly on objects, but in Java 8 version, Oracle community has focused more on functional programming to bring its benefits to the Java language. However, it doesn’t mean that Java is a functional oriented programming language, but we should grasp it in a way that we can apply functional aspects of programming as well now with Java 8. It is a part of Java 8, as an object oriented programming.

Here, we will discuss new features of Java 8 one by one. However, in this article, we will have the list of Java 8 Features and quick recap of the concepts behind each feature. You may visit provided links to get more details of features where applicable. Instead, you can visit our Core Java Section for the same. Let’s discuss our topic ‘Java 8 Features’.

What is Functional Programming in Java 8 and Its Benefits?

Till Java 7, we could pass either primitive types or an Object inside our method arguments. But from Java 8 onward we can pass functions as an argument to a method as a lambda expression. Even we can directly pass implementation logic inside method arguments without creating new lengthy objects & methods. Of course, this is the concept behind functional programming. On doing so, we can minimize the lines of code in development and make our code easy to read.

What are the new features introduced in Java 8?

Here, we will list the new features introduced in Java 8 which are very crucial to know as a Java Programmer & talk about them in detail in a while. We will categorize the changes in groups as below:

Changes in Language :

Lambdas(λ) and Functional Interfaces
Default and Static Methods in Interfaces
Method References (::)
Repeating Annotations
Better Type Inference
Extended Annotations Support

Changes in Java Compiler :

Parameter names

New features in Java libraries: 

Optional
Streams
Date/Time API (JSR 310)
StringJoiner
Nashorn JavaScript engine
Base64
Parallel Arrays
Concurrency

New Java Tools

Nashorn engine: jjs
Class dependency analyzer: jdeps

New Features in java runtime(JVM)

Now let’s understand the concepts one by one which are important to us as a developer.

The Lambda (λ) Expression: 

Lambda calculus is a big change in mathematical world which has been introduced in 1930. Because of benefits of Lambda calculus slowly this concept started being used in the programming world. “LISP” is the first programming which uses Lambda Expression.
☀ The other languages which use lambda expressions are:

1) C#.Net
2) C Objective
3) C
4) C++
5) Python
6) Ruby etc.
and finally now in Java also.
☀ The Main Objective of Lambda Expression is to bring benefits of functional programming in Java.

Lambda Expression is an anonymous (nameless) function. In other words, the function which doesn’t have the name, return type and access modifiers. Lambda Expression also referred to as anonymous functions or closures.

How to write Lambda Expressions :

Suppose we have a traditional method for adding two numbers & printing the result on the console as below:

private void add(int i, int j){
System.out.println(i+j);
}

Step#1 : remove the access modifier ⇒ void add(int i, int j) { System.out.println(i+j); }

Step#2: remove the return type ⇒ add(int i, int j) { System.out.println(i+j); }

Step#3 : remove the method name ⇒ (int i, int j) { System.out.println(i+j); }

Step#4 : insert arrow sign (→) between remaining method declaration & body ⇒ (int i, int j) →{ System.out.println(i+j); }

Step#5 : If the compiler is able to identify the type of parameters, remove them as well ⇒ (i, j) →{ System.out.println(i+j); }

Now our final Lambda Expression simplifies to ⇒ (i, j) →{ System.out.println(i+j); }

Example 1: Given below is a method to print “Hello Java 8” on the console.

public void m1() { System.out.println(“Hello Java 8”); } ⇔                
() -> { System.out.println(“Hello Java 8”); }

Example 2: Here is another program to multiply two numbers.

public void add(int a, int b) { System.out.println(a*b); } ⇔
(int a, int b) -> System.out.println(a*b); ⇔ 
(a,b) -> System.out.println(a*b);

If the type of the parameter can be decided by the compiler automatically based on the context, then we can remove parameter types also. Further, the above Lambda expression can be rewritten as (a,b) -> System.out.println (a*b);

Example 3: For example, suppose we have a String, we wanted to do modification on this string & getting String again after multiple operations. We will write a method as below:

public String m2(String str1) { return str2; } ⇔
 (String str1) -> return str2; ⇔
(str1) -> str2;

Note on Lambda Expressions:

  1. If only one method parameter is available and compiler can understand the type based on the context, then we can remove the type & parenthesis both. Suppose (String s) -> {System.out.println(s)}; can be simplified to s-> {System.out.println(s)};   
  2. Similar to the method body, lambda expression body can contain multiple statements. If more than one statements present, then we have to enclose it within curly braces. If one statement present, then curly braces are optional. As in example at point # 1 can again be simplified as s-> System.out.println(s);
  3. However, if no parameters are available, we can use empty parenthesis like : ()->System.out.println(“No parameter test”);
  4. Additionally, we can call the Lambda expression just like a traditional method, but only with the help of functional interfaces. We have covered Functional Interfaces in detail in the separate Topic.

♥ In order to get more details on Lambda Expressions , visit our separate tutorial on Lambda Expressions in Java 8.

Functional Interfaces : 

If an interface contains only one abstract method, such type of interface is called functional interface and the contained method is called functional method or single abstract method (SAM). Functional interfaces provide target types for lambda expressions and method references. One separate package java.util.Function has been introduced for Functional Interfaces.

Note : In addition to single abstract method, we can have any number of default & static methods and even public methods of java.lang.Object class inside a Functional Interface.

Examples of Functional Interfaces:

Runnable : contains only run() method
Comparable : contains only compareTo() method
ActionListener : contains only actionPerformed()
Callable : contains only call() method 

How to write a Functional Interface : 

In addition to having only one abstract method, we must write @Functional Interface annotation to specify that the interface is a Functional Interface. If you add annotation @Functional Interface, compiler will not let you add any other abstract method inside it.

//This is a Functional Interface
@FunctionalInterface  
	  Interface FunctionalInterface1 {
		
		public void m1();

	}


//This is not a Functional Interface. This code will show compilation error
@FunctionalInterface  
	  Interface NonFunctionalInterface2 {
		
		public void m1();
		public void m2();

	}

♥ In order to get more details on Functional Interfaces, visit our separate tutorial on Functional Interfaces in Java 8.

♥ Additionally, in order to get details on Predefined Functional Interfaces, visit our separate tutorial on Predefined Functional Interfaces in Java 8.

Default Method in Interface

If we have an implemented method inside an interface with default keyword, then we will call it as a Default method. We also call it defender method or virtual extension method. Default methods in interfaces were introduced in JDK 8. For example, in the below code, walks() is a default method inside interface Human. Further, we can’t force Implementing classes to override this method.

interface Human {
   void speaks();
   void eats();
   default void walks(){
      System.out.println("Every human follows the same walking pattern");
   }
}

Implementing classes are free to provide their own implementation of the default method. If the implementing class doesn’t override the default method, it means that the class doesn’t need that functionality in it.

♥ In order to get more details on Default Methods in Interfaces, visit our separate tutorial on Default Methods in Interfaces in Java 8.

Static Methods In Interface

Similar to Default Methods in Interfaces, Static Methods also have a method body (implementation). However, we can’t override them. Needless to say, static methods can’t be overridden.

The Process of declaring a Static method in Interface is similar to defining a Static method in a class. In simple words, we have to use the static keyword while defining the method. For example, look at the code below.

interface A {
    public static void m1(){
      System.out.println("I am introduced in Java 1.8");
    }
}

♥ In order to get more details on Static Methods in Interfaces, visit our separate tutorial on Static Methods in Interfaces in Java 8.

Method References

As we have seen in Lambda expression’s topic that we use lambda expressions to implement Functional interfaces with minimum lines of code and even to get better code readability. Similarly, we can use Method References(::) Java 8 to implement Functional interfaces with even lesser code again than lambda expressions and this time we get the benefit of code re-usability as well, because we don’t provide implementation for functional interface. Instead, we provide reference to already existing method(with similar argument types) to simplify the implementation of the functional interface using double colon (::) operator. This process of providing reference to pre-existing method is called Method reference.

♥ In order to get more details on Method References, visit our separate tutorial on Method References in Java 8.

Optional 

It is also one of the most interesting feature among all new features in Java 8. Optional is a new class under package java.util. The purpose of this call is to address the infamous NullPointerException. Many people in the industry believe that you are not a real Java programmer until you’ve dealt with a NullPointerException. Since NullPointerException indicates the absence of a value, the null reference is the source of many problems. So, Optional class can address some of these problems.

What can you do to prevent NullPointerException? You will add checks to prevent null dereferences. However, sometimes applying too many null checks, decreases the readability of our code. Unfortunately, we need a lot of boilerplate code to make sure we don’t get a NullPointerException. Furthermore, it is an error-prone process; what if you forget to check that one property could be null? Hence, we need is a better way to model the absence and presence of a value.

Java SE 8 introduces a new class called java.util.Optional<T> that is inspired from the ideas of Haskell and Scala. It is a class that encapsulates an optional value. In a nutshell, Optional is a single-value container that either contains a value or doesn’t (it is then said to be “empty”). The Optional class includes methods to explicitly deal with the cases where a value is present or absent.

Creating Optional objects

For example, below code creates an empty Optional.

Optional<Employee> emp = Optional.empty();

And here is an Optional with a non-null value:

Employee employee = new Employee();
Optional<Employee> emp = Optional.of(employee);

Also, by using ofNullable, you can create an Optional object that may hold a null value:

Optional<Employee > emp = Optional.ofNullable(Employee);

♦ Note: You should only use of() when you are sure the object is not null. If the object can be both null or not-null, then you should instead choose the ofNullable() method.

Stream

The Stream is also another feature of Java 8. A stream is nothing, but a sequence of objects. It offers us various methods to produce the desired result in minimum lines of code. Stream takes inputs from Collections, Arrays or I/O channels and provides the result as per the pipelined methods, but don’t change the original data structure.

How to create Streams

There are multiple ways to create a stream instance of various sources.

Empty Stream

We create an empty Stream with the help of empty() method when we want to avoid returning null for Streams having no element. For example:

Stream<String> emptyStream = Stream.empty();

Stream of Array

One of the sources is an Array to create a stream. We call it Stream of Array. Below are the two ways to create an Array of Streams. For example:

String[] array = new String[]{"p", "q", "r"}; 
Stream<String> streamOfArray= Arrays.stream(array);

Stream<String> streamOfArray = Stream.of("p", "q", "r");

Stream of Collection

Needless to say, we can create stream of any type of Collection like Set, List. For example:

Collection<String> collection = Arrays.asList("x", "y", "z"); 
Stream<String> streamOfCollection = collection.stream();

List<String> list = List.of("x", "y", "z"); Stream<String> streamOfList = list.stream();

Stream of Primitives

We can create streams of three primitive types such as int, long and double in Java 8. For that, we have three special interfaces : IntStream, LongStream, DoubleStream to work on int, long and double respectively. We can use two methods to generate any of the three types of streams of primitives. These are : range(int startInclusive, int endExclusive) and rangeClosed(int startInclusive, int endInclusive) 

range(int startInclusive, int endExclusive) method creates an ordered stream from the first parameter to the second parameter. It increments the value of subsequent elements with the step equal to 1. The result doesn’t include the last parameter, it is just an upper bound of the sequence.

rangeClosed(int startInclusive, int endInclusive) method does the same thing with only one difference, the second element is included.

For example :

IntStream intStream = IntStream.range(1, 4); 
LongStream longStream = LongStream.rangeClosed(1, 4);

Since Java 8, the Random class provides a wide range of methods for generating streams of primitives. For example, the following code creates a DoubleStream, which has four elements:

Random random = new Random(); DoubleStream doubleStream = random.doubles(4);

Stream of String

Even we can useString as a source for creating a stream with the help of the chars() method of the String class. Since there is no interface for CharStream in JDK, we use the IntStream to represent a stream of chars instead. For example:

IntStream streamOfChars = "pqr".chars();

Stream via Stream.generate()

In order to generate the elements, the generate() method accepts a Supplier<T>. As the resulting stream is infinite, we should define the desired size, or the generate() method will work until it reaches the memory limit. For example, the code below will create a sequence of twenty strings with the value “pqr”.

Stream<String> generatedStream = Stream.generate(() -> "pqr").limit(20);

Stream via Stream.builder()

While using this method of creating streams, we should specify the desired type, otherwise the build() method will create an instance of Stream<Object> by default. For example, the code below will create the Stream<String>.

Stream<String> streamBuilder = Stream.<String>builder().add("p").add("q").add("r").build();

Stream via Stream.iterate()

If you want to create an infinite stream, use iterate() method. For example, the code below will create a Stream of integers starting from value 24, as the first parameter of the method is 24. The second element in the stream will be 28, as per the expression in the second parameter of the method.

Stream<Integer> streamIterated = Stream.iterate(24, n -> n + 4).limit(20);

Stream of a File

When we create a stream of a file containing the text, every line of the text becomes an element of the stream. Furthermore, Java NIO class Files offers us to generate a Stream<String> of a text file through the lines() method. For example:

Path path = Paths.get("D:\\myfile.txt"); 
Stream<String> streamOfStrings = Files.lines(path); 
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));
♥ In order to get complete details on Stream API , visit our separate tutorial on Stream API in Java 8.

 

Date/Time API (JSR 310)

Java 8 has come with a new date and time API in java.time that offers greatly improved safety and functionality for developers. The new API models the domain well, with a good selection of classes for modeling a wide variety of developer use cases.

The already existing classes before Java8 (such as java.util.Date and SimpleDateFormatter) aren’t thread-safe, leading to potential concurrency issues for users—not something the average developer would expect to deal with when writing date-handling code. In order to address these problems and provide better support in the JDK core, a new date and time API, which is free of these problems, has been designed for Java SE 8. The project has been led jointly by the author of Joda-Time (Stephen Colebourne) and Oracle, under JSR 310. Hence, sometimes we also call it Joda API.

Classes in Date/Time API 

Class
Purpose
LocalDate  Displays only date (no offset or zone)
LocalTime  Displays only time (no offset or zone)
LocalDateTime  Displays date and time (no offset or zone)
OffsetDate  Displays a date with an offset like +05:30
OffsetTime  Displays time with an offset like +05:30
OffsetDateTime  Displays date and time with an offset
ZonedDateTime  Displays date and time with offset and time zone
YearMonth  Displays a year and month
MonthDay  Displays month and day
Period  Displays a defined time (such as 1 week)

These types can be mapped to vendor-specific database types or ANSI SQL types; for example, the ANSI mapping looks like Table below.

ANSI SQL
Java SE 8
DATE LocalDate
TIME LocalTime
TIMESTAMP LocalDateTime
TIME WITH TIMEZONE OffsetTime
TIMESTAMP WITH TIMEZONE OffsetDateTime

StringJoiner

StringJoiner is a new class added in Java 8 under java.util package. In fact, we can use it for joining Strings by making use of a delimiter, prefix, and suffix. It comes with below two constructors.

StringJoiner(CharSequence delimiter)
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

Example Of StringJoiner

For example, below code snippet demonstrates the feature of StingJoiner class.

private static String PREFIX = "{";
private static String SUFFIX = "}";

StringJoiner joiner = new StringJoiner(", ", PREFIX, SUFFIX);
   joiner.add("Core Java")
         .add("Spring Boot")
         .add("Angular");
System.out.println(joiner.toString());

Output

{Core Java, Spring Boot, Angular}

♦ In order to learn Java 8 New features in detail kindly visit Core Java Section.

close

2 thoughts on “Java 8 Features

Leave a Reply

Top