Imagine you're working on a complex puzzle. There are two ways to solve it:
Functional programming isn't just another programming style - it's a way of thinking that makes your code more predictable, testable, and often, more readable. In this article, we'll break down functional programming concepts in a way that will make you say, "Ah, now I get it!"
Let's break down the core concepts that separate functional programming from traditional imperative (or "primitive") programming.
In functional programming, pure functions are like vending machines. Given the same input (money and selection), they always return the same output (specific snack). They don't:
// Impure function - Traditional approach
class Calculator {
// This variable can be changed by any method, making it unpredictable
private int runningTotal = 0;
// Impure method - it changes the state of runningTotal
public int addToTotal(int number) {
runningTotal += number; // Modifying external state
return runningTotal;
}
}
// Pure function - Functional approach
class BetterCalculator {
// Pure method - only works with input parameters
// Same inputs will ALWAYS give same outputs
public int add(int first, int second) {
return first + second;
}
}
// Usage example:
Calculator calc = new Calculator();
System.out.println(calc.addToTotal(5)); // Output: 5
System.out.println(calc.addToTotal(5)); // Output: 10 (state changed!)
BetterCalculator betterCalc = new BetterCalculator();
System.out.println(betterCalc.add(5, 5)); // Always outputs: 10
System.out.println(betterCalc.add(5, 5)); // Always outputs: 10
In traditional programming, we often modify data directly. In functional programming, we treat data as immutable - once created, it cannot be changed. Instead of modifying existing data, we create new data with our desired changes.
// Traditional approach - Mutable List
public class MutableExample {
public static void main(String[] args) {
// Creating a mutable list
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
// Modifying the original list - This can lead to unexpected behaviors
fruits.add("Orange");
System.out.println(fruits); // [Apple, Banana, Orange]
}
}
// Functional approach - Immutable List
public class ImmutableExample {
public static void main(String[] args) {
// Creating an immutable list
List<String> fruits = List.of("Apple", "Banana");
// Instead of modifying, we create a new list
List<String> newFruits = new ArrayList<>(fruits);
newFruits.add("Orange");
// Original list remains unchanged
System.out.println("Original: " + fruits); // [Apple, Banana]
System.out.println("New List: " + newFruits); // [Apple, Banana, Orange]
}
}
Traditional programming often focuses on how to do something (step-by-step instructions). Functional programming focuses on what we want to achieve.
public class NumberProcessing {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// Traditional approach (imperative) - Focusing on HOW
List<Integer> evenNumbersImperative = new ArrayList<>();
// Step by step instructions
for (Integer number : numbers) {
if (number % 2 == 0) {
evenNumbersImperative.add(number);
}
}
// Functional approach (declarative) - Focusing on WHAT
List<Integer> evenNumbersFunctional = numbers.stream()
// Just specify what we want: numbers that are even
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println("Imperative Result: " + evenNumbersImperative);
System.out.println("Functional Result: " + evenNumbersFunctional);
}
}