1. Innovations in Java 8: Functional programming

With the release of Java 8, the language gained powerful support for functional programming. You could even say it gained long-awaited support for functional programming. Coding became faster, although the code was more difficult to read 🙂

Before learning functional programming in Java, we recommend that you understand three things well:

  1. OOP, inheritance and interfaces (Levels 1-2 in the Java Core quest).
  2. Default method implementations in an interface.
  3. Inner and anonymous classes.

The good news is that you don't have to know all this to use many of the features of functional programming in Java. The bad news is that it will be difficult to understand exactly how everything is arranged and how everything works without knowing about anonymous inner classes.

In the upcoming lessons, we will focus on how easy and simple it is to use Java's functional programming features, without a deep understanding of how it works.

It takes months to understand all the nuances of functional programming in Java. You can learn to read such code in a few hours. So we suggest starting small. Even if it's with I/O streams.

2. I/O streams: stream pipelines

Do you remember that once upon a time you learned about I/O streams: InputStream, OutputStream, Reader, Writer etc.?

There were stream classes that read data from data sources, such as FileInputSteam, and there were intermediate data streams that read data from other streams, such as InputStreamReader and BufferedReader.

These streams could be organized into data processing pipelines. For example, like this:

FileInputStream input = new FileInputStream("c:\\readme.txt");
InputStreamReader reader = new InputStreamReader(input);
BufferedReader buff = new BufferedReader(reader);

String text = buff.readLine();

It's important to note that in the first few lines of code, we're just constructing a chain of Stream objects. The data hasn't passed through the pipeline yet.

But as soon as we call the buff.readLine() method, the following will happen:

  1. The BufferedReader object calls the read() method on the InputStreamReader object
  2. The InputStreamReader object calls the read() method on the FileInputStream object
  3. The FileInputStream object starts reading data from the file

In other words, there is no movement of data along the stream pipeline until we start calling methods like read() or readLine(). The mere construction of the stream pipeline does not drive data through it. The streams themselves do not store data. They only read from others.

Collections and streams

Starting with Java 8, it became possible to get a stream to read data from collections (and not only from them). But this is not the most interesting thing. It actually became possible to easily and simply construct complex chains of data streams. And in doing so, the code that previously took 5-10 lines could now be written in 1-2 lines.

Example of finding the longest string in a list of strings:

Finding the longest string
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
String max = list.stream().max((s1, s2)-> s1.length()-s2.length()).get();
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Stream<String> stream = list.stream();
Optional<String> optional = stream.max((s1, s2)-> s1.length()-s2.length());
String max = optional.get();

3. Stream interface

Java 8's extended support for streams is implemented using the Stream<T> interface, where T is a type parameter that indicates the type of data being passed in the stream. In other words, a stream is completely independent of the type of data it passes.

To get a stream object from a collection, just call its stream() method. The code looks roughly like this:

Stream<Type> name = collection.stream();
Getting a stream from a collection

In this case, the collection will be considered the stream's data source, and the Stream<Type> object will be a tool for obtaining data from the collection in the form of a data stream.

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Stream<String> stream = list.stream();

By the way, you can get a stream not only from collections, but also from arrays. To do this, you need to use the Arrays.stream() method. For example:

Stream<Type> name = Arrays.stream(array);
Getting a stream from an array

In this case, the array will be considered the data source for the stream called name.

Integer[] array = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(array);

No data is moved when the Stream<Type> object is created. We simply got a stream object in order to start building a stream pipeline.