Java 8 Features - Complete Guide
Comprehensive guide to Java 8 features including Lambda expressions, Stream API, Optional, Functional Interfaces, Method References, Default Methods, and Date/Time API with diagrams, data flows, and real-world examples.
Java 8 Features - Complete Guide
Java 8, released in March 2014, was one of the most significant updates to the Java programming language. It introduced functional programming capabilities, making Java code more concise, readable, and maintainable.
Why Java 8 Matters
- Functional Programming: Lambda expressions and functional interfaces
- Stream API: Declarative data processing
- Better Code: More concise and readable
- Performance: Parallel processing made easy
- Modern Java: Foundation for Java 9+ features
Lambda Expressions
Lambda expressions enable functional programming in Java.
Syntax
(parameters) -> expression
(parameters) -> { statements; }
Before and After Java 8
// Before Java 8: Anonymous Class
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
};
// Java 8: Lambda Expression
Runnable runnable = () -> System.out.println("Hello World");
Lambda Examples
import java.util.*;
public class LambdaExamples {
public static void main(String[] args) {
List<String> names = Arrays.asList("Venu", "John", "Alice", "Bob");
// Sorting with lambda
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
// forEach with lambda
names.forEach(name -> System.out.println(name));
// Thread with lambda
new Thread(() -> System.out.println("Running")).start();
}
}
Lambda Data Flow
Traditional:
┌─────────────┐ ┌──────────────┐
│ Anonymous │ --> │ Implementation│
│ Class │ │ (Verbose) │
└─────────────┘ └──────────────┘
Lambda:
┌─────────────┐ ┌──────────────┐
│ Lambda │ --> │ Implementation│
│ (Concise) │ │ (Clean) │
└─────────────┘ └──────────────┘
Functional Interfaces
Interface with exactly one abstract method.
Built-in Functional Interfaces
| Interface | Method | Example |
|---|---|---|
Predicate<T> |
test(T) |
x -> x > 0 |
Function<T,R> |
apply(T) |
x -> x * 2 |
Consumer<T> |
accept(T) |
x -> System.out.println(x) |
Supplier<T> |
get() |
() -> new ArrayList<>() |
Examples
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Predicate - test condition
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
// Function - transform
Function<String, Integer> length = s -> s.length();
System.out.println(length.apply("Java")); // 4
// Consumer - consume
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello");
// Supplier - supply
Supplier<Double> random = () -> Math.random();
System.out.println(random.get());
}
}
Stream API
Functional approach to processing collections.
Stream Pipeline
┌──────────┐
│ Source │ (Collection, Array)
└────┬─────┘
│
▼
┌──────────┐
│Intermediate│ (filter, map, sorted)
│Operations│ (Lazy)
└────┬─────┘
│
▼
┌──────────┐
│ Terminal │ (collect, forEach, reduce)
│Operation │ (Eager)
└──────────┘
Stream Operations
import java.util.*;
import java.util.stream.*;
public class StreamExamples {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter even numbers
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Evens: " + evens);
// Map to squares
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares: " + squares);
// Sum using reduce
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// Count
long count = numbers.stream()
.filter(n -> n > 5)
.count();
System.out.println("Count > 5: " + count);
// Sorted
List<Integer> sorted = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println("Sorted desc: " + sorted);
}
}
Stream Processing Flow
Input: [1, 2, 3, 4, 5, 6]
│
▼
filter(n%2==0) → [2, 4, 6]
│
▼
map(n*n) → [4, 16, 36]
│
▼
collect() → List[4, 16, 36]
Optional Class
Container to avoid NullPointerException.
Optional Methods
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
// Create Optional
Optional<String> optional = Optional.of("Hello");
Optional<String> empty = Optional.empty();
// Check if present
if (optional.isPresent()) {
System.out.println(optional.get());
}
// orElse - default value
String value = empty.orElse("Default");
System.out.println(value);
// ifPresent - execute if present
optional.ifPresent(s -> System.out.println(s));
// map - transform
Optional<Integer> length = optional.map(String::length);
System.out.println("Length: " + length.get());
// filter
Optional<String> filtered = optional
.filter(s -> s.startsWith("H"));
System.out.println("Filtered: " + filtered.get());
}
}
Optional Flow
┌─────────────┐
│ Optional.of │
└──────┬──────┘
│
┌───┴───┐
│Present│
└───┬───┘
│
┌───┴───┐
Yes No
│ │
▼ ▼
┌─────┐ ┌────────┐
│get()│ │orElse()│
└─────┘ └────────┘
Method References
Shorthand for lambda expressions.
Types
1. Static: ClassName::staticMethod
2. Instance: instance::instanceMethod
3. Constructor: ClassName::new
4. Arbitrary: ClassName::instanceMethod
Examples
import java.util.*;
public class MethodReferenceDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Venu", "John", "Alice");
// Lambda
names.forEach(name -> System.out.println(name));
// Method reference
names.forEach(System.out::println);
// Static method reference
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.map(String::valueOf)
.forEach(System.out::println);
// Constructor reference
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
}
}
Date and Time API
New API in java.time package.
Key Classes
LocalDate - Date only
LocalTime - Time only
LocalDateTime - Date + Time
ZonedDateTime - Date + Time + Zone
Instant - Timestamp
Duration - Time difference
Period - Date difference
Examples
import java.time.*;
import java.time.format.DateTimeFormatter;
public class DateTimeDemo {
public static void main(String[] args) {
// LocalDate
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, 1, 15);
System.out.println("Today: " + today);
// LocalTime
LocalTime now = LocalTime.now();
LocalTime specific = LocalTime.of(14, 30);
System.out.println("Now: " + now);
// LocalDateTime
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("DateTime: " + dateTime);
// Formatting
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
String formatted = dateTime.format(formatter);
System.out.println("Formatted: " + formatted);
// Period - date difference
Period age = Period.between(birthday, today);
System.out.println("Age: " + age.getYears() + " years");
// Duration - time difference
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration duration = Duration.between(start, end);
System.out.println("Hours: " + duration.toHours());
}
}
Real-World Examples
Example 1: Employee Processing
import java.util.*;
import java.util.stream.*;
class Employee {
private String name;
private String department;
private double salary;
public Employee(String name, String dept, double salary) {
this.name = name;
this.department = dept;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
}
public class EmployeeProcessing {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Venu", "Engineering", 150000),
new Employee("John", "Finance", 120000),
new Employee("Alice", "Engineering", 140000),
new Employee("Bob", "Marketing", 110000)
);
// Find employees with salary > 120000
List<Employee> highEarners = employees.stream()
.filter(e -> e.getSalary() > 120000)
.collect(Collectors.toList());
System.out.println("High earners:");
highEarners.forEach(e ->
System.out.println(e.getName() + ": " + e.getSalary()));
// Group by department
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("\nBy Department:");
byDept.forEach((dept, emps) -> {
System.out.println(dept + ": " + emps.size() + " employees");
});
// Average salary by department
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
System.out.println("\nAverage Salary:");
avgSalary.forEach((dept, avg) ->
System.out.println(dept + ": $" + avg));
// Total salary
double total = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
System.out.println("\nTotal Salary: $" + total);
}
}
Example 2: Data Processing Pipeline
import java.util.*;
import java.util.stream.*;
public class DataPipeline {
public static void main(String[] args) {
List<String> data = Arrays.asList(
"apple", "banana", "cherry", "date",
"elderberry", "fig", "grape"
);
// Complex pipeline
String result = data.stream()
.filter(s -> s.length() > 4) // Filter long names
.map(String::toUpperCase) // Convert to uppercase
.sorted() // Sort alphabetically
.limit(3) // Take first 3
.collect(Collectors.joining(", ")); // Join with comma
System.out.println("Result: " + result);
// Output: BANANA, CHERRY, ELDERBERRY
}
}
Example 3: Parallel Processing
import java.util.*;
import java.util.stream.*;
public class ParallelProcessing {
public static void main(String[] args) {
List<Integer> numbers = IntStream.rangeClosed(1, 1000)
.boxed()
.collect(Collectors.toList());
// Sequential
long start = System.currentTimeMillis();
long sum1 = numbers.stream()
.mapToLong(Integer::longValue)
.sum();
long end = System.currentTimeMillis();
System.out.println("Sequential: " + (end - start) + "ms");
// Parallel
start = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
end = System.currentTimeMillis();
System.out.println("Parallel: " + (end - start) + "ms");
}
}
Best Practices
1. Use Method References When Possible
// ❌ Less readable
list.forEach(item -> System.out.println(item));
// ✅ More readable
list.forEach(System.out::println);
2. Avoid Side Effects in Streams
// ❌ Bad - modifying external state
List<Integer> results = new ArrayList<>();
numbers.stream()
.filter(n -> n > 5)
.forEach(results::add);
// ✅ Good - use collect
List<Integer> results = numbers.stream()
.filter(n -> n > 5)
.collect(Collectors.toList());
3. Use Optional Properly
// ❌ Bad
Optional<String> opt = Optional.of("value");
if (opt.isPresent()) {
String value = opt.get();
}
// ✅ Good
opt.ifPresent(value -> System.out.println(value));
// or
String value = opt.orElse("default");
4. Choose Right Stream Type
// For primitive types, use specialized streams
IntStream.range(1, 10)
.sum(); // More efficient than Stream<Integer>
Summary
Java 8 introduced revolutionary features:
✅ Lambda Expressions - Concise functional code ✅ Stream API - Declarative data processing ✅ Optional - Better null handling ✅ Method References - Cleaner code ✅ Default Methods - Interface evolution ✅ Date/Time API - Modern date handling
Key Benefits
- Readability: More expressive code
- Maintainability: Less boilerplate
- Performance: Parallel processing
- Safety: Better null handling
Next Steps
- Practice lambda expressions
- Master Stream API operations
- Use Optional in your code
- Explore parallel streams
- Learn advanced collectors
Remember: Java 8 is the foundation of modern Java. Master these features to write clean, efficient, and maintainable code! 🚀