OCP Exam Series VI – Streams

Introduction

In this post, we will cover the streams in Java 8. We will discover the three parts of the pipeline: source, terminal, and intermediate operations.

What is a stream pipeline?

Stream operations are divided into intermediate and terminal operations and are combined to form stream pipelines. A stream pipeline consists of a source; followed by zero or more intermediate operations; and terminal operation.

Source

There are few ways to create a finite stream. Let’s look at them:

Create an empty stream

Stream<String> empty = Stream.empty();

Create a single element stream:

Stream<Integer> stream = Stream.of(1);

Create a stream from an array:

Stream<Integer> stream = Stream.of(12, 13, 14);

Convert a list to stream using stream() method:

List list = Arrays.asList("ates", "su", "toprak", "tahta");
Stream stream = list.stream();

Create an infinite stream. As we generate an infinite stream, the program will not finish until we terminate it.

Stream<Double> randomNumbers = Stream.generate(Math::random);
Stream<Integer> iterateForever = Stream.iterate(1, n -> n * 2);

Terminal Operations

Terminal operations may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used. If you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.

count()

Stream s = Stream.of("1", "2", "3");
System.out.println(s.count()); //3

min() and max()

Stream<String> s = Stream.of("suleyman", "canan", "fatma", "omur");
Optional<String> min = s.min((s1, s2) -> s1.length()—s2.length());
min.ifPresent(System.out::println); // omur

Optional<String> max = s.max((s1, s2) -> s1.length()—s2.length());
max.ifPresent(System.out::println); // suleyman

findAny() and findFirst()

Stream<String> s = Stream.of("suleyman", "canan", "fatma", "omur");
Stream<String> infinite = Stream.generate(() -> "tomis");

s.findFirst().ifPresent(System.out::println); // suleyman
infinite.findAny().ifPresent(System.out::println); // tomis

allMatch() , anyMatch() and noneMatch()

List<String> list = Arrays.asList("suleyman", "4", canan", "fatma", "omur");
Stream<String> infinite = Stream.generate(() -> "omur");
Predicate<String> predicate = x -> Character.isLetter(x.charAt(0));

System.out.println(list.stream().anyMatch(predicate)); // true
System.out.println(list.stream().allMatch(predicate)); // false
System.out.println(list.stream().noneMatch(predicate)); // false
System.out.println(infinite.anyMatch(predicate)); // true

forEach()

Stream<String> s = Stream.of("suleyman", "canan", "fatma", "omur");
s.forEach(System.out::print); // suleymancananfatmaomur

reduce()

The reduce() is a reduction operation that combines a stream into a single object. A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list.

Stream<String> stream = Stream.of("l", "o", "g", "a", "r");
String word = stream.reduce("", (s, c) -> s + c);
System.out.println(word); // combines stream input to a string, "logar"

collect()

This is called mutable reduction. It is more efficient than reduce method. We use the same mutable object, such as Collection or StringBuilder, while accumulating.

collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

Stream<String> stream = Stream.of("l", "o", "g", "a", "r");
StringBuilder word = stream.collect(StringBuilder::new,
StringBuilder::append, StringBuilder:append)

collect(Collector<? super T,A,R> collector)

Stream stream = Stream.of("g", "o", "r", "a");
TreeSet set = stream.collect(Collectors.toCollection(TreeSet::new));
System.out.println(set); // [a,g,o,r]

Intermediate operations

Unlike a terminal operation, intermediate operations deal with infinite streams simply by returning an infinite stream. Since elements are produced only as needed, this works fine.

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation does not perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.

filter()

Stream<String> s = Stream.of("hali", "kilim", "time travel");
s.filter(x -> x.startsWith("k")).forEach(System.out::print); // kilim

distinct()

Stream<String> s = Stream.of("komutan logar", "ceku", "ceku", "ceku");
s.distinct().forEach(System.out::print); // komutan logar

limit() and skip()

Stream<Integer> s = Stream.iterate(1, n -> n + 1);
s.skip(5).limit(2).forEach(System.out::print); // 67

map()

Stream<String> s = Stream.of("ates", "su", "toprak", "tahta");
s.map(String::length).forEach(System.out::print); // 4265

flatMap()

This is useful if you want to remove empty elements from a stream or you want to combine a stream of lists

List<String> list = Arrays.asList();
List<String> ates = Arrays.asList("Ates");
List<String> tahta = Arrays.asList("Su", "Toprak", "Tahta");
Stream<List<String>> element = Stream.of(list, ates, tahta);
element .flatMap(l -> l.stream()).forEach(System.out::print); // AtesSuToprakTahta

sorted()

Stream<String> s = Stream.of("ates", "su", "toprak", "tahta");
s.sorted().forEach(System.out::print); // atestahtatopraksu

peek()

Stream<String> stream = Stream.of("ates", "su", "toprak", "tahta");
long count = stream.filter(s -> s.startsWith("t")).peek(System.out::println).count(); // tahta

Summary

In this section, we covered the three parts of the pipeline in Java 8: source, intermediate and terminal operations. We also gave code samples of the common operations of the stream pipeline.

Leave a Reply

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