Core Java

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.

 
 
 
 
 
 
 
 

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 a String
  • Return a string’s length as an Integer
  • Combine two functions into a new function
  • Convert elements in a list via Streammap(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 Streamfilter(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 and DoublePrediate

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 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 annotation
  • GreetFunction 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. Collectionssort 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 API foreach(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

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.

Download
You can download the full source code of this example here: Java 8 Functional Programming Tutorial

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
john
john
3 years ago

Amazing thanks for FPs

Back to top button