Internal Iterations in Java 8

Internal iterations

Most of us learned in school to program in an imperative/structural/procedural way. Object oriented programming is also a type of procedural paradigm. In that context, when we are working with collections, we are iterating through them with some kind of for or for-each loops. We are telling the program how to perform the iteration and (within the loop) what do to with each processed element from the collection. Those is called external iteration. On the other side, in languages which support functional programming, you should focus only on ‘what’ and the underlying collection will take care of ‘how’ to perform iteration. Those are called internal iterations.

Internal Iterations in Java

Up to Java version 7, the Collections Framework classes relied on a contract (Collection<E> interface) that each class should implement Iterable<E> interface and implement the method Iterator<T> iterator(). Then, you are able to iterate over each collection using a for-each loop. Consider following example: there is a list of characters (as single-letter Strings) where you have to keep only alphabet characters and convert them to upper case. Here is an example of an external iteration:

import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

public class ExternalIterator {

    public static void main(String[] args) {
        List<String> characterList = Arrays.asList(new String[]{"a", "b", "2", "c", "4"});

        List<String> uppercaseLettersList = new ArrayList<String>();
        for (String character : characterList) {
            if (character.matches("[a-zA-Z]")) {
                uppercaseLettersList.add(character.toUpperCase());
            }
        }

        System.out.println(uppercaseLettersList);
    }
}

// prints: [A, B, C]

Of course, you can get an Iterator from the List and iterate using while(iterator.hasNext()) loop, too.

Since Java 8, you are able to use functional way of working with collections, where the iteration logic (‘how’ to iterate) is hidden within the concrete collection class. The Collection<E> interface still extends Iterable<E> interface, but if you look closer, you will notice some additional methods like Stream<E> stream() which are marked with a keyword default.

“The Java [8] API designers are updating the API with a new abstraction called Stream that lets you process data [collections] in a declarative way. Furthermore, streams can leverage multi-core architectures without you having to write a single line of multithread code.” [link]

 

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.” [link]

lambdaIn a functional version of the code above, you actually think of what to do to transform initial List of characters into a desired List, using a set of transformations (functions).

In plain English, one could describe the solution to the above task like this:

  1. take the initial characterList and remove (filter out) all non-letter characters
  2. convert each element in the remained list of character to upper case

The code in Java 8, using Stream API, gives a functional version of the imperative code above, but now using internal iterators:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class InternalIterator {
    public static void main(String[] args) {
        List<String> characterList = Arrays.asList(new String[]{"a", "b", "2", "c", "4"});

        List<String> uppercaseLettersList = characterList.stream()
        		.filter(c -> c.matches("[a-zA-Z]"))
        		.map(l -> l.toUpperCase())
        		.collect(Collectors.toList());

        System.out.println(uppercaseLettersList);
    }
}

// prints: [A, B, C]

The method stream() of the characterList returns sequential Stream with the initial List as its source. Next, filter() method is applied to the generated Stream. This method takes as an argument a Predicate – which represents a boolean-value functional interface of one argument. filter() method transforms initial Stream and returns a new Stream with only elements which satisfies the given boolean-value function. In Java 8, there is a feature called Lamba expressions, which are used as a syntactic sugar to represent anonymous Java classes which implement @FunctionalInterfaces, like Predicate is. Next, chained method map() takes over the new produced Stream and further transforms elements of the Stream – producing a new resulting Stream object with an uppercase letters. At the end, the resulting Stream is collected as List.

Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression. Lambda expressions also improve the Collection libraries making it easier to iterate through, filter, and extract data from a Collection. In addition, new concurrency features improve performance in multicore environments.” [link]

It could take some time to understand and do the “mind switch” towards functional programming. But, even if you are not familiar with new Java 8 features and functional programming, you can easily understand “the intention” of the InternalIterator class (2nd code listing) compared to the ExternalIterator class (1st code listing). The example above is fairly simple, but in more complex cases there is a significant difference in “clearness” of the internal iteration approach. That’s why it is worth to invest time in learning and understanding those concepts and a different perspective in writing the code.

Functional Programming vs. Imperative Programming

The functional programming paradigm was explicitly created to support a pure functional approach to problem solving. Functional programming is a form of declarative programming. In contrast, most mainstream languages, including object-oriented programming (OOP) languages such as C#, Visual Basic, C++, and Java –, were designed to primarily support imperative (procedural) programming.
With an imperative approach, a developer writes code that describes in exacting detail the steps that the computer must take to accomplish the goal. This is sometimes referred to as algorithmic programming. In contrast, a functional approach involves composing the problem as a set of functions to be executed. You define carefully the input to each function, and what each function returns. The following table describes some of the general differences between these two approaches.

Characteristic Imperative approach Functional approach
Programmer focus How to perform tasks (algorithms) and how to track changes in state. What information is desired and what transformations are required.
State changes Important. Non-existent.
Order of execution Important. Low importance.
Primary flow control Loops, conditionals, and function (method) calls. Function calls, including recursion.
Primary manipulation unit Instances of structures or classes. Functions as first-class objects and data collections.

Taken from MSDN.

Internal Iterations in JavaScript

JavaScript Arrays are list-like objects whose prototype has methods to perform traversal and mutation operations. There are also methods like filter(), map(), reduce() etc. If you are working a lot with arrays in JavaScript, you should consider those methods, too. Here is a JavaScript version of the example from above:

var characters = ['a', 'b', '2', 'c', '4'];
var uppercaseResult = characters
    .filter(function(value) {
        return value.match(/[a-zA-Z]/);
    })
    .map(function(value){
        return value.toUpperCase();
    });
                
console.log(uppercaseResult);
// prints in JS Console: ["A", "B", "C"]

Test it on jsFiddle.

Additional Info

Great presentation about external and internal iteration using Java 8 lambda expressions, by Venkat Subramaniam:

1 Comment

  1. Pingback: Moving from Java 6 to Java 8 - Programming Hints

Leave a Comment

Your email address will not be published. Required fields are marked *

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