# Java 8 Features — Lambdas, Method References, Streams and Optional

Source: https://skills101.in/learn/java/java-8-lambdas-streams — Skills101 free tutorial (Java Programming). Updated 2026-11-09.

Java 8 changed how Java code looks. Lambdas and streams let you describe **what** you want from a collection instead of writing loops — and they come up in almost every Java interview.

## 1. Lambda expressions

A **lambda** is a short anonymous function: `(parameters) -> expression`. It implements a **functional interface** (one abstract method).

```java
import java.util.*;
import java.util.function.*;

public class Main {
    public static void main(String[] args) {
        Function<Integer, Integer> square = x -> x * x;
        Predicate<String> isLong = s -> s.length() > 4;
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        Supplier<String> hello = () -> "hello";
        Consumer<String> shout = s -> System.out.println(s.toUpperCase());

        System.out.println(square.apply(6) + " " + isLong.test("Java") + " " + add.apply(2, 3) + " " + hello.get());
        shout.accept("lambdas");
    }
}
```

```text
36 false 5 hello
LAMBDAS
```

| Interface | Takes → gives | Method |
|---|---|---|
| `Function<T, R>` | T → R | `apply` |
| `Predicate<T>` | T → boolean | `test` |
| `Consumer<T>` | T → nothing | `accept` |
| `Supplier<T>` | nothing → T | `get` |

## 2. Method references

When a lambda just calls one method, write `Class::method`:

- `s -> s.toUpperCase()` → `String::toUpperCase`
- `x -> System.out.println(x)` → `System.out::println`

## 3. Streams

![A stream pipeline: a source list flows through filter (keep some) and map (transform) into collect (make the result). Nothing runs until the last step](https://skills101.in/learn-img/java/stream-pipeline.svg)

A stream is a **pipeline**: a **source** → zero or more **intermediate** operations (`filter`, `map`, `sorted`, `distinct`, `limit`) → one **terminal** operation (`collect`, `forEach`, `count`, `reduce`, `sum`). Streams **don't change** the original collection, and nothing runs until the terminal operation (lazy).

```java
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("asha", "ravi", "neha", "kabir", "om");
        List<String> result = names.stream()
            .filter(n -> n.length() > 3)
            .map(String::toUpperCase)
            .sorted()
            .collect(Collectors.toList());
        System.out.println(result);

        List<Integer> nums = List.of(4, 8, 15, 16, 23, 42);
        int sumOfEvens = nums.stream().filter(n -> n % 2 == 0).mapToInt(Integer::intValue).sum();
        long countBig = nums.stream().filter(n -> n > 10).count();
        int product = nums.stream().limit(3).reduce(1, (a, b) -> a * b);
        System.out.println(sumOfEvens + " " + countBig + " " + product);
    }
}
```

```text
[ASHA, KABIR, NEHA, RAVI]
70 4 480
```

### Grouping and statistics

```java
import java.util.*;
import java.util.stream.*;

record Student(String name, String city, int marks) {}

public class Main {
    public static void main(String[] args) {
        List<Student> list = List.of(
            new Student("Asha", "Jaipur", 92), new Student("Ravi", "Pune", 74),
            new Student("Neha", "Jaipur", 85), new Student("Om", "Pune", 66));

        Map<String, List<String>> byCity = list.stream()
            .collect(Collectors.groupingBy(Student::city, TreeMap::new,
                     Collectors.mapping(Student::name, Collectors.toList())));
        System.out.println(byCity);

        double avg = list.stream().mapToInt(Student::marks).average().orElse(0);
        String topper = list.stream().max(Comparator.comparingInt(Student::marks)).map(Student::name).orElse("none");
        System.out.println(avg + " " + topper);
    }
}
```

```text
{Jaipur=[Asha, Neha], Pune=[Ravi, Om]}
79.25 Asha
```

(`record` — Java 16+ — is a short way to write a class that just holds data.)

## 4. Optional: no more surprise nulls

`Optional<T>` is a box that **may or may not** contain a value. It forces you to think about the empty case.

```java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Optional<String> found = List.of("java", "python").stream().filter(s -> s.startsWith("p")).findFirst();
        Optional<String> missing = List.of("java").stream().filter(s -> s.startsWith("z")).findFirst();
        System.out.println(found.orElse("none") + " " + missing.orElse("none") + " " + missing.isPresent());
        System.out.println(found.map(String::length).orElse(0));
    }
}
```

```text
python none false
6
```

## Common mistakes

- Reusing a stream after a terminal operation (`IllegalStateException`) — create a new one.
- Writing long, unreadable stream chains; a loop can be clearer.
- Calling `optional.get()` without checking — use `orElse`, `orElseThrow`, `map`.

## Quick recap

- Lambda: `(x) -> …`, implements a functional interface (Function, Predicate, Consumer, Supplier).
- Method references: `Class::method`.
- Stream = source → intermediate ops (lazy) → one terminal op.
- `groupingBy`, `mapToInt().sum()/average()`, `reduce`, `Optional`.


Practice quiz and free certificate: https://skills101.in/learn/java/java-8-lambdas-streams