Java 8 Features – Lambda Expressions, Functional Interfaces, Streams, and More - Textnotes

Java 8 Features – Lambda Expressions, Functional Interfaces, Streams, and More


Learn the powerful new features introduced in Java 8, including Lambda Expressions, Functional Interfaces, Streams API, Method References, and the new Date and Time API.

Java 8 introduced several powerful features that significantly improved productivity and enhanced functional programming capabilities in Java. These features help developers write concise, readable, and maintainable code. Let’s explore each of them.

1. Lambda Expressions

  1. Lambda expressions provide a clear and concise way to represent an instance of a functional interface.
  2. They allow you to pass behavior as arguments (i.e., passing code as a parameter).

Syntax: (parameters) -> expression

Example – Simple Lambda Expression


public class Main {
public static void main(String[] args) {
// Traditional way
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task is running");
}
};
task.run();
// Using Lambda Expression
Runnable taskLambda = () -> System.out.println("Task is running");
taskLambda.run();
}
}

Key Points:

  1. Makes code more concise
  2. Great for functional interfaces (interfaces with only one abstract method)

2. Functional Interfaces

  1. An interface with only one abstract method is known as a functional interface.
  2. Java 8 provides the @FunctionalInterface annotation to denote functional interfaces.
  3. Examples of functional interfaces in the Java API include Runnable, Callable, Comparator, etc.

Example – Custom Functional Interface


@FunctionalInterface
interface MyFunctionalInterface {
void myMethod(); // single abstract method
}

public class Main {
public static void main(String[] args) {
MyFunctionalInterface myFunc = () -> System.out.println("Functional Interface");
myFunc.myMethod();
}
}

Key Points:

  1. @FunctionalInterface ensures that only one abstract method exists
  2. Can be used as the target type for lambda expressions

3. Predicate, Function, Consumer, Supplier Interfaces

Java 8 introduced several built-in functional interfaces such as:

Predicate Interface

  1. Represents a boolean-valued function.
  2. Used for conditions and filtering.

import java.util.function.Predicate;

public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = (n) -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
}
}

Function Interface

  1. Represents a function that accepts one argument and produces a result.

import java.util.function.Function;

public class Main {
public static void main(String[] args) {
Function<Integer, Integer> square = (n) -> n * n;
System.out.println(square.apply(5)); // 25
}
}

Consumer Interface

  1. Represents an operation that accepts a single input and returns no result.

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
Consumer<String> greet = (name) -> System.out.println("Hello " + name);
greet.accept("Alice");
}
}

Supplier Interface

  1. Represents a function that supplies an object of a given type.

import java.util.function.Supplier;

public class Main {
public static void main(String[] args) {
Supplier<String> getGreeting = () -> "Hello, World!";
System.out.println(getGreeting.get()); // Hello, World!
}
}

4. Streams API

  1. The Streams API allows you to process sequences of elements (like collections, arrays) in a functional style.
  2. Stream operations can be intermediate (e.g., filter, map) or terminal (e.g., collect, forEach).

Example – Filtering and Collecting Elements


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // [2, 4, 6, 8]
}
}

Key Points:

  1. Stream API enables lazy evaluation
  2. Supports parallel operations using parallelStream()
  3. Intermediate and terminal operations

5. Method References

  1. Method references are shorthand for calling a method directly.
  2. Syntax: ClassName::methodName

Example – Method Reference for Static Method


import java.util.Arrays;
import java.util.List;

public class Main {
public static void printNumber(Integer number) {
System.out.println(number);
}

public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
numbers.forEach(Main::printNumber); // Using method reference
}
}

Key Points:

  1. Method references can be used for static methods, instance methods, and constructor references.

6. Optional Class

  1. The Optional class is used to represent optional values that may or may not be present.
  2. It is used to avoid NullPointerException.

Example – Using Optional


import java.util.Optional;

public class Main {
public static void main(String[] args) {
Optional<String> name = Optional.ofNullable(null);

// If present, return value, otherwise return default value
System.out.println(name.orElse("Default Name")); // Default Name

// If present, execute a function
name.ifPresent(n -> System.out.println("Hello " + n));
}
}

Key Points:

  1. Avoids null checks
  2. Useful for returning values that may be missing

7. Date and Time API (java.time)

  1. Java 8 introduced a new Date and Time API under java.time package to improve the handling of dates, times, durations, and periods.
  2. Provides immutable classes like LocalDate, LocalTime, LocalDateTime, ZonedDateTime, and Duration.

Example – Using LocalDate and LocalTime


import java.time.LocalDate;
import java.time.LocalTime;

public class Main {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();

System.out.println("Today: " + today); // 2025-12-06
System.out.println("Time: " + now); // 14:30:15.533
}
}

Key Points:

  1. Immutable classes for thread-safety
  2. Supports time zones, date-time arithmetic, and formatting

8. Default and Static Methods in Interfaces

  1. Java 8 introduced the ability to define default and static methods in interfaces.

Default Method:

  1. Provides a default implementation for methods in interfaces.
  2. Can be overridden in implementing classes.

interface MyInterface {
default void defaultMethod() {
System.out.println("Default method");
}
}

public class Main implements MyInterface {
public static void main(String[] args) {
Main obj = new Main();
obj.defaultMethod(); // Default method
}
}

Static Method:

  1. Can define static methods in interfaces which can be invoked without an instance of the class.

interface MyInterface {
static void staticMethod() {
System.out.println("Static method");
}
}

public class Main {
public static void main(String[] args) {
MyInterface.staticMethod(); // Static method
}
}

9. Summary

  1. Lambda Expressions provide concise, functional-style code.
  2. Functional Interfaces define single-method interfaces.
  3. Streams API makes it easier to handle sequences of data.
  4. Method References offer shorthand for method invocation.
  5. Optional helps avoid null-related issues.
  6. Date and Time API simplifies date and time operations.
  7. Default and Static Methods in Interfaces allow additional functionality in interfaces.