Skip to content

Java 8 (March 2014)

Java 8 Status

Release Information

Release Date: March 18, 2014 End of Support: December 2030 (Extended) Theme: Functional Programming Revolution

Overview

Java 8 was a revolutionary release that transformed Java from a purely object-oriented language into one that embraces functional programming paradigms. This release is still widely used and remains the baseline for many enterprise applications.


Lambda Expressions

Interview Favorite

One of the most asked features. Know the syntax and functional interfaces.

The Problem

Creating instances of single-method interfaces required verbose anonymous classes.

Before/After

// Verbose anonymous class
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked!");
    }
});

// Sorting with Comparator
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});
// Concise lambda expression
button.addActionListener(e -> System.out.println("Button clicked!"));

// Sorting with lambda
Collections.sort(names, (a, b) -> a.compareTo(b));

// Or even simpler with method reference
Collections.sort(names, String::compareTo);

Key Points

  • Lambda syntax: (parameters) -> expression or (parameters) -> { statements; }
  • Works with functional interfaces (interfaces with single abstract method)
  • @FunctionalInterface annotation ensures compile-time checking

Stream API

Interview Favorite

Know the difference between intermediate and terminal operations.

The Problem

Processing collections required imperative loops with mutable state.

Before/After

// Filter and transform a list
List<String> filtered = new ArrayList<>();
for (Person person : people) {
    if (person.getAge() >= 18) {
        filtered.add(person.getName().toUpperCase());
    }
}

// Find sum of values
int total = 0;
for (Order order : orders) {
    if (order.getStatus() == Status.COMPLETED) {
        total += order.getAmount();
    }
}
// Declarative, readable pipeline
List<String> filtered = people.stream()
    .filter(p -> p.getAge() >= 18)
    .map(Person::getName)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// Sum with streams
int total = orders.stream()
    .filter(o -> o.getStatus() == Status.COMPLETED)
    .mapToInt(Order::getAmount)
    .sum();

// Parallel processing - just change stream() to parallelStream()
List<String> filtered = people.parallelStream()
    .filter(p -> p.getAge() >= 18)
    .map(Person::getName)
    .collect(Collectors.toList());

Common Stream Operations

Type Operations
Intermediate filter, map, flatMap, sorted, distinct, limit, skip
Terminal collect, forEach, reduce, count, findFirst, anyMatch

Optional

The Problem

NullPointerException was the most common runtime error. Null checks cluttered code.

Before/After

// Nested null checks
String city = null;
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        city = address.getCity();
    }
}
if (city == null) {
    city = "Unknown";
}
// Elegant Optional chain
String city = Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .orElse("Unknown");

// Other useful methods
Optional<User> user = findUserById(id);
user.ifPresent(u -> System.out.println(u.getName()));
User result = user.orElseThrow(() -> new UserNotFoundException(id));

Optional Best Practices

Do

  • Use as return type for methods that might not return a value
  • Use map() and flatMap() for transformations
  • Use orElse(), orElseGet(), orElseThrow() for defaults

Don't

  • Use Optional as method parameter
  • Use Optional as field type
  • Call get() without checking isPresent()

Date/Time API (java.time)

The Problem

java.util.Date and Calendar were confusing, mutable, and not thread-safe.

Before/After

// Confusing - month is 0-indexed!
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15); // January, not February!
Date date = cal.getTime();

// Mutable - dangerous in multithreaded code
date.setTime(System.currentTimeMillis());

// Formatting requires separate class
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String formatted = sdf.format(date); // Not thread-safe!
// Clear and intuitive
LocalDate date = LocalDate.of(2024, 1, 15); // January 15
LocalDateTime dateTime = LocalDateTime.of(2024, 1, 15, 10, 30);
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("America/New_York"));

// Immutable - safe to share
LocalDate tomorrow = date.plusDays(1); // Returns new instance

// Built-in formatting
String formatted = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String customFormatted = date.format(custom);

// Duration and Period
Duration duration = Duration.between(startTime, endTime);
Period period = Period.between(startDate, endDate);

java.time Classes

Class Use Case
LocalDate Date without time (birthday, holiday)
LocalTime Time without date (opening hours)
LocalDateTime Date and time without timezone
ZonedDateTime Date and time with timezone
Instant Machine timestamp (epoch-based)
Duration Time-based amount (hours, minutes)
Period Date-based amount (years, months, days)

Default Methods in Interfaces

The Problem

Adding methods to interfaces broke all implementations.

Before/After

// Adding a method to an interface...
public interface Vehicle {
    void start();
    void stop(); // NEW - breaks all existing implementations!
}

// Every implementation must be updated
public class Car implements Vehicle {
    public void start() { }
    public void stop() { } // Must add this
}
// Default implementation in interface
public interface Vehicle {
    void start();

    default void stop() {
        System.out.println("Vehicle stopped");
    }

    // Static methods also allowed
    static Vehicle create(String type) {
        return switch(type) {
            case "car" -> new Car();
            case "bike" -> new Bike();
            default -> throw new IllegalArgumentException();
        };
    }
}

// Existing implementations continue to work
// Or override if needed
public class Car implements Vehicle {
    public void start() { }
    // stop() is inherited with default behavior
}

Method References

The Problem

Even simple lambdas that just call a method were somewhat verbose.

Syntax

Type Lambda Method Reference
Static method x -> Math.abs(x) Math::abs
Instance method s -> s.toUpperCase() String::toUpperCase
Instance on object x -> obj.process(x) obj::process
Constructor s -> new Person(s) Person::new

Example

// Lambda vs Method Reference
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println); // Cleaner

list.stream().map(s -> s.toUpperCase());
list.stream().map(String::toUpperCase); // Cleaner

list.stream().map(name -> new Person(name));
list.stream().map(Person::new); // Cleaner

Quick Reference

// Lambda
(a, b) -> a + b
x -> x * 2
() -> System.out.println("Hello")

// Stream
list.stream()
    .filter(x -> x > 0)
    .map(x -> x * 2)
    .collect(Collectors.toList());

// Optional
Optional.ofNullable(value)
    .map(v -> v.transform())
    .orElse(defaultValue);

// Date/Time
LocalDate.now();
LocalDateTime.of(2024, 1, 15, 10, 30);
date.plusDays(1);