Java 8 Features
Dashboard
Topic 22/30
Home › Java 8 Features

8️⃣ Java 8 Features

Explore the revolutionary features introduced in Java 8, including Lambdas, Streams, Optional, and the new Date/Time API.

1. Lambda Expressions

Lambda expressions introduced a new syntax to Java, allowing you to treat functionality as a method argument, or code as data. This significantly reduces the boilerplate required for anonymous inner classes.

  • Syntax: (parameters) -> expression or (parameters) -> { statements; }
  • Functional Interface: A lambda expression can only be used where the expected type is a functional interface (an interface with exactly one abstract method).
  • Capturing Variables: Variables used inside a lambda must be effectively final.
  • Type Inference: The compiler can often infer parameter types based on the context.
LambdaDemo.javaJava
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class LambdaDemo {
    public static void main(String[] args) {
        // Runnable with lambda
        Runnable r = () -> System.out.println("Running in a thread!");
        new Thread(r).start();

        // Sorting a list using lambda
        List<String> names = Arrays.asList("John", "Alice", "Bob");
        
        // Old way: new Comparator<String>() { ... }
        // New way: Lambda expression
        Collections.sort(names, (a, b) -> a.compareTo(b));
        
        System.out.println(names); // [Alice, Bob, John]
    }
}
Output
Running in a thread!
[Alice, Bob, John]

2. Functional Interfaces

The java.util.function package introduced several built-in functional interfaces to represent common functions, predicates, and consumers.

  • Predicate<T>: Takes T, returns boolean (useful for filtering).
  • Function<T, R>: Takes T, returns R (useful for mapping/transforming).
  • Consumer<T>: Takes T, returns void (useful for iterating/processing).
  • Supplier<T>: Takes nothing, returns T (useful for lazy initialization).
  • BiFunction<T, U, R>: Takes T and U, returns R.
FunctionalInterfacesDemo.javaJava
import java.util.function.*;

public class FunctionalInterfacesDemo {
    public static void main(String[] args) {
        // Predicate
        Predicate<Integer> isEven = n -> n % 2 == 0;
        System.out.println("Is 4 even? " + isEven.test(4));

        // Function
        Function<String, Integer> lengthFunc = s -> s.length();
        System.out.println("Length of 'Java': " + lengthFunc.apply("Java"));

        // Consumer
        Consumer<String> printer = s -> System.out.println("Printing: " + s);
        printer.accept("Hello Functional World");

        // Supplier
        Supplier<Double> randomValue = () -> Math.random();
        System.out.println("Random: " + randomValue.get());

        // BiFunction
        BiFunction<Integer, Integer, Integer> adder = (a, b) -> a + b;
        System.out.println("Sum: " + adder.apply(10, 20));
    }
}
Tip: Custom functional interfaces should be annotated with @FunctionalInterface to ensure they only have one abstract method.

3. Method References

Method references provide a way to refer to methods or constructors without executing them. They can be considered shorthand for lambdas calling only a specific method.

MethodReferenceDemo.javaJava
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

class Person {
    String name;
    Person() { this.name = "Unknown"; }
    Person(String name) { this.name = name; }
    void printName() { System.out.println(this.name); }
    static void sayHello() { System.out.println("Hello"); }
}

public class MethodReferenceDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Tom", "Jerry");

        // 1. Static method reference (ClassName::staticMethod)
        Runnable r = Person::sayHello;
        r.run();

        // 2. Bound instance method reference (obj::instanceMethod)
        Person p = new Person("Alice");
        Runnable r2 = p::printName;
        r2.run();

        // 3. Unbound instance method reference (ClassName::instanceMethod)
        // Passes the element as the target of the method
        names.forEach(System.out::println); 

        // 4. Constructor reference (ClassName::new)
        Supplier<Person> personFactory = Person::new;
        Person newPerson = personFactory.get();
        newPerson.printName();
    }
}

4. Stream API

The Stream API introduced a declarative way to process collections of objects. Streams can be executed sequentially or in parallel.

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

public class StreamBasicsDemo {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("apple", "banana", "cherry", "date", "apricot");

        List<String> filtered = items.stream()
            .filter(s -> s.startsWith("a"))      // Intermediate: filter
            .map(String::toUpperCase)            // Intermediate: map
            .sorted()                            // Intermediate: sort
            .collect(Collectors.toList());       // Terminal: collect

        System.out.println(filtered); // [APPLE, APRICOT]
    }
}
StreamCollectorsDemo.javaJava
import java.util.*;
import java.util.stream.Collectors;

