Java 8 introduced a variety of enhancements to the programming language. In this blog let's understand the Java 8 features. Discuss their significance
Functional Interfaces and Lambda Expressions(λ)
Functional Interface
: An interface that contain only oneabstract
method but you can have any number of concrete ,default,static methods.
interface Calculator {
int calculate(int x, int y);
}
examples: Runnable ,Callable,Supplier,Consumer, Comparable etc..
Lambda Expressions
: It is an anonymous function , i.e, a function with no name, no type and no modifiers. They brought functional programming style closer to java and helps to write more concise and readable code.
syntax:
(parameters) -> { body } ; "-> "
Separates the parameters from impl
lambda expressions provide implementation to the abstract method of a functional interface. They leverage automatic type inference
Using anonymous inner classes to provide an implementation for the abstract method of a functional interface.
Calculator c = new Calculator(){
@Override
public int calculate(int x, int y) {
return x + y;
}};
Using Lambda Expressions:
Calculator cal = (x, y) -> x + y;
cal.calculate(10, 20);// calling a Lambda expression using FI reference variable
Commonly used functional interfaces:
Predicate<T> , Function<T, R> , Consumer<T> ,Supplier<T>
public interface Predicate { public interface Function<T,R> {
boolean test(T t); R appply(T t);
} }
public interface Consumer { public interface Supplier {
void accept(T,t); T get();
} }
Default and static methods in Interfaces
Java 8, interfaces are enhanced to have a method with implementation. We can use default
and static
keyword to create interfaces with method implementation.
why default and static methods were introduced ?
Static methods
in interfaces allow to defineutility methods
related to the interface. These methods can provide common functionality that is useful across multiple implementations of the interface.Default methods
enable you to add new functionality to existing interfaces and ensurebackward compatibility.
They allow interfaces to evolve over time by adding new methods without breaking existing implementations.Note :
1. Static methods can be accessed using interface name and they are not inherited by implementation class and cannot be overridden
2. Default methods can be accessed using reference variable of implementation class and can be overriddenpublic interface Calculator { // Static method static String getDefaultMode() { return "Basic"; } // Default method default void displayMode() { System.out.println("Calculator Mode: " + getDefaultMode()); } // Abstract method int calculate(int x, int y); }
Method References
The
double colon (::)
operator in Java is used for method references, providing a concise way to refer to methods without invoking them directly. It comes in four types:static method
reference,instance method
reference, reference to aninstance method of an arbitrary object
, andconstructor
reference. Use of method references contributes to writing cleaner, more expressive, and maintainable code in Java, particularly in the context of functional programming paradigms.
// Reference to a static method
Syntax: ContainingClass::staticMethodName
Calculator calculator = (x,y) -> x+y; // using lambda
Calculator calculator = Integer::sum;
//Reference to an instance method of a particular object
Syntax: containingObject::instanceMethodName
Calculator c = new BasicCalculator();
Runnable mode = c::displayMode;
String str = "Chess";
Supplier<Integer> lengthSupplier = str::length;
Integer length = lengthSupplier.get();
//Reference to an instance method of an arbitrary object of a particular type
Syntax : ContainingType::methodName
List<String> list = Arrays.asList("King", "Queen", "Rook");
list.forEach(System.out::println);
String[] stringArray = { "King", "Queen", "Rook", "Bishop","Knight", "Pawn"};
Arrays.sort(stringArray, String::compareToIgnoreCase);
//Reference to a constructor
Syntax : ClassName::new
Supplier<ArrayList<String>> arrayListSupplier = ArrayList::new;
ForEach
The
forEach
method was introduced to theIterable
interface, allowing for a more concise and readable way to iterate over collections like lists, sets, and maps. TheforEach
method accepts a lambda expression or a method reference as its argument, which is executed for each element in the collection.Syntax:
void forEach(Consumer<? super T> action);
List<String> chessPieces = Arrays.asList("King", "Queen", "Bishop", "Knight", "Rook", "Pawn");
// Using forEach to print each chess piece name
chessPieces.forEach(piece -> System.out.println(piece));
Optional
The
Optional
class was introduced to address the problem of dealing withnull
values in a more concise and expressive manner. It is a container class that may or may not contain a non-null value. UsingOptional
, you can preventNullPointerExceptions
and write more robust and readable code- Creating Optional :
// Empty Optional
Optional<String> emptyOptional = Optional.empty();
//Throws Null Pointer Exception when input is null
Optional<String> nonNullOptional = Optional.of("NonNull-Input");
//Returns an empty Optional if Value is Null
Optional<String> nullableOptional = Optional.ofNullable(null);
Stream API
The Stream API introduced in Java 8 , Stream is a sequence of elements which provides a new way to process data in a functional style, allowing you to perform operations on collections of objects. Streams allow you to express complex data processing queries concisely, which can lead to more readable and maintainable code
- Creating a stream :
// Using of Creates a stream from a sequence of elements
Stream<String> stream = Stream.of("King", "Queen", "Rook", "Bishop");
// Using stream() -- Converts a collection or array into a stream.
Stream<String> stream = Arrays.asList("King", "Queen", "Rook").stream();
// Generates an infinite stream using a Supplier.
Stream<String> infiniteStream = Stream.generate(() -> "atom").limit(5);
// Generates an infinite stream by applying a Unary Funcation repeatedly.
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2).limit(5);
- Intermediate Operations
// Filters the elements based on a predicate.
Stream.of("King", "Queen", "Rook", "Bishop").filter(s -> s.startsWith("K"))
//Transforms each element using the given function
Stream.of("King", "Queen", "Rook", "Bishop").map(String::toUpperCase)
//Truncates the stream to a maximum size.
Stream.of("King", "Queen", "Rook", "Bishop").limit(2);
//Flattens the elements of a stream of streams into a single stream.
Stream<List<String>> stream = Stream.of(Arrays.asList("King", "Queen"), Arrays.asList("Rook", "Bishop"));
stream.flatMap(Collection::stream).forEach(System.out::println);
- Short-Circuiting Operations
Stream.of("King", "Queen", "Rook").anyMatch(s -> s.equals("Queen"));
Stream.of("King", "Queen", "Rook").findFirst();
Stream.of("King", "Queen", "Rook").findAny();
- Terminal Operations
//Performs an action for each element
Stream<String> stream = Stream.of("King", "Queen", "Rook", "Bishop");
stream.forEach(System.out::println);
//Collects the elements into a collection
Stream<String> stream = Stream.of("King", "Queen", "Rook", "Bishop");
List<String> collected = stream.collect(Collectors.toList());
//Reduces the elements to a single value
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> reduced = stream.reduce((a, b) -> a + b);
Parallel Streams
A parallel stream can leverage the power of multi-core processors to perform operations concurrently. Instead of processing elements sequentially, a parallel stream divides the workload among multiple threads, allowing for potentially faster execution, especially for computationally intensive tasks or operations that can be performed independently on different elements.
//Creating a parallell stream
List<String> list = Arrays.asList("King", "Queen", "Rook", "Bishop");
// Sequential stream
Stream<String> sequentialStream = list.stream();
// Parallel stream
Stream<String> parallelStream = list.parallelStream();
Note: When using parallel streams, ensure thread-safe operations, expect potential element order changes, and consider overhead that may not always yield faster results.
Base64
Represents binary data in an ASCII string format, In Java 8, the java.util.Base64
class was introduced to provide native support for Base64 encoding and decoding operations.
//Encoding
Base64.getEncoder().encodeToString("Chess Game".getBytes());
//Decoding
String encodedString = "Q2hlc3MgR2FtZQ==";
new String(Base64.getDecoder().decode(encodedString));
Date-Time API (java.time Package)
Due to the limitations of the java.util.Date
and java.util.Calendar
classes. The Date-Time API introduced in Java 8, located in the java.time
package, provides a comprehensive and easy-to-use API for date and time manipulation.
LocalDate : Represents a date without time or timezone information
Example: 2024-04-06
LocalTime : Represents a time without date or timezone information
Example: 14:30:45
LocalDateTime : Represents a date and time without timezone information
Example: 2024-04-06T14:30:45
ZonedDateTime : Represents a date and time with timezone information
Example: 2024-04-06T19:00:45+05:30[Asia/Kolkata]
Instant : Represents an instantaneous point on the time-line
Example: 2024-04-06T13:30:45Z
Duration : Represents a time-based amount of time
Example : P1Y
LocalDate start = LocalDate.of(2023, 4, 6); LocalDate end = LocalDate.of(2024, 4, 6); Period period = Period.between(startDate, endDate);
Period : Represents a date-based amount of time
Example : PT8784H
Instant start = Instant.parse("2023-04-06T10:18:30.00Z"); Instant end = Instant.parse("2024-04-06T10:18:30.00Z"); Duration duration = Duration.between(start, end);
DateTimeFormatter : Formats and parses date-time objects.
Example: "yyyy-MM-dd HH:mm:ss"