Skip to main content

Functional Programming With Collections

Functional programming became an important part of modern Java after the introduction of Lambda Expressions and the Streams API in Java 8.

These features allow developers to process collections in a declarative, concise, and expressive way.

Instead of writing loops and temporary variables, developers can describe what they want to do, and the Java runtime handles how it is executed.


Key Concepts of Functional Programming

1. Immutability​

Functional programming prefers immutable data.

Instead of modifying existing data, new data structures are created.

Example:

Streams do not modify the original collection.


2. Declarative Programming​

Instead of writing step‑by‑step logic:

for(Integer n : numbers){
if(n % 2 == 0){
result.add(n);
}
}

We write:

numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

This describes what should happen, not how.


3. Higher Order Functions​

A function that takes another function as input or returns a function.

Example:

list.stream().map(x -> x * 2)

The map() method receives a function (lambda).


4. Lazy Evaluation​

Intermediate stream operations do not execute immediately.

They only execute when a terminal operation is called.

Example:

stream.filter(...).map(...).collect(...)

Execution starts only when collect() runs.


Functional Programming with Streams

The Streams API provides operations for processing collections.

1. Filtering​

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterExample {

public static void main(String[] args){

List<Integer> numbers =
Arrays.asList(1,2,3,4,5);

List<Integer> evens =
numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

System.out.println(evens);
}
}

Output

[2,4]


2. Mapping​

Mapping transforms each element.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamMapExample {

public static void main(String[] args){

List<String> fruits =
Arrays.asList("Apple","Banana","Cherry");

List<String> upper =
fruits.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

System.out.println(upper);
}
}

3. Reducing​

Reducing combines elements into a single value.

import java.util.Arrays;

public class StreamReduceExample {

public static void main(String[] args){

int sum =
Arrays.stream(new int[]{1,2,3,4,5})
.reduce(0,Integer::sum);

System.out.println(sum);
}
}

Output

15


4. Sorting​

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamSortExample {

public static void main(String[] args){

List<String> fruits =
Arrays.asList("Banana","Apple","Cherry");

List<String> sorted =
fruits.stream()
.sorted()
.collect(Collectors.toList());

System.out.println(sorted);
}
}

Output

[Apple, Banana, Cherry]


5. Grouping​

Grouping organizes data by a property.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamGroupExample {

public static void main(String[] args){

List<String> words =
Arrays.asList("apple","banana","blueberry","avocado");

Map<Character,List<String>> grouped =
words.stream()
.collect(Collectors.groupingBy(w -> w.charAt(0)));

System.out.println(grouped);
}
}

Method References

Method references simplify lambda expressions.

Syntax​

ClassName::method object::method ClassName::new

Example

fruits.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

This replaces:

.map(s -> s.toUpperCase())

Parallel Streams

Parallel streams process data using multiple threads.

import java.util.stream.IntStream;

public class ParallelStreamExample {

public static void main(String[] args){

int sum =
IntStream.range(1,1_000_000)
.parallel()
.sum();

System.out.println(sum);
}
}

Parallel streams are useful for:

  • CPU intensive tasks
  • Large datasets

But may not help small datasets.


Best Practices

1. Use Streams for Complex Operations​

Streams are excellent for:

  • filtering
  • mapping
  • grouping
  • aggregation

2. Avoid Side Effects​

Lambda expressions should avoid modifying external variables.

Bad example:

list.stream().forEach(x -> total += x);

3. Prefer Method References​

Use them to improve readability.


4. Benchmark Parallel Streams​

Parallel processing is not always faster.

Always test performance.


When to Use Functional Programming

Use functional programming when:

  • Processing collections
  • Performing transformations
  • Writing concise declarative code

Avoid when:

  • Logic is extremely simple
  • Imperative loops are clearer

Functional vs Imperative Programming

FeatureFunctionalImperative
StyleDeclarativeStep‑by‑step
ReadabilityConciseExplicit
ParallelismBuilt‑inManual
Best UseTransformationsSimple loops

Functional programming combined with Java collections enables cleaner, safer, and more expressive code in modern Java applications.