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