Functional programming in Java combines object-oriented principles with functional concepts, enabling more concise and maintainable code.
A functional interface is the cornerstone of functional programming in Java. It's an interface with exactly one abstract method, making it perfect for lambda expressions and method references.
// Example of custom functional interface
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
// Using built-in functional interfaces
Function length = str -> str.length();
Predicate isEven = num -> num % 2 == 0;
Consumer printer = System.out::println;
Supplier random = Math::random;
// Practical example
public class Calculator {
public static int calculate(int a, int b, MathOperation operation) {
return operation.operate(a, b);
}
public static void main(String[] args) {
// Using lambda expressions with functional interface
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;
System.out.println(calculate(5, 3, add)); // Output: 8
System.out.println(calculate(5, 3, multiply)); // Output: 15
}
}
Method references provide a shorthand notation for lambda expressions that call a single method, making the code more readable and concise.
// Examples of different method references
public class MethodReferenceDemo {
// Static method reference
List numbers = Arrays.asList("1", "2", "3");
List parsed = numbers.stream()
.map(Integer::parseInt) // Static method reference
.collect(Collectors.toList());
// Instance method reference
String prefix = "Mr. ";
List names = Arrays.asList("John", "James");
names.stream()
.map(prefix::concat) // Instance method reference
.forEach(System.out::println); // Another method reference
// Constructor reference
List strings = Arrays.asList("apple", "banana");
List builders = strings.stream()
.map(StringBuilder::new) // Constructor reference
.collect(Collectors.toList());
// Arbitrary object method reference
String[] words = {"hello", "world"};
Arrays.sort(words, String::compareToIgnoreCase);
}
Pure functions are fundamental to functional programming. They always produce the same output for the same input and have no side effects, making code more predictable and easier to test.
// Examples of pure vs impure functions
public class PureFunctionDemo {
// Impure function - depends on external state
private int multiplier = 2;
public int impureMultiply(int number) {
return number * multiplier; // Depends on class field
}
// Pure function - only depends on input
public static int pureMultiply(int number, int multiplier) {
return number * multiplier;
}
// Pure function example with collections
public static List pureFilter(List numbers, int threshold) {
return numbers.stream()
.filter(n -> n > threshold)
.collect(Collectors.toList());
}
// Pure function for string manipulation
public static String pureConcatenate(String str1, String str2) {
return str1 + str2;
}
}
Immutability is a core principle in functional programming where objects, once created, cannot be modified. This leads to more predictable code and eliminates many concurrency issues.
// Example of an immutable class
public final class ImmutablePerson {
private final String name;
private final int age;
private final List hobbies;
public ImmutablePerson(String name, int age, List hobbies) {
this.name = name;
this.age = age;
// Deep copy of mutable object
this.hobbies = new ArrayList<>(hobbies);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List getHobbies() {
// Return copy to prevent modification
return new ArrayList<>(hobbies);
}
// Creating new instance instead of modifying
public ImmutablePerson withAge(int newAge) {
return new ImmutablePerson(this.name, newAge, this.hobbies);
}
}
// Using immutable collections
public class ImmutableCollectionsDemo {
public static void main(String[] args) {
List mutableList = new ArrayList<>();
mutableList.add("Java");
// Creating immutable collections
List immutableList = List.of("Java", "Python");
Set immutableSet = Set.of(1, 2, 3);
Map immutableMap = Map.of(
"One", 1,
"Two", 2
);
}
}
Functional composition is the process of combining multiple functions to create more complex operations. This technique enables building complex behavior from simple, reusable functions.
// Examples of functional composition
public class FunctionalCompositionDemo {
public static void main(String[] args) {
// Function composition
Function multiply = x -> x * 2;
Function add = x -> x + 3;
// Combining functions
Function multiplyThenAdd = multiply.andThen(add);
Function addThenMultiply = multiply.compose(add);
System.out.println(multiplyThenAdd.apply(5)); // (5 * 2) + 3 = 13
System.out.println(addThenMultiply.apply(5)); // (5 + 3) * 2 = 16
// Predicate composition
Predicate isLongerThan5 = s -> s.length() > 5;
Predicate containsJava = s -> s.contains("Java");
// Combining predicates
Predicate isLongAndContainsJava = isLongerThan5.and(containsJava);
// Stream composition
List programmingLanguages = Arrays.asList(
"Java", "Python", "JavaScript", "Kotlin"
);
List result = programmingLanguages.stream()
.filter(isLongAndContainsJava)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// Optional composition
Optional optional = Optional.of("hello")
.map(String::toUpperCase)
.filter(s -> s.length() > 3)
.map(s -> s + " World");
}
}