Java Quiz Player

An Example of Java 8's Functional Style Programming

Nov 10, 2017

1. Overview

Java 8 introduced new libraries and programing style -- streams, functional interfaces and lambda expressions. These bring functional-style programming to the Java's object-oriented programming capabilities.

This post uses example code with these new features and shows the differences over the pre Java 8 code. The example has two programs, the first a pre Java 8 code and the second is Java 8 code. In addition, there is a non-Java program for the same example, and this is from a real functional programming language.

The example is a simple method or function that takes a non-empty string text as input and returns the number of words starting with a capital letter. The example text is "Counting the Number of Words that Begin with a Capital-letter in a String". Note there are thirteen words in the given text and six of them begin with capital letters.

Also, the example uses the recently released Java 9's Java Shell (a.k.a. JShell) to run the Java code samples.

1.1. About JShell

The Java Shell is an interactive tool for learning the Java programming language and prototyping Java code. JShell is Read-Evaluate-Print Loop (REPL), which evaluates declarations, statements, and expressions as they are entered and immediately shows the results.

The tool is available with Java 9 and is started from the operating system's command prompt. The following is a screenshot of the Java code entered into the JShell started from Windows command prompt.

GUI image

A copy of the JShell's user guide can be downloaded from the Oracle JDK 9 Documentation web page: https://docs.oracle.com/javase/9/index.html

2. Java Code Examples

Create the input text string variable in the JShell:

jshell> String input = "Counting the Number of Words that Begin with a Capital-letter in a String"
input ==> "Counting the Number of Words that Begin with a Capital-letter in a String"

2.1. Pre Java 8 Code

Create a method in the JShell using pre Java 8 code. The method takes an input string and returns the number of words with capital letters as an integer.

jshell> int noOfWordsWithUppercase(String inputString) {
   ...>     String [] words = inputString.split(" ");
   ...>     int total = 0;
   ...>     for (String word : words) {
   ...>         char ch = word.charAt(0);
   ...>         if (Character.getType(ch) == Character.UPPERCASE_LETTER) {
   ...>             total++;
   ...>         }
   ...>     }
   ...>     return total;
   ...> }
|  created method noOfWordsWithUppercase(String)

Run the method. The method uses the variable input (created earlier) as the argument and returns the result, 6.

jshell> int noOfWords = noOfWordsWithUppercase(input)
noOfWords ==> 6

The code uses a simple sequential for-each loop and mutative accumulation to get number of words.

2.2. Java 8 Code

Create a method in the JShell using Java 8. Run the method. Note the input text string and the results are the same as that of the example in section 2.1.Pre Java 8 Code, above.

jshell> long noOfWordsWithUppercase2(String inputString) {
   ...>   return
   ...>     Stream.of(inputString.split(" "))
   ...>           .mapToInt(word -> word.charAt(0))
   ...>           .filter(x -> Character.getType(x) == Character.UPPERCASE_LETTER)
   ...>           .count();
   ...> }
|  created method noOfWordsWithUppercase2(String)

jshell> long noOfWords2 = noOfWordsWithUppercase2(input)
noOfWords2 ==> 6

Note this code is easier to read and maintain compared to the code written with pre Java 8 code. The code shows what it does rather than the how -- an important aspect of functional programming.

This code is of Java 8's functional style programming and the API's are from the java.util.stream.Stream. It has lambda expressions which are input functions to the Stream's methods map and filter. The lambda's are instances of java.util.function.Function and Predicate functional interfaces respectively.

Here is the link to the Java SE 8 API: https://docs.oracle.com/javase/8/docs/api/

The filter and mapToInt are intermediate stream operations and are lazy. The count method, a terminal operation, is a reduction. The code is an example of a reduction -- reduces the stream to a primitive.

The code uses the filter/map/reduce operations ubiquitous with functional programming.

The code is stateless and is amenable for parallel execution (given a substantial input). The Stream's parallel method converts the sequential stream into a parallel stream. Parallel execution is affected by factors like statelessness, side-effects, ordering and non-interference. The code converted to execute using a parallel stream is as follows:

Stream.of(inputString.split(" "))
      .parallel()
      .mapToInt(
      ...

3. The Example using Haskell

3.1. About Haskell

Haskell is a general purpose, lazy, purely functional programming language.

Haskell provides higher-order functions, non-strict semantics, static polymorphic typing, user-defined algebraic datatypes, pattern-matching, list comprehensions, a module system, a monadic I/O system, and a rich set of primitive datatypes, including lists, arrays, arbitrary and fixed precision integers, and floating-point numbers.

More about Haskell at haskell.org

3.2. Example Code

The example code is entered and run from the GHCi (Glasgow Haskell Compiler interactive) -- a REPL tool for Haskell.

ghci> :module + Data.Char
ghci> let capCount = length . filter (isUpper . head) . words
ghci> let input = "Counting the Number of Words that Begin with a Capital-letter in a String"
ghci> capCount input
6

The first line imports the module Data.Char. The following three lines of code define the function and the input data and then run the function.

The initial line is a definition of a user-defined function, capCount. It uses various Haskell in-built functions -- length, filter, isUpper, head and words; all these are imported by default from the Prelude module, except the isUpper which is from the Data.Char. The next line defines the input string as input. The last line runs the capCount function with input as argument and returns the result, 6.

The Haskell example code is taken from the online tutorial, Real World Haskell: http://book.realworldhaskell.org/read/functional-programming.html

The following is a screenshot of the GHCi for Windows with the example code:

GUI image

3.3. Function Description

About strings: Note that in Haskell there is a character data type, Char; and there is no string data type. A list of characters is a string. For example, the string "cap" is a list of characters and is same as ['c','a','p'] and has a size of 3.

The example function uses various in-built list functions and a character function to arrive at the solution:

NOTE: "e.g." is an abbreviation for the Latin word "exempli gratia", which means "for example".

The period (.) used in the definition of the function capCount is a function composition operator. capCount definition is a composition of the various built-in functions. The style of writing functions using function composition is called as point-free style. Composing two functions f and g produces a new function that, when called with a parameter x, is the equivalent of calling g with the parameter x and then calling the f with that result:

(f.g)x is the same as f(g(x))

Consider the following code run from the GHCi. It checks if the first character of the given string is a capital letter and returns a boolean True or False. The solution uses two built-in functions -- isUpper and head.

ghci> isUpper (head "Caps")
True

The same functionality using function composition:

ghci> (isUpper . head) "Caps"
True

In Java 8, function composition is available from various pre-defined functional interfaces, e.g., the java.util.function.Function interface's andThen and compose default methods.

3.4. Using $ Operator

The Haskell code from the section 3.2.Example Code, can also be written akin to the Java 8 example, using the function application with a dollar ($) operator.

ghci> length $ filter isUpper $ map head $ words input
6

The code is run from the GHCi and uses the same input variable defined earlier and returns the result, 6.

In the above code the right most function (words) is evaluated first and its result is input to the next right most function (map) separated by the $, and so on; note the functions are separated by the $ operator. Function application using $ is right associative in Haskell.

Note this version of the code uses an additional built-in function, map.

The map takes a function (built-in or user-defined) and a list of any type as its arguments and returns a list with its elements transformed by the function argument; in the example above the map function returns a list of the first characters of all the string words. An example of using map:

ghci> map (+1) [1,99,0]
[2,100,1]
Return to top