Java 8 Functional Programming Tutorial
Java 8 supports functional programming via the lambda expression and Stream API. In this tutorial, I will demonstrate how Java 8 supports functional programming via common pre-defined functional interfaces, Collections, and Stream API.
Table Of Contents
1. Introduction
Java is an object-oriented programming language. Java 8 supports the functional programming style via the lambda expression.
A lambda expression is characterized by the following syntax:
(A list of parameters separated by commas) -> {expression body which contains one or more statements}
A lambda expression can be shortened in two ways because JDK compiler supports type inference.
- Can omit the declaration of the parameter’s type. The compiler can infer it from the parameter’s value.
- Can omit the
returnkeyword if the expression body has a single expression.
Moreover, a lambda expression can be simplified with the following conditions:
- Can omit the parenthesis for a single parameter.
- Can omit the curly brackets if the expression body only contains a single statement.
Functional programming supports a higher-order function (a.k.a. first-class function) which receives a function as an argument or returns a function as a result. The new Stream API supports the higher-order function. The Java 8 Collections class is enhanced to support the higher-order function too.
In this tutorial, I will demonstrate how Java 8 supports functional programming via common pre-defined functional interfaces, Collections, and Stream API.
2. Technologies Used
The example code in this article was built and run using:
- Java 1.8.101
- Maven 3.3.9
- Eclipse Oxygen
- JUnit 4.12
2.1 Maven Project
In this step, I will create a simple Maven project that needs a Junit library.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>zheng.jcg.demo</groupId>
<artifactId>java8-demo</artifactId>
<version>0.0.1-SNAPSHOT</version><dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Predefined Functional Interfaces
Functional Interface (FI) is an interface with only one abstract method that does not override any method from java.lang.Object.
One of the Functional Programming concepts is the pure function. A pure function is a function that it takes an input, and returns an output. It has a single purpose and doesn’t mutate any state; therefore, it has no side effects. It always produces the same output for the same input that is known as referential transparency.
Java 8 provides 40+ common predefined functional interfaces. All of them except the Consumer FI are pure functions.
Java 8 method reference is a shorthand for the lambda expression that executes just one method. Developers can use a lambda expression or method reference to instantiate a FI. Java 8 Stream API utilizes these pre-defined FIs to process stream in a declarative way.
In this step, I will create several JUnit test classes to demonstrate how to use these common functional interfaces.
3.1 Function
A Function FI accepts one argument and returns one result. Its abstract method is called apply(Object).
Java 8 provides several convenient FIs for the primitive data types: IntFunction, DoubleFunction, IntToDoubleFunction, IntToLongFunction, DoubleToIntFunction, DoubleToLongFunction, LongToDoubleFunction, and LongToIntFunction.
A BiFunction FI accepts two arguments and produces a result. Its abstract method is called apply(Object, Object).
Java 8 also provides ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction that accepts two arguments and produces a double-valued, int-valued, and long-valued result.
In this step, I will create a FunctionTest.java class to demonstrate how to:
- Convert an
Integerto aString - Return a string’s length as an
Integer - Combine two functions into a new function
- Convert elements in a list via
Stream–map(Function <T, R>) - Utilize
IntFunction,DoubleFunction, etc
FunctionTest.java
package com.zheng.demo.jdk.fi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import java.util.function.DoubleToIntFunction;
import java.util.function.DoubleToLongFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.LongToDoubleFunction;
import java.util.function.LongToIntFunction;
import java.util.function.ToDoubleBiFunction;
import java.util.function.ToIntBiFunction;
import java.util.function.ToLongBiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import com.zheng.demo.DataUtil;
import com.zheng.demo.model.LoginUser;
public class FunctionTest {
@Test
public void BiFunction_concat_two_String() {
BiFunction<String, String, String> concat = (a, b) -> a + b;
String combinedStr = concat.apply("Today is", " a wonderful day");
assertEquals("Today is a wonderful day", combinedStr);
}
@Test
public void BiFunction_multiple_two_int() {
BiFunction<Integer, Integer, Integer> concat = (a, b) -> a * b;
Integer product = concat.apply(3, 4);
assertEquals(12, product.intValue());
}
@Test
public void DoubleFunction_convertDoubleToString_via_lambda() {
DoubleFunction<String> doubleToString = num -> Double.toString(num);
assertEquals("123.456", doubleToString.apply(123.456));
}
@Test
public void DoubleToIntFunction_convertDoubleToInt_via_lambda() {
DoubleToIntFunction doubleToInt = num -> (int) num;
assertEquals(123, doubleToInt.applyAsInt(123.456));
}
@Test
public void DoubleToLongFunction_convertDoubleToLong_via_lambda() {
DoubleToLongFunction doubleToLongFunc = num -> (long) num;
assertEquals(123789008080l, doubleToLongFunc.applyAsLong(123789008080.456));
}
@Test
public void Function_combine_TwoFunctions() {
Function<LoginUser, String> getUser = LoginUser::getUsertName;
Function<String, String> toUpper = String::toUpperCase;
Function<LoginUser, String> userNameMustBeUppercase = getUser.andThen(toUpper);
assertEquals("MARY", userNameMustBeUppercase.apply( DataUtil.buildLoginUser("Mary", "pwd123")));
}
@Test
public void Function_convertStringToInteger_via_methodReference() {
Function<String, Integer> convertToWordCount = String::length;
List<String> words = Arrays.asList("The", "That", "John", "Thanks");
List<Integer> wordsCounts = words.stream().map(convertToWordCount).collect(Collectors.toList());
assertEquals(3, wordsCounts.get(0).intValue());
assertEquals(4, wordsCounts.get(1).intValue());
assertEquals(4, wordsCounts.get(2).intValue());
assertEquals(6, wordsCounts.get(3).intValue());
}
@Test
public void IntFunction_convertIntegerToString_via_lambda() {
IntFunction<String> intToString = num -> Integer.toString(num);
assertEquals("123", intToString.apply(123));
}
@Test
public void IntFunction_via_lambda() {
IntFunction<Integer> powerValue = num -> num * num;
assertEquals(9, powerValue.apply(3).intValue());
}
@Test
public void IntToDoubleFunction_convertIntToDouble_via_lambda() {
IntToDoubleFunction intToDoubleFunc = num -> (double) num;
assertEquals(123, intToDoubleFunc.applyAsDouble(123), 0.1);
}
@Test
public void IntToLongFunction_convertIntToLong_via_lambda() {
IntToLongFunction intToLongFunc = num -> (long) num;
assertEquals(123456, intToLongFunc.applyAsLong(123456));
}
@Test
public void LongToDoubleFunction_convertLongToDouble_via_lambda() {
LongToDoubleFunction longToDoubleFunc = num -> (double) num;
assertEquals(123456, longToDoubleFunc.applyAsDouble(123456), 0.1);
}
@Test
public void LongToIntFunction_convertLongToInt_via_lambda() {
LongToIntFunction longToIntFun = num -> (int) num;
assertEquals(123456, longToIntFun.applyAsInt(123456));
}
@Test
public void stream_map_via_methodReference() {
Map<String, List<String>> awards = new HashMap<>();
awards.put("Mary", Arrays.asList("Math", "Spelling Bee"));
awards.put("Tom", Arrays.asList("Basketball", "Spelling Bee"));
awards.put("Allen", Arrays.asList("English", "Spelling Bee"));
Function<String, String> convertKeyToUppercase = String::toUpperCase;
List<String> uppercaseKeys = awards.entrySet().stream().map(e -> convertKeyToUppercase.apply(e.getKey()))
.collect(Collectors.toList());
assertTrue(uppercaseKeys.contains("MARY"));
assertTrue(uppercaseKeys.contains("TOM"));
assertTrue(uppercaseKeys.contains("ALLEN"));
}
@Test
public void stream_map_with_lambda() {
List<String> collected = Stream.of("Java", "Rocks").map(string -> string.toUpperCase())
.collect(Collectors.toList());
assertTrue(collected.contains("JAVA"));
assertTrue(collected.contains("ROCKS"));
}
@Test
public void ToDoubleBiFunction_power_two_int() {
ToDoubleBiFunction<Integer, Integer> concat = (a, b) -> Math.pow(a, b);
double powerRet = concat.applyAsDouble(5, 3);
assertEquals(125.0, powerRet, 0.1);
}
@Test
public void ToIntBiFunction_multiple_two_int() {
ToIntBiFunction<Integer, Integer> concat = (a, b) -> a * b;
Integer product = concat.applyAsInt(3, 4);
assertEquals(12, product.intValue());
}
@Test
public void ToLongBiFunction_power_two_int() {
ToLongBiFunction<Integer, Integer> concat = (a, b) -> (long) Math.pow(a, b);
Long powerRet = concat.applyAsLong(5, 3);
assertEquals(125, powerRet.intValue());
}
}
3.2 Predicate
A Predicate FI accepts one argument and returns a Boolean value. Its abstract method is test(Object). A BiPredicate FI accepts two arguments and return a Boolean value. Java 8 also provides IntPredicate, LongPredicate, and DoublePredicate for the primitive data types.
In this step, I will create a PredicateTest.java class to demonstrate how to:
- Check an
Integeris a even number - Filter an element with
Stream–filter(Predicate <T, R>) - Combine two predicates into a new predicate
- Check a
Longis divisible by three - Check a
Doubleis a positive number - Check if the first
Integeris bigger than the second value - Utilize
IntPredicateandDoublePrediate
PredicateTest.java
package com.zheng.demo.jdk.fi;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.function.BiPredicate;
import java.util.function.DoublePredicate;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.Test;
public class PredicateTest {
@Test
public void BiPredicate_whichIsBigger() {
BiPredicate<Integer, Integer> isBigger = (x, y) -> x > y;
assertTrue(isBigger.test(5, 4));
assertTrue(isBigger.negate().test(4, 5));
}
@Test
public void DoublePredicate_test_isPositive() {
DoublePredicate isPositive = x -> x > 0;
assertTrue(isPositive.test(1.5));
assertFalse(isPositive.test(-1.7));
}
@Test
public void IntPredicate_test_isNagative() {
IntPredicate isNagative = x -> x < 0;
assertTrue(isNagative.test(-1));
assertFalse(isNagative.test(1));
}
@Test
public void LongPredicate_test_isDivisibleByThree() {
LongPredicate isDivisibleBy3 = x -> x % 3 == 0;
assertTrue(isDivisibleBy3.test(12));
assertFalse(isDivisibleBy3.test(11));
}
@Test
public void Predicate_combine_two_predicates() {
// takes one argument and return a boolean
Predicate<String> stringIsLongerThanTen = s -> s.length() > 10;
assertTrue(stringIsLongerThanTen.test("This string is longer than 10"));
assertFalse(stringIsLongerThanTen.test("short"));
Predicate<String> stringStartWithA = s -> s.startsWith("A");
assertTrue(stringStartWithA.test("Apple is a fruit"));
Predicate<String> startWithAandLongerThan10 = stringIsLongerThanTen.and(stringStartWithA);
assertTrue(startWithAandLongerThan10.test("Apple is a fruit which grows everywhere."));
}
@Test
public void Predicate_test_integer_isEven() {
Predicate<Integer> isEven = s -> s % 2 == 0;
assertTrue(isEven.test(4));
assertFalse(isEven.test(5));
}
@Test
public void stream_filter_via_lambda() {
Stream.of("Apple", "Pear", "Banana", "Cherry", "Apricot").filter(fruit -> {
System.out.println("filter:" + fruit);
return fruit.startsWith("A");
}).forEach(fruit -> System.out.println("Started with A:" + fruit));
}
}
3.3 Supplier
A Supplier FI accepts no argument and returns a result. Its abstract method is get(). As usual, Java 8 provides convenient interfaces for the primitive data types: IntSupplier, DoubleSupplier, BooleanSupplier, and LongSupplier.
In this step, I will create a SupplierTest.java class to demonstrate how to:
- Return a
Stringvalue - Return a
truevalue - Return the max
Integervalue - Return the max
Longvalue - Return the
pivalue
SupplierTest.java
package com.zheng.demo.jdk.fi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import org.junit.Test;
public class SupplierTest {
@Test
public void BooleanSupplier_getAsBoolean() {
BooleanSupplier booleanSupplier = () -> true;
assertTrue(booleanSupplier.getAsBoolean());
}
@Test
public void DoubleSupplier_getAsDouble() {
DoubleSupplier pi = () -> Math.PI;
assertEquals(3.14, pi.getAsDouble(), 0.01);
}
@Test
public void IntSupplier_getAsInt() {
IntSupplier maxInteger = () -> Integer.MAX_VALUE;
assertEquals(2147483647, maxInteger.getAsInt());
}
@Test
public void LongSupplier_getAsLong() {
LongSupplier maxLongValue = () -> Long.MAX_VALUE;
assertEquals(9223372036854775807l, maxLongValue.getAsLong());
}
@Test
public void Supplier_AString() {
Supplier<String> message = () -> "Mary is fun";
assertEquals("Mary is fun", message.get());
}
}
3.4 Consumer
A Consumer FI accepts a single argument and returns no result. Its abstract method is accept(Object). As usual, Java 8 also provides convenient interfaces for the primitive data types: IntConsumer, LongConsumer, DoubleConsumer, BiConsumer, ObjtIntConsumer, ObjLongConsumer, and ObjDoubleconsumer.
Note: XXConsumer FIs are designed to allow the side-effects.
In this step, I will create a ConsumerTest.java class to demonstrate how to:
- Print out a
Stringafter converting to lowercase - Print out a
String - Print out two strings
- Alter the
Contact‘s age - Calculate a circumference for a circle
ConsumerTest.java
package com.zheng.demo.jdk.fi;
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;
import java.util.function.ObjDoubleConsumer;
import java.util.function.ObjIntConsumer;
import java.util.function.ObjLongConsumer;
import org.junit.Test;
import com.zheng.demo.DataUtil;
import com.zheng.demo.model.Contact;
public class ConsumerTest {
@Test
public void BiConsumer_printout() {
BiConsumer<String, String> echo = (x, y) -> {
System.out.println(x);
System.out.println(y);
};
echo.accept("This is first line.", "Here is another line");
}
@Test
public void Consumer_convertToLowercase_via_lambda() {
Consumer<String> convertToLowercase = s -> System.out.println(s.toLowerCase());
convertToLowercase.accept("This Will convert to all lowercase");
}
@Test
public void Consumer_print_prefix() {
Consumer<String> sayHello = name -> System.out.println("Hello, " + name);
for (String name : Arrays.asList("Mary", "Terry", "John")) {
sayHello.accept(name);
}
}
@Test
public void Consumer_print_via_methodreferce() {
Consumer<String> output = System.out::println;
output.accept("This will be printed out.");
}
@Test
public void DoubleConsumer_printout() {
DoubleConsumer echo = System.out::println;
echo.accept(3.3);
}
@Test
public void IntConsumer_printout() {
IntConsumer echo = System.out::println;
echo.accept(3);
}
@Test
public void LongConsumer_printout() {
LongConsumer echo = System.out::println;
echo.accept(3l);
}
@Test
public void ObjDoubleConsumer_caculate_circle_circumference() {
ObjDoubleConsumer<Double> circleCircumference = (r, p) -> System.out.println("Circumference: " + 2 * r * p);
circleCircumference.accept(new Double(4.0), Math.PI);
}
@Test
public void ObjIntConsumer_alterContactAge() {
ObjIntConsumer<Contact> addThreeYear = (c, a) -> {
c.setAge(c.getAge() + a);
System.out.println("Updated contact" + c);
};
addThreeYear.accept(DataUtil.buildContact("mzheng", "pwd", 40), 3);
}
@Test
public void ObjLongConsumer() {
ObjLongConsumer<String> appendex = (m, l) -> {
System.out.println("Append " + m + l);
};
appendex.accept("test message", 10l);
}
}
3.5 UnaryOperator
A UnaryOperator FI is a specialization of Function whose operand and result are the same type. Its abstract method is apply(Object). As usual, Java 8 provides separated classes for the primitive data types: IntUnaryOperator, DoubleUnaryOperator, and LongUnaryOperator.
In this step, I will create a UnaryOperatorTest.java class to demonstrate how to:
- Convert a
Stringto the uppercase format - Concatenate a
Stringwith a prefix value - Return a
Integerwith a doubled value - Return a
Longwith a squared value - Return a
Doublewith a squared value
UnaryOperatorTest.java
package com.zheng.demo.jdk.fi;
import static org.junit.Assert.assertEquals;
import java.util.function.DoubleUnaryOperator;
import java.util.function.IntUnaryOperator;
import java.util.function.LongUnaryOperator;
import java.util.function.UnaryOperator;
import org.junit.Test;
public class UnaryOperatorTest {
@Test
public void UnaryOperator_convertToUppdercase_via_lamdba() {
UnaryOperator<String> convertToUppercase = msg -> msg.toUpperCase();
String uppString = convertToUppercase.apply("this will be all uppercase");
assertEquals("THIS WILL BE ALL UPPERCASE", uppString);
}
@Test
public void UnaryOperator_concatString_via_methodReference() {
UnaryOperator<String> sayHi = "Hi, "::concat;
String concatString = sayHi.apply("Mary");
assertEquals("Hi, Mary", concatString);
}
@Test
public void IntUnaryOperator_doubleIt() {
IntUnaryOperator doubledIt = x -> x * 2;
assertEquals(24, doubledIt.applyAsInt(12));
}
@Test
public void LongUnaryOperator_squareIt() {
LongUnaryOperator squareIt = x -> x * x;
assertEquals(144, squareIt.applyAsLong(12));
}
@Test
public void DoubleUnaryOperator_squareIt() {
DoubleUnaryOperator squareIt = x -> x * x;
assertEquals(144, squareIt.applyAsDouble(12), 0.1);
}
}
3.6 BinaryOperator
A BinaryOperator FI is a specialization of BiFunction whose operands and result are the same type. Its abstract method is apply(Object). Java 8 provides separated classes for the int, long, and double data type as IntBinaryOperator, LongBinaryOperator, and DoubleBinaryOperator.
In this step, I will create a BinaryOperatorTest.java class to demonstrate how to:
- Add two numbers
- Multiple two numbers
- Power two numbers
BinaryOperatorTest.java
package com.zheng.demo.jdk.fi;
import static org.junit.Assert.assertEquals;
import java.util.function.BinaryOperator;
import java.util.function.IntBinaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.DoubleBinaryOperator;
import org.junit.Test;
public class BinaryOperatorTest {
@Test
public void BinaryOperator_add_via_lambda() {
BinaryOperator<Integer> add = (a, b) -> a + b;
Integer sum = add.apply(10, 12);
assertEquals(22, sum.intValue());
}
@Test
public void IntBinaryOperator_add_two_numbers() {
IntBinaryOperator add2 = (a, b) -> a + b;
int sum = add2.applyAsInt(10, 12);
assertEquals(22, sum);
}
@Test
public void LongBinaryOperator_mutiple_two_numbers() {
LongBinaryOperator add2 = (a, b) -> a * b;
long product = add2.applyAsLong(10, 12);
assertEquals(120, product);
}
@Test
public void DoubleBinaryOperator_power_two_number() {
DoubleBinaryOperator add2 = (a, b) -> Math.pow(a, b);
double powerRet = add2.applyAsDouble(10, 2);
assertEquals(100, powerRet, 001);
}
}
4. Customized Functional Interfaces
Java 8 provides a new annotation: @FunctionalInterface which marks an interface as a FI. Java compiler will throw an error when an interface marked with @FunctionalInterface has more than one abstract methods.
In this step, I will create two customized FIs and demonstrate them in a Java application.
IntegerCalculatorwith@FunctionalInterfaceannotationGreetFunctionwithout@FunctionalInterface
4.1 IntegerCaculator
In this step, I will create a IntegerCaculator.java with one method – calculate.
IntegerCaculate.java
package com.zheng.demo;
@FunctionalInterface
public interface IntegerCalculator {
int caculate(int x, int y);
}
4.2 GreetingFunction
In this step, I will create a GreetingFunction.java with one method: speak. Java compiler will treat it as a FI even it does not have @FunctionalInterface annotation.
GreetFunction.java
package com.zheng.demo;
public interface GreetingFunction {
void speak(String message);
}
4.3 Demo
In this step, I will create a FPDemo.java class to demonstrate:
- How to calculate two integers with addition, subtraction, and division
- How to greet a person
FPDemo.java
package com.zheng.demo;
public class FPDemo {
public static void main(String[] args) {
GreetingFunction greeting = message -> System.out.println("Hello " + message + "!");
greeting.speak("Tom");
greeting.speak("Mary");
caculateTwoNumbers(3, 4);
caculateTwoNumbers(3, 0);
}
private static void caculateTwoNumbers(int x, int y) {
IntegerCalculator add = (a, b) -> a + b;
IntegerCalculator diff = (a, b) -> a - b;
IntegerCalculator divide = (a, b) -> (b == 0 ? 0 : a / b);
System.out.println(x + " + " + y + " = " + add.caculate(x, y));
System.out.println(x + " - " + y + " = " + diff.caculate(x, y));
System.out.println(x + " / " + y + " = " + divide.caculate(x, y));
}
}
5. Java 8 Enhancements
Java 8 enhances the Collections class with its sort, max, and min methods. These methods take a functional interface – Comparator as a parameter.
Java 8 Stream API provides the map, filter, sorted, min, max, and reduce methods that accept a functional interface as an argument.
5.1 Collections and Comparator
The Collections class and Comparator interface are enhanced in Java 8. Comparator is annotated with @FunctionalInterface. Collections‘ sort method takes Comparator as an argument.
In this step, I will create a CollectionsTest.java to demonstrate how to:
- Sort a list of contacts by their age
- Sort a list of String
- Compare the sorting to JDK7 style
CollectionsTest.java
package com.zheng.demo.jdk;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.junit.Test;
import com.zheng.demo.dao.ContactDao;
import com.zheng.demo.model.Contact;
public class CollectionsTest {
private ContactDao contDao = new ContactDao();
@Test
public void Collections_sort_by_contact_age() {
Comparator<Contact> contactComparator = Comparator.comparing(Contact::getAge);
List<Contact> contacts = contDao.findAllContacts();
Collections.sort(contacts, contactComparator);
System.out.println("Sorted contact");
contacts.stream().forEach(System.out::println);
Contact oldertContact = Collections.max(contacts, contactComparator );
assertEquals(57, oldertContact.getAge());
Contact youngestContact = Collections.min(contacts, contactComparator );
assertEquals(21, youngestContact.getAge());
}
@Test
public void Collections_sortWithInferType() {
List<String> names = Arrays.asList("Allen", "Matt", "Mary", "Megan", "Alex");
Collections.sort(names, (a, b) -> a.compareTo(b));
System.out.println("Sorted names: " + names);
}
@Test
public void sortBeforeJava8() {
List<String> names = Arrays.asList("Allen", "Matt", "Mary", "Megan", "Alex");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
}
}
5.2 Stream
Java 8 Stream API iterates and processes elements in a collection in a declarative style. Developers no longer use a loop to find, search, and filter elements in a collection of objects.
In this step, I will create StreamTest.java to demonstrate how to:
- Iterate the elements with a
Forloop - Iterate the elements via
Iterator StreamAPIforeach(Consumer<T>)- Filter an element from a list
- Transform the elements in a list
- Sort a list and find its minimum and maximum elements
StreamTest.java
package com.zheng.demo.jdk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
public class StreamTest {
private List<String> userNames;
@Test
public void search() {
Predicate<String> startWithA = name -> name.startsWith("a");
List<String> startWithANames = userNames.stream().filter(startWithA).collect(Collectors.toList());
assertEquals("aWang", startWithANames.get(0));
}
@Test
public void IntStream_sum() {
int sum = IntStream.of(1, 3, 5, 7, 9).sum();
assertEquals(25, sum);
}
@Test
public void tranform() {
List<String> uppercaseNames = userNames.stream().map(String::toUpperCase).collect(Collectors.toList());
assertTrue(uppercaseNames.contains("MZHENG"));
assertTrue(uppercaseNames.contains("AWANG"));
assertTrue(uppercaseNames.contains("TCHANG"));
}
@Test
public void min() {
Comparator<String> comparator = Comparator.comparing(String::length);
Optional<String> shortestName = userNames.stream().min(comparator );
assertTrue(shortestName.isPresent());
assertEquals("aWang", shortestName.get());
Optional<String> longestName = userNames.stream().max(comparator );
assertTrue(longestName.isPresent());
assertEquals("mzheng", longestName.get());
}
@Test
public void print_elelments_via_loop() {
for (String name : userNames) {
System.out.println(name);
}
}
@Test
public void print_elements_via_Iterator() {
Iterator<String> i = userNames.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}
@Test
public void print_elemetns_via_Stream() {
// Internal iteration
userNames.stream().forEach(System.out::println);
}
@Before
public void setup() {
userNames = Stream.of("mzheng", "tChang", "aWang").collect(Collectors.toList());
}
@Test
public void sort() {
List<String> sortedNames = userNames.stream().sorted().collect(Collectors.toList());
assertEquals("aWang", sortedNames.get(0));
assertEquals("mzheng", sortedNames.get(1));
assertEquals("tChang", sortedNames.get(2));
}
}
6. A Real Example
We tested several Java 8 pre-defined functional interfaces at the step 3; we built two customized functional interfaces at the step 4; we experienced Comparator, Collections, and Stream at the step 5. In this step, I will demonstrate how to use them in a real application.
A business application has two requirements:
- Retrieve the contacts from a database
- Convert the contact into login user
6.1 Contact
In this step, I will create a Contact.java class that contains a first name, last name, age, username, and password.
Contact.java
package com.zheng.demo.model;
public class Contact {
private String firstName;
private String lastName;
private String userName;
private String password;
private int age;
public Contact() {
super();
}
public Contact(String firstName, String lastName, String userName, String password, int age) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.userName = userName;
this.password = password;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Contact [firstName=" + firstName + ", lastName=" + lastName + ", userName=" + userName + ", password="
+ password + ", age=" + age + "]";
}
}
6.2 LoginUser
In this step, I will create a LoginUser.java class that has user name and password.
LoginUser.java
package com.zheng.demo.model;
public class LoginUser {
private String userName;
private String password;
public String getUsertName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "LoginUser [userName=" + userName + ", password=" + password + "]";
}
}
6.3 DataUtil
In this step, I will create a DataUtil.java class.
DataUtil.java
package com.zheng.demo.model;
import java.util.ArrayList;
import java.util.List;
public class DataUtil {
public static List<Contact> getListOfContacts() {
List<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("Becky", "Zheng", "bzheng", "pwd1234@", 48));
contacts.add(new Contact("Alex", "Change", "aChange", "pwd987$", 21));
contacts.add(new Contact("Caleb", "Wang", "cWang", "pwd2345#", 57));
return contacts;
}
public static Contact buildContact(String username, String pwd, int age) {
Contact cnt = new Contact();
cnt.setUserName(username);
cnt.setPassword(pwd);
cnt.setAge(age);
return cnt;
}
public static LoginUser buildLoginUser(String userName, String pwd) {
LoginUser user = new LoginUser();
user.setUserName(userName);
user.setPassword(pwd);
return user;
}
public static LoginUser toUser(Contact contact) {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}
}
6.4 ContactDao
In this step, I will create a ContactDao.java class that contains a method to find all contacts.
ContactDao.java
package com.zheng.demo.dao;
import java.util.List;
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;
public class ContactDao {
public List<Contact> findAllContacts(){
return DataUtil.getListOfContacts();
}
}
6.4.1 ContactDaoTest
In this step, I will create a ContactDaoTest.java class.
ContactDaoTest.java
package com.zheng.demo.dao;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;
import com.zheng.demo.model.Contact;
public class ContactDaoTest {
private ContactDao testClass = new ContactDao();
@Test
public void findAllContacts() {
List<Contact> allContacts = testClass.findAllContacts();
assertEquals(3, allContacts.size());
}
}
6.5 Data Mapper
In this step, I will create a DataMapper class to transform a Contact to LoginUser:
DataMapper.java
package com.zheng.demo.service;
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;
import com.zheng.demo.model.LoginUser;
public class DataMapper {
public LoginUser toUser(Contact contact) {
return DataUtil.toUser(contact);
}
}6.5.1 DataMapperTest
In this step, I will create a DataMapperTest.java class.
DataMapperTest.java
package com.zheng.demo.service;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.LoginUser;
public class DataMapperTest {
private DataMapper dto = new DataMapper();
@Test
public void toUser() {
Contact contact = new Contact("firstName", "lastName", "userName", "password", 40);
LoginUser user = dto.toUser(contact);
assertNotNull(user);
assertEquals("USERNAME", user.getUsertName());
assertEquals("password", user.getPassword());
}
}
6.6 LoginUserService
In this step, I will create a LoginUserService.java class to demonstrate several ways to transform aContact to LoginUser:
- Java 8 method reference ( including three different formats)
- Java 8 lambda expression ( including three different formats)
- Java 8 named lambda expression
Java Forloop
We then compare them and conclude that the most readable way is via the method reference.
LoginUserService.java
package com.zheng.demo.service;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.zheng.demo.dao.ContactDao;
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;
import com.zheng.demo.model.LoginUser;
public class LoginUserService {
ContactDao contactDao = new ContactDao();
DataMapper dto = new DataMapper();
public List<LoginUser> getAllUser_java8Style_Lambda_1() {
return contactDao.findAllContacts().stream().map(contact -> {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_Lambda_2() {
return contactDao.findAllContacts().stream().map(c -> {
return toUser(c);
}).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_Lambda_3() {
return contactDao.findAllContacts().stream().map(c -> toUser(c)).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_methodReference_1() {
return contactDao.findAllContacts().stream().map(DataUtil::toUser).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_methodReference_2() {
return contactDao.findAllContacts().stream().map(this::toUser).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_methodReference_best() {
return contactDao.findAllContacts().stream().map(dto::toUser).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_java8Style_namedLambda() {
Function<Contact, LoginUser> convertContactToLoginUser = contact -> {
return toUser(contact);
};
return contactDao.findAllContacts().stream().map(convertContactToLoginUser).collect(Collectors.toList());
}
public List<LoginUser> getAllUser_loopStyle() {
List<Contact> allContacts = contactDao.findAllContacts();
List<LoginUser> allUser = new ArrayList<>();
for (Contact contact : allContacts) {
allUser.add(toUser(contact));
}
return allUser;
}
private LoginUser toUser(Contact contact) {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}
}
6.6.1 LoginUserServiceTest
In this step, I will create a LoginUserServiceTest.java class.
LoginUserServiceTest.java
package com.zheng.demo.service;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.Test;
import com.zheng.demo.model.LoginUser;
public class LoginUserServiceTest {
private LoginUserService testService = new LoginUserService();
@Test
public void getAllUser_java8Style_Lambda_1() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_1();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_Lambda_2() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_2();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_Lambda_3() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_3();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_methodReference_1() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_1();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_methodReference_2() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_2();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_methodReference_best() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_best();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_java8Style_namedLambda() {
List<LoginUser> allusers = testService.getAllUser_java8Style_namedLambda();
assertTrue(allusers.size() == 3);
validate(allusers);
}
@Test
public void getAllUser_loopStyle() {
List<LoginUser> allusers = testService.getAllUser_loopStyle();
assertTrue(allusers.size() == 3);
validate(allusers);
}
private void validate(List<LoginUser> allusers) {
Consumer<LoginUser> printOutUser = System.out::println;
allusers.stream().forEach(printOutUser );
Predicate<LoginUser> foundMary = e -> e.getUsertName().equalsIgnoreCase("bzheng") ;
List<LoginUser> foundusers = allusers.stream().filter(foundMary ).collect(Collectors.toList());
assertTrue(foundusers.size() == 1);
}
}
7. Summary
In this tutorial, I demonstrated how to use pre-defined functional interfaces, then built two customized functional interfaces, later experienced the Stream API. Finally, I showed how to use them in a real business application.
Java 8 was released on March 18, 2014 to support the functional programming. However, Java is not a functional programming. Functional programming focuses on computing the results from functions rather than performing actions on the objects.
8. References
- http://tutorials.jenkov.com/java-functional-programming/index.html
- https://dzone.com/articles/functional-programming-patterns-with-java-8
- https://flyingbytes.github.io/programming/java8/functional/part1/2017/01/23/Java8-Part1.html
- https://www.baeldung.com/java-8-lambda-expressions-tips
- https://hackernoon.com/finally-functional-programming-in-java-ad4d388fb92e
- https://www.javaworld.com/article/3319078/learn-java/functional-programming-for-java-developers-part-2.html
9. Download the Source Code
This example consists of a Maven project which contains several Junit tests for the pre-defined functional interfaces, Collections, and Stream. It also includes a real application to convert the contact to login user via functional programming style.
You can download the full source code of this example here: Java 8 Functional Programming Tutorial

Amazing thanks for FPs