Basics of Functional Programming
1. What Is Functional Style?
-
Imperative style: Describes how to perform a task using loops, variables, and conditions.
-
Functional style: Describes what should happen using streams, lambdas, and operations like
filter()andmap().
Java's Stream API makes it easier to refactor many common loops into a more declarative style.
2. Common Refactorings
| Imperative | Functional |
|---|---|
for loop |
IntStream.range() |
for loop with custom step |
IntStream.iterate() + takeWhile() |
foreach + if |
filter() |
| Loop + transformation | map() |
| Read file line by line | Files.lines() |
3. Simple for Loop → IntStream.range()
Imperative
for (int i = 0; i < n; i++) {
// action
}
Functional
IntStream.range(0, n)
.forEach(i -> {
// action
});
range(0, n) generates:
0, 1, 2, ..., n - 1
For an inclusive upper limit, use rangeClosed():
IntStream.rangeClosed(1, 5)
.forEachprintln;
Output:
1
2
3
4
5
4. Loop with a Custom Step
Imperative
for (int i = 0; i < n; i += step) {
// action
}
Functional
IntStream.iterate(0, i -> i + step)
.takeWhile(i -> i < n)
.forEach(i -> {
// action
});
For example:
IntStream.iterate(0, i -> i + 2)
.takeWhile(i -> i < 10)
.forEachprintln;
Output:
0
2
4
6
8
-
iterate()generates the values. -
takeWhile()stops when the condition becomes false.
5. foreach + if → filter()
Imperative
for (String s : list) {
if (s.length() > 3) {
// action
}
}
Functional
list.stream()
.filter(s -> s.length() > 3)
.forEach(s -> {
// action
});
Think of filter() as:
"Keep only the elements that satisfy this condition."
6. Loop + Transformation → map()
Suppose we want to convert every string into its length.
Imperative
List<Integer> result = new ArrayList<>();
for (String s : list) {
result.add(s.length());
}
Functional
List<Integer> result = list.stream()
.maplength
.collect(Collectors.toList());
Here:
.maplength
means:
String → Integer
For example:
["Java", "Python", "Go"]
↓ map(length)
[4, 6, 2]
7. Reading Files with Streams
Instead of manually reading every line:
Imperative
BufferedReader reader =
new BufferedReader(new FileReader("file.txt"));
String line;
while ((line = reader.readLine()) != null) {
// process line
}
Functional
Files.lines(Paths.get("file.txt"))
.forEach(line -> {
// process line
});
Files.lines() creates a Stream<String> where each element represents a line.
Important: In real code, prefer try-with-resources so the file is properly closed:
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
lines.forEachprintln;
}
8. The Basic Stream Pipeline
Many functional-style operations follow this pattern:
Source
↓
filter()
↓
map()
↓
forEach() / collect()
Example:
List<String> result = list.stream()
.filter(s -> s.length() > 3)
.maptoUpperCase
.collect(Collectors.toList());
This means:
-
Get the elements from
list. -
Keep strings longer than 3 characters.
-
Convert them to uppercase.
-
Collect the result into a list.
Summary
| Task | Imperative | Functional |
|---|---|---|
| Simple iteration | for |
IntStream.range() |
| Inclusive range | for |
IntStream.rangeClosed() |
| Custom step | for (i += step) |
iterate() + takeWhile() |
| Filtering | if |
filter() |
| Transformation | result.add(...) |
map() |
| File processing | while (readLine()) |
Files.lines() |
| Final action | Loop body | forEach() |
| Create collection | Manually add | collect() |
In short
The most important conversions to remember are:
if → filter()
convert → map()
loop → stream()
action → forEach()
collect → collect()
Functional style is especially useful for data processing pipelines, where you can express a sequence of transformations without manually managing loop counters and temporary variables.