These are notes from a course I purchased and followed.
Imperative example (traditional Java) |
Functional example (Java 8 and beyond) |
Comments |
Output |
Every instruction is written explicitly, uses loops and conditions. import java.util.List; public class ImperativeExample { public static int calculateSum( List< Integer > numbers ) { int sum = 0; for( int number : numbers ) if( number % 2 == 0 ) sum += number; return sum; } |
Able to concentrate on what's needed rather than on how to do it. import java.util.List; class FunctionalExample { public static int calculateSum( List< Integer > numbers ) { return numbers.stream() .filter( number -> number % 2 == 0 ) .mapToInt( number -> number ) .sum(); } } |
Step by step—what each does: _ // for each/over the stream of numbers // but, only even numbers, ... // convert number to int // and add to sum! _ |
What is output from both: Test: test ----------------------------- Numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] Test: testCalculateSum ----------------- 30 |
In the interest of full disclosure, my test code looks like this:
import java.util.Arrays; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.runners.MethodSorters; import net.javaguides.ImperativeExample; import com.windofkeltia.utilities.TestUtilities; /** * @author Russell Bateman * @since May 2025 */ @FixMethodOrder( MethodSorters.JVM ) public class xExampleTest { @Rule public TestName name = new TestName(); @After public void tearDown() { System.out.println(); } @Before public void setUp() { TestUtilities.setUp( name, 33 ); } private static final List< Integer > numbers = Arrays.asList( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ); @Test public void test() { System.out.println( "Numbers: " + numbers ); } @Test public void testCalculateSum() { System.out.println( ImperativeExample.calculateSum( numbers ) ); } } |
import org.junit.rules.TestName; public class TestUtilities { public static void setUp( TestName name, int pad ) { String testName = name.getMethodName(); System.out.print( "Test: " + testName + " " ); int nameWidth = testName.length(); pad -= nameWidth; while( pad-- > 0 ) System.out.print( "-" ); System.out.println(); } } |
A pure function in functional programming follows two rules:
That is, if they are defined using Function< ?, ? > function.
In Java functional programming, it's possible to return first-class functions as objects in a variable or in a function return. In this, Java becomes a bit more like C.
First-class functions are created according to this example (which involves a lambda expression). Applying the function is also shown:
Function< Integer, Integer > square = x -> x * x; System.out.println( "The square of 5 is %d.", square.apply( 5 ) );
Just as in C, functions in Java are treated like variables in that they can be assigned to a variable, get passed as arguments to other functions and get returned from other functions.
A higher-order function is one that can take another function as an argument and/or returns a function as its result. Here are two examples:
import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; public class HighOrderFunctionsTest { static void applyFunction( Function< Integer, Integer > function, int value ) { System.out.println( "Result: " + function.apply( value ) ); } static Function< Integer, Integer > createMultiplier( int factor ) { return x -> x * factor; } static List< Integer > applyFunctionToList( List< Integer > numbers, Function< Integer, Integer > function ) { return numbers.stream() .map( function ) // (apply function to each element) .collect( Collectors.toList() ); } static void main( String[] args ) { List< Integer > numbers = Arrays.asList( 1, 2, 3, 4, 5 ); Function< Integer, Integer > doubleValue = x -> x * 2; List< Integer > doubledNumbers = applyFunctionToList( numbers, doubleValue ); System.out.println( "Doubled numbers: " + doubledNumbers ); } }
Asserted advantages of higher-order functions are:
Pure functional programming follows strict rules to ensure that functions are predictable, resusable and maintainable. There must be...
A lambda expression is simply a function without a name, also called an anonymous function. It can be used as an argument to function where a function is expected. Lambda expressions facilitate functional programming and simplify development.
Principally, the purpose of the lambda expression is to provide an implementation for functional interfaces.
Remember that a functional interface is one that contains exactly one abstract method (though it may also contain default and static methods).
The lambda expression is constructed syntactically (lambda expression syntax) as:
parameters linking to lambda body
( arguments ) -> statement
or
( arguments ) -> { statements }
First, the object-oriented way (later to be compared to the functional programming way):
public class ShapeTest { interface Shape { void draw(); } class Rectangle implements Shape { @Override public void draw() { System.out.println( "Rectangle drawn" ); } } class Square implements Shape { @Override public void draw() { System.out.println( "Square drawn" ); } } class Circle implements Shape { @Override public void draw() { System.out.println( "Circle drawn" ); } } Shape rectangle = new Rectangle(); Shape square = new Square(); Shape circle = new Circle(); public static void main( String[] args ) { rectangle.draw(); square.draw(); circle.draw(); } }
The functional-programming way:
public class ShapeTest { interface Shape { void draw(); } // (remember: lambda is arguments (none) -> (one) statement static Shape rectangle = () -> System.out.println( "Rectangle drawn" ); static Shape square = () -> System.out.println( "Square drawn" ); static Shape circle = () -> System.out.println( "Circle drawn" ); public static void main( String[] args ) { rectangle.draw(); square.draw(); circle.draw(); } }
As we transition (the OOP code) to functional programming, we change some things. It's as if we copy down the Java code:
class Rectangle implements Shape1 { @Override2 public3 void4 draw5() { System.out.println( "Rectangle drawn" ); } }
...and convert it over to functional:
public class CalculatorTest { @FunctionalInterface // (unnecessary, but good practice to use) interface Calculator { int calculate( int a, int b ); } static class CalculatorExample { public static void main( String[] args ) { // imagine all the class boilerplate to be created above (missing here) // in support of what's below: // Calculator addition = new Addition; // System.out.println( addition.calculate( 10, 20 ) ); // // Calculator subtraction = new Subtraction; // System.out.println( subtraction.calculate( 20, 10 ) ); // // Calculator multiplication = new Multiplication; // System.out.println( multiplication.calculate( 10, 20 ) ); // // Calculator division = new Division; // System.out.println( division.calculate( 20, 10 ) ); // instead, let's use lambda expressions: Calculator addition = ( a, b ) -> a + b; System.out.println( addition.calculate( 10, 20 ) ); Calculator subtraction = ( a, b ) -> a - b; System.out.println( subtraction.calculate( 20, 10 ) ); Calculator multiplication = ( a, b ) -> a * b; System.out.println( multiplication.calculate( 10, 20 ) ); Calculator division = ( a, b ) -> a / b; System.out.println( division.calculate( 20, 10 ) ); } } }
We're referring to the calculator code above.
public class CalculatorTest { interface Calculator { int calculate( int a, int b ); } static class CalculatorExample { public static void main( String[] args ) { // replace what's above with these passing the lambda as third argument: System.out.println( calculate( 10, 20, ( a, b ) -> a + b ) ); System.out.println( calculate( 20, 10, ( a, b ) -> a - b ) ); System.out.println( calculate( 10, 20, ( a, b ) -> a * b ) ); System.out.println( calculate( 20, 10, ( a, b ) -> a / b ) ); } // but, we have to create this method: private static int calculate( int a, int b, Calculator calculator ) { return calculator.calculate( a, b ); } } }
class ThreadDemo implements Runnable { @Override public void run() { System.out.println( "run() method called..." ); } } public class LambdaThread { public static void main( String[] args ) { ThreadDemo thread = new Thread( new ThreadDemo() ); thread.start(); } }
Eschewing the traditional Java boilerplate, let's do this using lambdas.
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class EmployeeTest { static class Employee { private final int id; private final String name; private final int age; private int salary; Employee( int id, String name, int age, int salary ) { this.id = id; this.name = name; this.age = age; this.salary = salary; } public int getSalary() { return salary; } @Override public String toString() { return "{\n" + " id: " + id + '\n' + " name: " + name + '\n' + " age: " + age + '\n' + " salary: " + salary + '\n' + "}"; } } static class SortEmployeeList { public static void main( String[] args ) { List< Employee > employees = new ArrayList<>(); employees.add( new Employee( 23, "Russell", 70, 50000 ) ); employees.add( new Employee( 1, "Bruce", 62, 80000 ) ); employees.add( new Employee( 2, "Moray", 75, 75000 ) ); System.out.println( "Ascending order by salary:" ); Collections.sort( employees, ( a, b ) -> a.getSalary() - b.getSalary()); for( Employee employee : employees ) System.out.println( employee ); System.out.println( "\nDescending order by salary:" ); Collections.sort( employees, ( a, b ) -> b.getSalary() - a.getSalary()); for( Employee employee : employees ) System.out.println( employee ); } } }
Functional interfaces follow this pattern:
@FunctionalInterface interface MyFunctionalInterface { void abstractMethod(); // single, abstract method default void defaultMethod() { System.out.println( "Default method" ); } static void staticMethod() { System.out.println( "Static method" ); } }
Java 8 introduced several built-ins within the java.util.function package. Common ones include:
Let's try it:
package com.windofkeltia.interfaces; @FunctionalInterface interface Printable { void print( String message ); } public class PrintableDemo { public static void main( String[] args ) { // note that because only one parameter, no need for parentheses: Printable printable = message -> System.out.println( message ); printable( "Hello world" ); } }
/** * @parm T type of the argument. * @parm R type of the result. */ interface Function< T, R > { /** Applies this function to the given argument. * @param t the function argument. * @return the function result. */ R apply( T t ); // takes argument type T and returns result type R }
Let's demonstrate. First, an implementation according to traditional Java
(highlighted). We'll use
an anonymous class implementation.
import java.util.function.Function; public class FunctionDemo { public static void main( String[] args ) { Function< String, String > function = new Function<>() { @Override public String apply( String s ) { return s.toUpperCase(); } }; System.out.println( function.apply( "Russell" ) ); } }
Now, let's recode using lambda—reducing the implementation to one line.
import java.util.function.Function; public class FunctionDemo { public static void main( String[] args ) { Function< String, String > uppercase = s -> s.toUpperCase(); System.out.println( uppercase.apply( "Russell" ) ); } }
Both print:
RUSSELL
Let's simply add another function to reverse the string.
import java.util.function.Function; public class FunctionDemo { public static void main( String[] args ) { Function< String, String > uppercase = s -> s.toUpperCase(); System.out.println( uppercase.apply( "Russell" ) ); Function< String, String > reverse = s -> { return new StringBuilder( s ).reverse().toString(); }; System.out.println( reverse.apply( "Russell" ) ); } }
The output is:
RUSSELL llessuR
Note also other variants on the code above, to wit for lines 7 and 11-14:
/* 7: */ Function< String, String > uppercase = String::toUpperCase; /* 11-14: */ Function< String, String > reverse = s -> new StringBuilder( s ).reverse().toString();