Java 8 introduced functional programming, which helped developers simplify and clean code. Developers new to Java or used to using Java older than Java Development Kit(JDK) version 8 should learn functional programming.
Functional Interfaces and Lambda Expressions
Java 8 introduced functional interfaces, and the FunctionalInterface annotation creates an interface that receives a parameter. The developer can manipulate processes inside the function’s operation.
@FunctionalInterface
interface Operation {
int apply(int a, int b);
}
public class FunctionalProgramming {
public static void main(String[] args) {
Operation addition = (a, b) -> a + b;
Operation subtraction = (a, b) -> a - b;
System.out.println("Addition: " + addition.apply(5, 3));
System.out.println("Subtraction: " + subtraction.apply(5, 3));
}
}The operation interface has an “apply” method with two parameters.
Addition: 8 Subtraction: 2
The developer can add default methods or static methods inside the operation interface.
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
default int add(int a, int b) {
return a + b;
}
default int subtract(int a, int b) {
return a - b;
}
// Static method
static void greet() {
System.out.println("Welcome to Math Operations!");
}
}
public class FunctionalProgramming {
public static void main(String[] args) {
// Lambda expression implementing the operate method
MathOperation multiplication = (a, b) -> a * b;
System.out.println("Multiplication: " + multiplication.operate(5, 3));
// Accessing default methods
MathOperation operation = (a, b) -> a + b;
System.out.println("Addition: " + operation.add(5, 3));
System.out.println("Subtraction: " + operation.subtract(5, 3));
// Calling static method without an instance
MathOperation.greet();
}
}The MathOperation interface has an “operate” method with two parameters: a default “add” and “subtract” method, and a static “greet” method.
Multiplication: 15 Addition: 8 Subtraction: 2 Welcome to Math Operations!
Method References
Allow the developer to call the constructor method without invoking the class. There are four types of method references.
- Static method reference
- Instance method reference
- Constructor reference
- Arbitrary object method reference
Static method reference
import java.util.function.IntBinaryOperator;
class MathUtils {
// Static method to add two numbers
static int add(int a, int b) {
return a + b;
}
}
public class MethodReference {
public static void main(String[] args) {
// Using a static method reference
IntBinaryOperator adder = MathUtils::add;
int result = adder.applyAsInt(5, 3);
System.out.println("Result of addition: " + result);
}
}Result of addition: 8
Instance method reference
import java.util.function.BiFunction;
class Calculator {
// Instance method to multiply numbers
int multiply(int a) {
return a*a;
}
}
public class MethodReference {
public static void main(String[] args) {
MethodReference m = new MethodReference();
m.instanceMethod();
}
public void instanceMethod(){
// Creating an instance of the Calculator class
Calculator calculator = new Calculator();
// Using an instance method reference
BiFunction<Calculator, Integer, Integer> multiplier = Calculator::multiply;
// Calling the instance method reference with the specified arguments
int result = multiplier.apply(calculator, 5);
System.out.println("Result of multiplication: " + result);
}
}Result of multiplication: 25
Constructor reference
Constructor references provide a concise and readable way to refer to constructors when using functional interfaces, enhancing code clarity and reducing verbosity.
import java.util.function.Function;
class Person {
private String name;
// Constructor
public Person(String name) {
this.name = name;
}
// Getter
public String getName() {
return name;
}
}
public class MethodReference {
public static void main(String[] args) {
// Using constructor reference
Function<String, Person> personCreator = Person::new;
// Creating a new Person instance using the constructor reference
Person person = personCreator.apply("John Doe");
// Accessing the name using the getter method
System.out.println("Person's name: " + person.getName());
}
}The Person class contains the constructor method. The developer can create a new class instance by “Person::new” and call the constructor with “personCreator.apply(“John Doe”) that creates a new person with the name “John Doe”.
Person's name: John Doe
Arbitrary object method reference
Arbitrary object method references provide a convenient way to refer to instance methods on specific objects when using functional interfaces, making the code more readable and expressive.
import java.util.function.Function;
class StringUtils {
// Instance method to convert a string to uppercase
String toUpperCase(String str) {
return str.toUpperCase();
}
}
public class MethodReference {
public static void main(String[] args) {
// Creating an instance of StringUtils
StringUtils stringUtils = new StringUtils();
// Using an arbitrary object method reference
Function<String, String> stringConverter = stringUtils::toUpperCase;
// Calling the instance method using method reference
String result = stringConverter.apply("hello");
System.out.println("Result: " + result);
}
}Result: HELLO
Custom function: The developer can use a method reference for any parameter.
@FunctionalInterface
interface TriFunction<A, B, C, D, R> {
R apply(A a, B b, C c, D d);
}
class Calculator {
// Instance method to perform a calculation with four parameters
public int calculate(int a, int b, int c) {
return a + b +c;
}
}
public class MethodReference {
public static void main(String[] args) {
// Creating an instance of Calculator
Calculator calculator = new Calculator();
// Using an arbitrary object method reference with four parameters
TriFunction<Calculator, Integer, Integer, Integer, Integer> calculatorFunction = Calculator::calculate;
// Calling the instance method using method reference
int result = calculatorFunction.apply(calculator, 5, 3, 8);
System.out.println("Result of calculation: " + result);
}
}The developer creates a custom function interface, “TriFunction”, to receive three parameters, as shown in an example.
Streams and Functional Programming
Streams in Java provide a fluent API for processing sequences of elements. They allow you to perform aggregate operations on collections, such as filtering, mapping, and reducing.
Stream: print all words in uppercase
public static void main(String[] args) {
List<String> words = Arrays.asList("hello", "world", "java");
// Using streams to convert words to uppercase and print them
words.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
//Not functional
for(String s : words){
System.out.println(s.toUpperCase());
}
}HELLO WORLD JAVA
Stream: Manipulate values inside the list using Java Streams
public void manipulate(){
// Create a list of integers
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// Manipulate values inside the list using Java Streams
List<Integer> squaredNumbers = numbers.stream()
.map(num -> num * num)
.collect(Collectors.toList());
// Printing the squared numbers
System.out.println("Squared numbers: " + squaredNumbers);
//Not functional programming
squaredNumbers = new ArrayList<>();
for(int i = 0 ; i < numbers.size() ; i++){
squaredNumbers.add(numbers.get(i) * numbers.get(i));
}
System.out.println("Squared numbers: " + squaredNumbers);
}Squared numbers: [1, 4, 9, 16, 25]
Stream: Manipulate values inside the list using Java Streams and method reference
public class FuncStream {
public void reference() {
// Create a list of integers
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// Manipulate values inside the list using Java Streams and method reference
List<Integer> doubledNumbers = numbers.stream()
.map(FuncStream::doubleNumber)
.collect(Collectors.toList());
// Printing the doubled numbers
System.out.println("Doubled numbers: " + doubledNumbers);
}
// Method to double a number
private static int doubleNumber(int num) {
return num * 2;
}
}Doubled numbers: [2, 4, 6, 8, 10]
Stream finds the first match
Using findFirst() For Java stream, find the first match.
findFirst()Retrieves the first element in the stream that matches the given condition.- Returns an
Optionalelement containing it if found, or an emptyOptionalelement if no match is found.
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FindFirstExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Find the first name starting with 'C'
Optional<String> firstMatch = names.stream()
.filter(name -> name.startsWith("C"))
.findFirst();
// Print the result
firstMatch.ifPresentOrElse(
match -> System.out.println("Found: " + match),
() -> System.out.println("No match found")
);
}
}Found: Charlie
Filter: filter even numbers.
public void filter(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Using stream and filter to get even numbers
List<Integer> evenNumbers = numbers.stream()
.filter(num -> num % 2 == 0)
.collect(Collectors.toList());
// Printing the even numbers
System.out.println("Even numbers: " + evenNumbers);
//Not functional
evenNumbers = new ArrayList<>();
for(int num : numbers){
if(num % 2 == 0){
evenNumbers.add(num);
}
}
System.out.println("Even numbers: " + evenNumbers);
}Even numbers: [2, 4, 6, 8, 10]
The result is the same, but the code looks more elegant and clean.
Predicate: find the name letter starting with “A”.
public void predicate(){
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve", "Alan");
// Create a Predicate to filter names starting with 'A'
Predicate<String> startsWithA = name -> name.startsWith("A");
// Filter names based on the Predicate
List<String> filteredNames = names.stream()
.filter(startsWithA)
.collect(Collectors.toList());
// Print filtered names
System.out.println("Names starting with 'A': " + filteredNames);
}Names starting with 'A': [Alice, Alan]
Predicate: Filter numbers greater than 1000 using Java Streams and Predicate
public void predicateNumbers(){
// Create a list of integers
List<Integer> numbers = new ArrayList<>(Arrays.asList(500, 1200, 800, 1500, 2000));
// Define a Predicate to check if a number is greater than 1000
Predicate<Integer> greaterThan1000 = num -> num > 1000;
// Filter numbers greater than 1000 using Java Streams and Predicate
List<Integer> filteredNumbers = numbers.stream()
.filter(greaterThan1000)
.collect(Collectors.toList());
// Printing the filtered numbers
System.out.println("Numbers greater than 1000: " + filteredNumbers);
}Numbers greater than 1000: [1200, 1500, 2000]
Reduce summary numbers in an ArrayList.
public void reduce(){
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Using stream and reduce to find the sum of all numbers
int sum = numbers.stream()
.reduce(0, (subtotal, num) -> subtotal + num);
// Printing the sum
System.out.println("Sum of all numbers: " + sum);
//Not functional
sum = 0;
for(int num : numbers){
sum += num;
}
System.out.println("Sum of all numbers: " + sum);
}Sum of all numbers: 15
Reduce: find the maximum number in an array list.
public void max(){
List<Integer> numbers = Arrays.asList(7, 2, 9, 4, 5);
// Using stream and reduce to find the maximum number
int max = numbers.stream()
.reduce(Integer.MIN_VALUE, Integer::max);
// Printing the maximum number
System.out.println("Maximum number: " + max);
//Not functional
max = Integer.MIN_VALUE;
for(int num : numbers){
max = Math.max(num, max);
}
System.out.println("Maximum number: " + max);
}Maximum number: 9
Reduce: concatenate the string.
public static void main(String[] args) {
// Create a list of strings
List<String> words = new ArrayList<>(Arrays.asList("Hello", "world", "Java", "Streams"));
// Concatenate strings using Java Streams and reduce
String concatenatedString = words.stream()
.reduce("", (str1, str2) -> str1 + str2);
// Printing the concatenated string
System.out.println("Concatenated string: " + concatenatedString);
}Concatenated string: HelloworldJavaStreams
Optional and Functional Error Handling.
Java’s Optional class provides a way to express the absence of a value instead of using null references.
public void optional(){
divide(4, 2).ifPresent(result -> System.out.println("Result: " + result));
divide(4, 0).ifPresentOrElse(
result -> System.out.println("Result: " + result),
() -> System.out.println("Division by zero!")
);
}
public static Optional<Double> divide(int a, int b) {
if (b == 0) {
return Optional.empty();
}
return Optional.of((double) a / b);
}Result: 2.0 Division by zero!
Finally
Functional programming represents a contemporary approach to Java coding. Developers should familiarize themselves with its principles to enhance coding efficiency and maintain code cleanliness.
This article was originally published on Medium.



