Sunday, 9 September 2018

Java 8 features with examples

         Java 8, released on March 18, 2014, introduced a major shift in the way Java applications are developed. It brought several powerful features that made code more concise, readable, and efficient.

In this post, we will explore the key features introduced in Java 8 and walk through practical examples to understand how they can be used in real-world applications.


Java 8 features:--

Some of the important Java 8 features are,

  • Lambda Expression
  • static and default methods in interface
  • Java Stream
  • Functional Interfaces
  • Java Time API
  • Collection Improvement.
  • Concurrency API improvement
  • Java IO improvement

  • Lambda Expression
A lambda expression is an anonymous function, which means it has no name and is not associated with any specific class. To create a lambda expression, specify the input parameters on the left side of the lambda operator (->) and place the expression or code block (method body) on the right side of the operator.

Syntax,

(parameter_list) -> {function_body}

Example:--

//Java 7: 
new Thread(new Runnable() { 
     @Override 
     public void run() { 
           System.out.println("Before Java8, runnable run() method"); 
     } 
}).start(); 

//Java 8 way: 
new Thread(() -> System.out.println("In Java8, Lambda expression  !!") ).start();

The advantages of lambda expressions are:

  1. They reduce the amount of code.

  2. They support both sequential and parallel execution by passing behavior into methods using the Stream API.

  3. When combined with the Stream API, lambda expressions can achieve higher efficiency (through parallel execution) for bulk operations on collections.


  • static and default methods in interface
       
          Before Java 8, interfaces had limited functionality. They could contain only abstract methods, and every class implementing an interface was required to provide implementations for all of those methods. Since all interface methods were implicitly public and abstract, there was little flexibility for extending interfaces without affecting existing implementations.

       Java 8 addressed this limitation by introducing default and static methods in interfaces. Default methods were introduced to solve a long-standing problem: how to evolve an interface without breaking the classes that already implement it. With default methods, developers can add new functionality to an interface by providing a default implementation, allowing existing implementations to remain unchanged. This enhancement made interfaces more powerful, flexible, and easier to evolve, especially when designing and maintaining APIs.

Default method Example:-

In the below example, InterfaceEx interface has default method method1(), which is implemented in interface itself, no need to implement in the implementing classes.

interface InterfaceEx {  

    // This is a default method, no need to implement in the implementation classes. 
    default void method1(){  
        System.out.println("Java 8 default method");  
    }  
    
    // abstract method, need to implement in implementing classes
    public void existingabstractMethod(String str);  
}  

public class JavaEx implements InterfaceEx { 
 
    // implementing abstract method
    public void existingabstractMethod(String str){           
        System.out.println("Existing Java abstract method implementation");  
    } 
 
    public static void main(String[] args) {  
         JavaEx ex = new JavaEx();
         //calling the default method of interface
         ex.method1();     
         //calling the abstract method of interface
         ex.existingabstractMethod("Java 8 features"); 
    }  
}

Output:--
Java 8 default method
Existing Java abstract method implementation

         Static method in interfaces are similar to the default methods except that we cannot override these methods in the classes that implements these interfaces.

Static method example:--


interface InterfaceEx {  

    // This is a default method, no need to implement in the implementation classes. 
    default void method1(){  
        System.out.println("Java 8 default method");  
    }  
    
    static void staticMethod() {
         System.out.println("Java 8 static method");
    }

    // abstract method, need to implement in implementing classes
    public void existingabstractMethod(String str);  
}  

public class JavaEx implements InterfaceEx { 
 
   // implementing abstract method
    public void existingabstractMethod(String str){           
        System.out.println("Existing Java abstract method implementation");  
    } 
 
    public static void main(String[] args) {  
         JavaEx ex = new JavaEx();
         //calling the default method of interface
         ex.method1();     
        
         // call static method using interface
         InterfaceEx.staticMethod();

         //calling the abstract method of interface
ex.existingabstractMethod("Java 8 features"); } }

Output:--
Java 8 default method
Java 8 static method
Existing Java abstract method implementation.


  • Java Stream

         The Stream API is used to process collections of objects in a functional and efficient manner. A stream is a sequence of elements that supports various operations, which can be chained together (pipelined) to produce the desired result.

A stream does not store data; therefore, it is not a data structure. Instead, it operates on data from a source, such as a collection, array, or I/O channel, without modifying the underlying data source.

In Java 8, streams can be created from collections, arrays, or I/O channels. Stream operations are categorized into intermediate and terminal operations. Intermediate operations are executed lazily and return another stream, allowing multiple operations to be chained together. A terminal operation triggers the execution of the stream pipeline and produces the final result, such as a collection, a single value, or a side effect.


Different ways to build the Stream:--

1) Using Stream.of(val1, val2, val3….or arrayOfElements)

public class StreamApi {
     public static void main(String[] args){
         Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
         stream.forEach(p -> System.out.println(p));
     }
}


2) Using list.stream()

public class StreamEx {
     public static void main(String[] args){
         List<Integer> list = Arrays.asList(new Integer[]{2,3,4,6,7,9});
         Stream<Integer> stream = list.stream();
         stream.forEach(p -> System.out.println(p));
     }
}