class Employee {
    String name, dept;
    int salary;
    Employee(String n, String d, int s) { name = n; dept = d; salary = s; }
    public String getDept() { return dept; }
}

public class StreamCollectorsDemo {
    public static void main(String[] args) {
        List<Employee> emps = Arrays.asList(
            new Employee("Alice", "IT", 5000),
            new Employee("Bob", "HR", 4000),
            new Employee("Charlie", "IT", 6000)
        );

        // Group by department
        Map<String, List<Employee>> byDept = emps.stream()
            .collect(Collectors.groupingBy(Employee::getDept));

        System.out.println("IT Employees count: " + byDept.get("IT").size());

        // Joining names
        String names = emps.stream()
            .map(e -> e.name)
            .collect(Collectors.joining(", "));
        System.out.println("All names: " + names);
    }
}
ParallelStreamDemo.javaJava
import java.util.stream.IntStream;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        long count = IntStream.range(1, 10000000)
            .parallel()
            .filter(n -> isPrime(n))
            .count();
        long end = System.currentTimeMillis();
        
        System.out.println("Primes found: " + count + " in " + (end - start) + "ms");
    }
    
    static boolean isPrime(int n) {
        if (n <= 1) return false;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
}

5. Optional Class

Optional<T> is a container object which may or may not contain a non-null value. It provides a better alternative to returning null, avoiding NullPointerException.

OptionalDemo.javaJava
import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {
        String name = null;
        
        // Creating Optional
        Optional<String> optName = Optional.ofNullable(name);
        
        // Providing default value
        String result = optName.orElse("Unknown User");
        System.out.println(result); // Unknown User
        
        // Using map and filter
        Optional<String> upper = Optional.of("java")
            .filter(s -> s.length() >= 3)
            .map(String::toUpperCase);
            
        upper.ifPresent(System.out::println); // JAVA
    }
}

6. Default and Static Methods in Interfaces

Interfaces can now have implementations via default methods, which help in evolving interfaces without breaking implementing classes. static methods in interfaces were also introduced.

InterfaceDefaultMethodDemo.javaJava
interface Vehicle {
    void start();
    
    // Default method
    default void honk() {
        System.out.println("Beep beep!");
    }
    
    // Static method
    static void displayInfo() {
        System.out.println("Vehicles help in transport.");
    }
}

class Car implements Vehicle {
    public void start() {
        System.out.println("Car started.");
    }
}

public class InterfaceDefaultMethodDemo {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
        car.honk(); // Inherited default method
        
        Vehicle.displayInfo(); // Calling static interface method
    }
}

7. New Date/Time API (java.time)

The old java.util.Date and Calendar were mutable, not thread-safe, and poorly designed. Java 8 introduced the Joda-Time-inspired java.time package.

DateTimeDemo.javaJava
import java.time.*;
import java.time.format.DateTimeFormatter;

public class DateTimeDemo {
    public static void main(String[] args) {
        // LocalDate, LocalTime, LocalDateTime
        LocalDate date = LocalDate.now();
        LocalTime time = LocalTime.now();
        LocalDateTime dt = LocalDateTime.now();
        
        System.out.println("Current Date: " + date);
        
        // Formatting
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
        System.out.println("Formatted: " + dt.format(formatter));
        
        // ZonedDateTime
        ZonedDateTime tokyoTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println("Tokyo Time: " + tokyoTime);
        
        // Period & Duration
        LocalDate future = date.plusDays(5);
        Period period = Period.between(date, future);
        System.out.println("Days diff: " + period.getDays());
    }
}

8. Base64 Encoding/Decoding

Java 8 provides standard built-in support for Base64 encoding and decoding.

Base64Demo.javaJava
import java.util.Base64;

public class Base64Demo {
    public static void main(String[] args) {
        String originalStr = "Java 8 is awesome";
        
        // Encode
        String encodedStr = Base64.getEncoder().encodeToString(originalStr.getBytes());
        System.out.println("Encoded: " + encodedStr);
        
        // Decode
        byte[] decodedBytes = Base64.getDecoder().decode(encodedStr);
        System.out.println("Decoded: " + new String(decodedBytes));
    }
}

9. Nashorn JavaScript Engine

Nashorn was introduced in Java 8 to execute JavaScript code from Java applications. Note: It was deprecated in Java 11 and removed in Java 15.

10. Summary Table

Feature Description
Lambda Expressions Enable functional programming in Java.
Stream API Pipeline operations on collections.
Optional Null-safe wrapper for values.
Date/Time API Immutable and thread-safe date/time models.
Default Methods Method implementations inside interfaces.