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
return
keyword 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
Integer
to 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
Integer
is a even number - Filter an element with
Stream
–filter(Predicate <T, R>)
- Combine two predicates into a new predicate
- Check a
Long
is divisible by three - Check a
Double
is a positive number - Check if the first
Integer
is bigger than the second value - Utilize
IntPredicate
andDoublePrediate
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
String
value - Return a
true
value - Return the max
Integer
value - Return the max
Long
value - Return the
pi
value
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
String
after 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
String
to the uppercase format - Concatenate a
String
with a prefix value - Return a
Integer
with a doubled value - Return a
Long
with a squared value - Return a
Double
with 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.
IntegerCalculator
with@FunctionalInterface
annotationGreetFunction
without@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
For
loop - Iterate the elements via
Iterator
Stream
APIforeach(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 For
loop
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