3) Using Stream.forEach() function

public class StreamEx {
     public static void main(String[] args){
         List<Integer> list = Arrays.asList(new Integer[]{2,3,4,6,7,9});
         Stream<Integer> stream = list.stream();
         stream.forEach(p -> System.out.println(p));
     }
}


4) Using Stream.collect(Collectors.toList()) function - Convert Stream to List

public class StreamEx {
     public static void main(String[] args){
         List<Integer> list = Arrays.asList(new Integer[]{2,3,4,6,7,9});
         Stream<Integer> stream = list.stream();
         List<Integer> evenNumbersList = stream.filter(i -> i%2 == 
            0).collect(Collectors.toList());
         System.out.print(evenNumbersList);
     }
}


5) Using Stream.toArray(EntryType[]::new) function - Convert Stream to array

public class StreamEx {
     public static void main(String[] args){
         List<Integer> list = Arrays.asList(new Integer[]{2,3,4,6,7,9});
         Stream<Integer> stream = list.stream();
         List<Integer> evenNumbersList = stream.filter(i -> i%2 == 
            0).toArray(Integer[]::new);
         System.out.print(evenNumbersList);
     }
}


6) Intermediate filter() Operation

      Filter accepts a predicate to filter all elements of the stream. This operation is intermediate which enables us to call another stream operation (e.g. forEach) on the result.


list.stream().filter((s) -> i%2 == 0)
                    .forEach(System.out::println);
                                 
Output:
2
4


7) Intermediate map() operation

         
The map() intermediate operation transforms each element in a stream by applying the specified mapping function. In the following example, each string is converted to its uppercase form. You can also use the map() operation to transform each element into an object of a different type.

names.stream().filter((s) -> s.startsWith("A"))
                     .map(String::toUpperCase)
                     .forEach(System.out::println);
                             
Output:
AMAR
ANKITA


8) Intermediate sorted() operation

Sorted is an intermediate operation which returns a sorted view of the stream.


names.stream().sorted()
                    .map(String::toUpperCase)
                    .forEach(System.out::println);
Output:
AMAR
KIRAN



  • Functional Interface

        A functional interface, introduced in Java 8, is an interface that contains exactly one abstract method. Any interface with a single abstract method is considered a functional interface, regardless of whether it is annotated with @FunctionalInterface.

The @FunctionalInterface annotation is optional, but it is recommended because it clearly indicates the intended purpose of the interface. It also enables the compiler to verify that the interface contains only one abstract method, preventing accidental additions of extra abstract methods.


Example:-

@FunctionalInterface
interface FunctionalIntrface {
      int operation(int a, int b);
 }

If you try to add the second abstract method in the above code, will get compilation error.

@FunctionalInterface
interface FnInteface {
      int operation(int a, int b);
      String getEmplName(Long empId);
 }

will get compilation error as,

Unexpected @FunctionalInterface annotation
@FunctionalInterface ^ FnInteface is not a functional interface
multiple non-overriding abstract methods found in interface FnInteface 

Runnable is a great example of functional interface with single abstract method run().


  • Java Time API
   
           Working with dates, times, and time zones in Java has always been challenging. Before Java 8, there was no comprehensive and consistent API for handling date and time. Java 8 addressed this issue by introducing the java.time package, which provides a modern, powerful, and easy-to-use Date and Time API.

The Java Time API is well designed and includes several sub-packages. For example, the java.time.format package provides classes for formatting and parsing dates and times, while the java.time.zone package offers support for time zones and their rules.

Unlike the older Date and Calendar APIs, the new Date and Time API uses enums instead of integer constants to represent months and days of the week. This makes the code more readable, type-safe, and less error-prone. Another important class is DateTimeFormatter, which provides a simple and flexible way to format and parse date-time values, making it easy to convert between date-time objects and their string representations.

Reference :--Java 8 Date – LocalDate, LocalDateTime, Instant



  • Collection API Improvement   
          We have discussed the forEach() method and the Stream API for collections. Several new methods have been added to the Collection API:
  • Iterator: Default method forEachRemaining(Consumer<? super E> action) performs the given action for each remaining element until all elements have been processed or the action throws an exception.

  • Collection: Default method removeIf(Predicate<? super E> filter) removes all elements of the collection that satisfy the given predicate.

  • Collection: Method spliterator() returns a Spliterator instance that can be used to traverse elements sequentially or in parallel.

  • Map: New methods replaceAll(), compute(), and merge() provide more flexible operations on map entries.

  • HashMap: Performance improvements have been made for handling key collisions more efficiently.


  • Concurrency API improvements 
         Some concurrent API enhancements in Java 8 are as,
  • 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.

  • Java IO Improvement
        Some of IO improvements in Java 8 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.
  3. 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.
  4. BufferedReader.lines() that return a Stream, the elements of which are lines read from this BufferedReader.

Thank you for visiting blog.


Related Posts:--

No comments:

Post a Comment