-
Notifications
You must be signed in to change notification settings - Fork 0
Java 8
Release Date: March 14, 2014 | LTS: Yes (Extended Support) | Status: ✅ Still Widely Used
Java 8 transformed Java from a traditional imperative language into a modern functional programming powerhouse. It introduced:
- Lambdas: Concise function syntax
- Functional Interfaces: Single-method interfaces for functional programming
- Streams API: Declarative data processing
- Method References: Elegant function references
- Default Methods: Extend interfaces without breaking code
Before Java 8: After Java 8:
Collections.sort(list, list.stream()
new Comparator<Person>() { .filter(p -> p.getAge() > 21)
@Override .map(Person::getName)
public int compare(Person a, ...) { .sorted()
return a.getAge() - b.getAge(); .collect(toList());
}
});
mindmap
root((Java 8))
Lambdas
Concise anonymous functions
Replace single-method inner classes
Streams API
Declarative data processing
filter · map · reduce · collect
Functional Interfaces
Consumer / Supplier / Function / Predicate
@FunctionalInterface contract
Method References
ClassName::method
Instance::method
Default Methods
Interface evolution without breaking implementers
Optional
Explicit "value may be absent"
Replaces defensive null checks
What: Anonymous functions with a concise syntax
// Traditional Anonymous Class
Comparator<Integer> comp = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a.compareTo(b);
}
};
// Lambda (Java 8+)
Comparator<Integer> comp = (a, b) -> a.compareTo(b);Syntax Rules:
// No parameters
() -> System.out.println("Hello");
// Single parameter (parentheses optional)
x -> x * 2
(x) -> x * 2
// Multiple parameters
(x, y) -> x + y
// Multiple statements
(x, y) -> {
int sum = x + y;
return sum;
}
// Type inference
(String s) -> s.length() // or (s) -> s.length()Real-World Example:
public class LambdaExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Traditional way
List<Integer> evenNumbers = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
evenNumbers.add(num);
}
}
System.out.println("Even: " + evenNumbers); // [2, 4]
// Lambda way
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even: " + evens); // [2, 4]
}
}What: Interfaces with exactly ONE abstract method
// Built-in Functional Interfaces
// Consumer: Accept input, no return
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello"); // Output: Hello
// Supplier: No input, return value
Supplier<String> greeting = () -> "Hello, World!";
System.out.println(greeting.get()); // Hello, World!
// Function: Input → Output
Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25
// Predicate: Input → Boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // trueCustom Functional Interface:
@FunctionalInterface // Compiler ensures only 1 abstract method
public interface Calculator {
int calculate(int a, int b);
default int add(int a, int b) {
return calculate(a, b);
}
}
// Usage
Calculator adder = (a, b) -> a + b;
System.out.println(adder.calculate(5, 3)); // 8What: Functional-style operations on collections
List<Person> people = Arrays.asList(
new Person("Alice", 25, 75000),
new Person("Bob", 30, 85000),
new Person("Charlie", 22, 50000)
);
// Filter → Map → Collect
List<String> highEarners = people.stream()
.filter(p -> p.getSalary() > 60000)
.map(Person::getName)
.sorted()
.collect(Collectors.toList());
System.out.println(highEarners); // [Alice, Bob]flowchart LR
A["List<Person>\n(source)"] -->|"filter(p -> p.salary > 60000)"| B["Stream: high earners only"]
B -->|"map(Person::getName)"| C["Stream<String>\n(names)"]
C -->|"sorted()"| D["Stream<String>\n(alphabetical)"]
D -->|"collect(toList())"| E["List<String>\n(result)"]
style A fill:#e8f4f8,stroke:#0891b2
style E fill:#d4f4dd,stroke:#22863a
Stream Operations:
| Category | Methods | Purpose |
|---|---|---|
| Filtering | filter() |
Keep elements matching condition |
| Mapping |
map(), flatMap()
|
Transform elements |
| Sorting | sorted() |
Order elements |
| Limiting |
limit(), skip()
|
Restrict elements |
| Aggregating | reduce() |
Combine into single value |
| Collecting | collect() |
Gather into collection |
Advanced Stream Examples:
// 1. Count even numbers
int evenCount = numbers.stream()
.filter(n -> n % 2 == 0)
.count(); // Terminal operation
// 2. Find first element matching condition
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
// 3. Group by age
Map<Integer, List<Person>> byAge = people.stream()
.collect(Collectors.groupingBy(Person::getAge));
// 4. Get average salary
double avgSalary = people.stream()
.mapToDouble(Person::getSalary)
.average()
.orElse(0.0);
// 5. Flatten nested lists
List<List<Integer>> matrix = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
List<Integer> flat = matrix.stream()
.flatMap(List::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4]What: Shorthand to call existing methods
// Lambda vs Method Reference
// 1. Static method reference
// Lambda: numbers.forEach(n -> System.out.println(n));
// Reference:
numbers.forEach(System.out::println);
// 2. Instance method reference
// Lambda: people.forEach(p -> print(p));
// Reference:
people.forEach(this::print);
// 3. Constructor reference
// Lambda: names.stream().map(n -> new Person(n)).collect(toList());
// Reference:
names.stream().map(Person::new).collect(toList());
// 4. Sorting with method reference
Collections.sort(people, Comparator.comparing(Person::getAge));
// vs Lambda:
Collections.sort(people, (p1, p2) -> p1.getAge() - p2.getAge());Types:
ClassName::staticMethod // Static method
instance::instanceMethod // Instance method
ClassName::new // Constructor
ClassName::instanceMethod // All instances
What: Provide implementation in interfaces (backward compatibility)
public interface Vehicle {
void drive();
// Default method (has implementation)
default void honk() {
System.out.println("Beep! Beep!");
}
}
public class Car implements Vehicle {
@Override
public void drive() {
System.out.println("Car is driving");
}
// honk() is inherited by default
}
// Usage
Car car = new Car();
car.honk(); // Output: Beep! Beep!What: Represents optional values (prevents NullPointerException)
flowchart TD
A["Optional.ofNullable(getPerson())"] --> B{"Value present?"}
B -->|Yes| C["map / flatMap / filter\nchain runs normally"]
B -->|No| D["Chain short-circuits\n— nothing throws"]
C --> E["ifPresent(...) runs\nor .get() returns the value"]
D --> F["ifPresentOrElse's 2nd branch runs\nor .orElse(default) kicks in"]
style B fill:#fff4e0,stroke:#cc8800
style D fill:#ffe0e0,stroke:#cc3333
style C fill:#d4f4dd,stroke:#22863a
// Before Optional
Person person = getPerson();
if (person != null) {
String name = person.getName();
if (name != null) {
System.out.println(name);
}
}
// With Optional
Optional<Person> person = Optional.ofNullable(getPerson());
person.flatMap(p -> Optional.ofNullable(p.getName()))
.ifPresent(System.out::println);
// Common Optional methods
Optional<String> name = Optional.of("Alice");
name.isPresent(); // true
name.get(); // "Alice"
name.orElse("Unknown"); // "Alice"
name.orElseGet(() -> "Guest"); // "Alice"
name.ifPresent(System.out::println); // Alice
name.map(String::length) // Optional(5)
.filter(len -> len > 3) // Optional(5)// 1. Keep lambdas short and readable
list.stream()
.filter(x -> x > 10)
.forEach(System.out::println);
// 2. Use method references when appropriate
list.forEach(System.out::println);
// 3. Use streams for data transformations
List<String> names = people.stream()
.filter(p -> p.getAge() > 21)
.map(Person::getName)
.collect(toList());
// 4. Use Optional instead of null checks
Optional<Person> person = findPerson("Alice");
person.ifPresent(p -> System.out.println(p));
// 5. Use functional interfaces for callbacks
interface EventListener {
void onEvent(Event e);
}
EventListener listener = event -> handleEvent(event);// 1. Don't use complex logic in lambdas
// ❌ Bad
list.stream()
.filter(x -> {
if (x > 10) {
if (x < 100) {
return true;
}
}
return false;
})
// ✅ Good
list.stream()
.filter(x -> x > 10 && x < 100)
// 2. Don't use side effects in streams
// ❌ Bad
list.stream()
.filter(x -> {
System.out.println(x); // Side effect!
return x > 10;
})
// ✅ Good
list.stream()
.filter(x -> x > 10)
.forEach(System.out::println);
// 3. Don't catch exceptions in lambdas without handling
// ❌ Bad
list.stream()
.map(s -> Integer.parseInt(s)) // Can throw NumberFormatException
// ✅ Good
list.stream()
.map(s -> {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return 0;
}
})// Lambda creation is cheap (no new object each time)
Consumer<Integer> consumer = x -> System.out.println(x);
consumer.accept(5); // Reuses same lambda instance
// But streams have overhead for small collections
// Use only for complex transformations on large data
List<Integer> small = Arrays.asList(1, 2, 3);
small.parallelStream() // Overhead > benefit for 3 items
.filter(x -> x > 1)
.forEach(System.out::println);
// Parallel streams beneficial for large datasets
List<Integer> large = IntStream.range(0, 1_000_000)
.boxed()
.collect(toList());
long start = System.currentTimeMillis();
long sum = large.parallelStream()
.mapToLong(x -> x)
.sum();
System.out.println("Time: " + (System.currentTimeMillis() - start)); // Faster!| Tip | Example | Benefit |
|---|---|---|
Use findFirst() instead of filter().collect()
|
.filter(x -> x > 100).findFirst() |
Early termination |
Avoid unnecessary sorted()
|
.sorted().limit(10) |
Can be expensive |
| Use primitive streams | IntStream.range(0, 1000) |
Avoids boxing overhead |
| Order operations wisely | Filter before map | Reduce processing |
// ❌ Won't compile - lambda captures effectively final variables
int multiplier = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * multiplier));
multiplier = 3; // ❌ Error: multiplier must be effectively final
// ✅ Use final or effectively final
final int multiplier = 2;
numbers.forEach(n -> System.out.println(n * multiplier));// ❌ Streams are consumed after terminal operation
Stream<Integer> stream = Arrays.asList(1, 2, 3).stream();
stream.filter(x -> x > 1).forEach(System.out::println);
stream.filter(x -> x < 3).forEach(System.out::println); // ❌ Error: stream already consumed
// ✅ Create new stream
List<Integer> list = Arrays.asList(1, 2, 3);
list.stream().filter(x -> x > 1).forEach(System.out::println);
list.stream().filter(x -> x < 3).forEach(System.out::println);// ❌ NPE if any element is null
List<Person> people = Arrays.asList(person1, null, person3);
people.stream()
.map(Person::getName) // NPE when person is null
.forEach(System.out::println);
// ✅ Filter nulls first
people.stream()
.filter(Objects::nonNull)
.map(Person::getName)
.forEach(System.out::println);import java.util.*;
import java.util.stream.*;
public class WordFrequencyAnalyzer {
public static void main(String[] args) {
String text = "Java is great Java is powerful Functional programming is Java";
// Split into words and count frequency
Map<String, Long> wordFreq = Arrays.stream(text.toLowerCase().split("\\s+"))
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
// Sort by frequency and display
wordFreq.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
// Output:
// java: 3
// is: 3
// great: 1
// powerful: 1
// functional: 1
// programming: 1
}
}✅ Lambdas make code more readable and concise
✅ Streams API enables functional data processing
✅ Method References provide elegant syntax
✅ Optional helps handle null values safely
✅ Functional Interfaces support functional programming
👉 Master these Java 8 concepts, then explore:
-
Java 11: Type inference with
var - Java 14+: Records and Pattern Matching
- Java 21: Virtual Threads and Structured Concurrency
⬅️ Back to Main Guide | ➡️ Java 9 Guide →
Made with ☕ by Neelu Sahai