Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.

Java 8 Revealed: Lambdas, Default Methods and Bulk Data Operations

Stream sources

Streams can use different sources to consume data and the standard JDK API is extended with the new methods to make the experience more pleasant.

First source of data for streams is, of course, collections:

List list;
Stream stream = list.stream();

An other interesting source of data are so-called generators:

Random random = new Random();
Stream randomNumbers = Stream.generate(random::nextInt);

There’s a number of utility methods to help defining the ranges of data:

IntStream range = IntStream.range(0, 50, 10);
range.forEach(System.out::println); // 0, 10, 20, 30, 40

Also, some of the existing classes in the standard library. For instance, Random class has been extended with some useful methods:

new Random()
  .ints()             // generate a stream of random integers
  .limit(10)          // we only need 10 random numbers
  .forEach(System.out::println);

Intermediate operations

Intermediate operations are used to described the transformations that should be performed over the stream of data. The filter(..) and map(..) methods in the good examples of intermediate operations. The return type of these methods is Stream, so that it would allow chaining of more operations.

Your time is too valuable to sit around waiting for your code to redeploy. Seriously, imagine how much more you could accomplish if you choose to Eliminate Redeploys from your daily routine!

Here’s a list of some useful intermediate operations:

  • filter excludes all elements that don’t match a Predicate.
  • map perform transformation of elements using a Function.
  • flatMap transforms each element into zero or more elements by way of another Stream.
  • peek perform some action on each element as it is encountered.
  • distinct excludes all duplicate elements according to their equals(..)
    behavior.
  • sorted ensures that stream elements in subsequent operations are encountered according to
    the order imposed by a Comparator.
  • limit ensures that subsequent operations only see up to a maximum number of elements.
  • substream ensure that subsequent operations only see a range (by index) of elements.

Some of the operations, like sorted, distinct and limit are stateful, meaning the resulting stream of these operations depend on the values that the operation processed previously. As the Javadoc says, all intermediate operations are lazy. Let’s take a look at some of the operations in more details.

Filter

Filtering a stream of data is the first natural operation that we would need. Stream interface exposes a filter(..) method that takes in a Predicate SAM that allows us to use lambda expression to define the filtering criteria:

List persons = ...
Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);

Map

Assume we now have a filtered data that we can use for the real operations, say transforming the objects. The map operations allows us to apply a function, that takes in a parameter of one type, and returns something else. First, let’s see how it would have been described in the good ‘ol way, using an anonymous inner class:

Stream students = persons.stream()
    .filter(p -> p.getAge() > 18)
    .map(new Function<Person, Student>() {
          @Override
          public Student apply(Person person) {
            return new Student(person);
          }
         });

Now, converting this example into a lambda syntax we get the following:

Stream map = persons.stream()
    .filter(p -> p.getAge() > 18)
    .map(person -> new Student(person));

And since the lambda that is passed to the map(..) method just consumes the parameter without doing anything else with it, then we can transform it further to a method reference:

Stream map = persons.stream()
    .filter(p -> p.getAge() > 18)
    .map(Student::new);

Download the PDF