In functional programming, immutability is a key concept. An immutable object is one whose state cannot be changed after it is created. In Java, we can create immutable classes by making all fields final
and not providing any setter methods.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Immutable class example
final class ImmutablePerson {
private final String name;
private final int age;
private final List<String> hobbies;
public ImmutablePerson(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getHobbies() {
return hobbies;
}
}
A pure function is a function that, given the same input, will always return the same output and has no side - effects. It does not modify any external state or rely on any mutable state.
// Pure function example
public class PureFunctionExample {
public static int add(int a, int b) {
return a + b;
}
}
Higher - order functions are functions that can take other functions as parameters or return functions as results. In Java, we can achieve this using functional interfaces.
import java.util.function.IntUnaryOperator;
// Higher - order function example
public class HigherOrderFunctionExample {
public static IntUnaryOperator multiplyBy(int factor) {
return num -> num * factor;
}
}
Lambda expressions are a concise way to represent anonymous functions in Java. They can be used wherever a functional interface is expected.
import java.util.Arrays;
import java.util.List;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
}
}
Method references are a more concise way to refer to existing methods. They can be used instead of lambda expressions when the lambda expression just calls an existing method.
import java.util.Arrays;
import java.util.List;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);
}
}
The Stream API introduced in Java 8 allows us to perform various operations on collections in a functional way.
import java.util.Arrays;
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(num -> num % 2 == 0)
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum);
}
}
We can use the filter
method in the Stream API to filter elements from a collection based on a certain condition.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilteringExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> longNames = names.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
System.out.println(longNames);
}
}
The map
method in the Stream API can be used to transform each element of a collection into another form.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MappingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squaredNumbers = numbers.stream()
.map(num -> num * num)
.collect(Collectors.toList());
System.out.println(squaredNumbers);
}
}
The reduce
method in the Stream API can be used to combine all elements of a collection into a single value.
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ReducingExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
sum.ifPresent(System.out::println);
}
}
Stream
API, you may need to handle exceptions in a map
or filter
operation.import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ErrorHandlingExample {
public static void main(String[] args) {
List<String> numbersAsStrings = Arrays.asList("1", "2", "three", "4");
List<Integer> validNumbers = numbersAsStrings.stream()
.map(numStr -> {
try {
return Integer.parseInt(numStr);
} catch (NumberFormatException e) {
return null;
}
})
.filter(num -> num != null)
.collect(Collectors.toList());
System.out.println(validNumbers);
}
}
Functional programming in Java has brought a new way of writing code that is more concise, expressive, and easier to parallelize. By understanding the fundamental concepts such as immutability, pure functions, and higher - order functions, and using features like lambda expressions, method references, and the Stream API, developers can write more maintainable and efficient code. Following best practices in terms of readability, maintainability, and error handling will further enhance the quality of the code.