Functional Programming
Lambdas, method references, functional interfaces, Stream API, Optional
Java 8 introduced functional programming constructs — lambdas, method references, functional interfaces, the Stream API, and Optional. These features allow concise, declarative data processing and form the foundation of modern Java code. Understanding how they work under the hood (and their performance characteristics) is essential.
Key Points
- Lambda: (params) -> expression — anonymous function that captures effectively final variables from the enclosing scope
- Method reference types: ClassName::staticMethod, instance::method, ClassName::instanceMethod, ClassName::new
- @FunctionalInterface: interface with exactly one abstract method — Predicate<T>, Function<T,R>, Supplier<T>, Consumer<T>, BiFunction<T,U,R>
- Stream pipeline: source → zero or more intermediate ops (lazy) → one terminal op (triggers evaluation)
- Intermediate ops: filter, map, flatMap, sorted, distinct, limit, skip, peek — all lazy (no work until terminal)
- Terminal ops: forEach, collect, reduce, count, findFirst, anyMatch, allMatch, min, max — trigger evaluation
- Collectors.toList(), groupingBy(), partitioningBy(), joining(), counting(), summarizingInt()
- Optional<T>: container that may or may not hold a value — use map/flatMap/orElse/orElseThrow instead of isPresent()+get()
- Parallel streams: stream().parallel() — use only for CPU-bound operations on large datasets; overhead kills small lists
- ★ Interview Q1 — Frequency count / find duplicates: Collectors.groupingBy(n -> n, counting()) then filter count > 1
- ★ Interview Q2 — First non-repeating character: groupingBy into LinkedHashMap to preserve insertion order, filter count == 1, findFirst()
- ★ Interview Q3 — Second highest salary: map to salary, distinct(), sorted(reverseOrder()), skip(1), findFirst()
- ★ Interview Q4 — Department-wise aggregation: Collectors.groupingBy(dept) + maxBy / averagingDouble / counting()
- ★ Interview Q5 — Group anagrams: groupingBy on sorted-char key — words with same letters share the same key
Functional Java: foundations + Top 5 Interview Programs — frequency/duplicates, first non-repeating char, second highest salary, department aggregations, anagram grouping
// ─── Foundations ────────────────────────────────────────────────────────────
// Functional interfaces
Predicate<String> notEmpty = s -> !s.isEmpty();
Function<String, Integer> len = String::length;
Supplier<List<String>> newList = ArrayList::new;
Consumer<String> printer = System.out::println;
// Stream pipeline — filter → groupBy
Map<String, Long> countByDept = employees.stream()
.filter(e -> e.salary() > 50_000)
.collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
// flatMap — flatten nested lists
List<String> allWords = sentences.stream()
.flatMap(s -> Arrays.stream(s.split(" ")))
.distinct().sorted().collect(Collectors.toList());
// reduce and Optional chaining
int sum = IntStream.rangeClosed(1, 100).reduce(0, Integer::sum);
Optional<String> city = findUser(id)
.map(User::address).map(Address::city).filter(c -> !c.isBlank());
String result = city.orElse("Unknown");
// ─── Top 5 Interview Programs ────────────────────────────────────────────────
// ① Frequency count + find duplicates in a list
List<Integer> nums = List.of(1, 2, 3, 2, 1, 4, 3, 1);
Map<Integer, Long> freq = nums.stream()
.collect(Collectors.groupingBy(n -> n, Collectors.counting()));
// {1=3, 2=2, 3=2, 4=1}
List<Integer> duplicates = freq.entrySet().stream()
.filter(e -> e.getValue() > 1)
.map(Map.Entry::getKey)
.toList();
// [1, 2, 3]
// ② First non-repeating character in a string
String input = "swiss";
Character firstNonRepeat = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c,
LinkedHashMap::new, // LinkedHashMap preserves insertion order
Collectors.counting()))
.entrySet().stream()
.filter(e -> e.getValue() == 1)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
// 'w'
// ③ Second highest salary
record Employee(String name, String dept, double salary) {}
OptionalDouble secondHighest = employees.stream()
.mapToDouble(Employee::salary)
.distinct()
.boxed()
.sorted(Comparator.reverseOrder())
.skip(1)
.mapToDouble(Double::doubleValue)
.findFirst();
System.out.println(secondHighest.orElse(-1));
// ④ Department-wise aggregations
// 4a — count per department
Map<String, Long> countByDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));
// 4b — highest earner per department
Map<String, Optional<Employee>> topEarner = employees.stream()
.collect(Collectors.groupingBy(
Employee::dept,
Collectors.maxBy(Comparator.comparingDouble(Employee::salary))));
// 4c — average salary per department
Map<String, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
Employee::dept,
Collectors.averagingDouble(Employee::salary)));
// 4d — names per department as a comma-separated string
Map<String, String> namesByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::dept,
Collectors.mapping(Employee::name, Collectors.joining(", "))));
// ⑤ Group anagrams
List<String> words = List.of("eat", "tea", "tan", "ate", "nat", "bat");
Map<String, List<String>> anagramGroups = words.stream()
.collect(Collectors.groupingBy(word -> {
char[] chars = word.toCharArray();
Arrays.sort(chars); // canonical form = sorted characters
return new String(chars);
}));
// {aet=[eat, tea, ate], ant=[tan, nat], abt=[bat]}Real-World Example
Stream pipelines are evaluated lazily — filter().map().findFirst() stops at the first match even on a million-element list. sorted() and distinct() are stateful — they buffer the entire stream before producing output, so avoid them on large parallel streams without limit(). In interviews, the most common mistakes are: forgetting distinct() before skip(1) in second-highest problems, and using HashMap instead of LinkedHashMap for the first-non-repeating character problem (HashMap does not preserve insertion order, so the result is random).