Java Streams were introduced in Java 8 and contains classes for processing a sequence of elements. A stream provides a number of methods that can be chained together to produce a transformation on such elements.
The Streams API is integrated with the Java collections framework, and can be used to process a Java collection when a stream is created.
We normally use the type Stream
As mentioned streams can be created from a collection.
1 2 3 4 5 6 7 8 9 10 | @Test
public void create_FromCollection() {
List<String> listOfStrings = new LinkedList<String>();
listOfStrings.add("one");
listOfStrings.add("two");
listOfStrings.add("three");
Stream<String> stream = listOfStrings.stream();
assertNotNull(stream);
}
|
Above we use the stream method to get a Stream from a Collection. This is because the java.util.Collection interface contains a default method to return a Stream.
1 2 3 4 5 | public interface Collection<E> extends Iterable<E> {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
|
We can also create a Stream from an Array.
1 2 3 4 5 6 | @Test
public void create_FromArray() {
String[] arrayOfString = new String[]{"one", "two", "three"};
Stream<String> stream = Arrays.stream(arrayOfString);
assertNotNull(stream);
}
|
We can also create a stream using a builder.
1 2 3 4 5 6 7 8 9 | @Test
public void create_FromStreamBuilder() {
Stream<Object> stream = Stream.builder()
.add("one")
.add("two")
.build();
assertNotNull(stream);
}
|
We can also create a parallel stream for executing stream operations over multiple threads.
1 2 3 4 5 6 7 8 9 10 | @Test
public void parallel_FromCollection() {
List<String> listOfStrings = new LinkedList<String>();
listOfStrings.add("one");
listOfStrings.add("two");
listOfStrings.add("three");
Stream<String> stream = listOfStrings.parallelStream();
assertNotNull(stream);
}
|
When we call an operation on the stream such as:
1 | stream.forEach((s) -> //do some work);
|
It will run the operation in parallel for each element in the parallel stream.
Now let’s look at some of the common operations on streams. Stream operations can be categorized into two main types of operations.
Stream operations are also functional programming friendly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Test
public void operations() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<Integer> expected = new LinkedList<>();
expected.add(2);
List<Integer> evenIntegers = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toList());
assertThat(evenIntegers, is(expected));
}
|
For example in the above code the filter method is an intermediate operation, that returns a Stream<Integer>
of even numbers. Finally, the call to collect is the terminal operation that converts the stream back to a List<Integer>
.
We can replace the traditional, for, while loop etc with more concise operations provided by the Stream API.
For example, we can replace the traditional for loop:
1 2 3 4 5 6 7 8 9 | List<String> listOfStrings = new LinkedList<String>();
listOfStrings.add("one");
listOfStrings.add("two");
listOfStrings.add("three");
for(String str : listOfStrings) {
System.out.println(str);
}
|
With the forEach
method.
1 | listOfStrings.forEach((s) -> System.out.println(s));
|
We can use the filter method to filter the items of a stream based on some predicate. For example, consider we want to find the even numbers in a list of integers.
1 2 3 4 5 6 7 8 9 10 11 12 13 | List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<Integer> expected = new LinkedList<>();
expected.add(2);
List<Integer> evenIntegers = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toList());
assertThat(evenIntegers, is(expected));
|
The above code passes the predicate as a lambda expression to the filter method of the Stream. After all the even numbers are found we apply the collect method to the resulting stream to convert the result back to a List of Integers.
The collect method of a Stream can be used to convert a Stream back to some result type such as a Map, Set, List etc.
1 2 3 | List<Integer> evenIntegers = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toList());
|
Above we use the utility class Collectors to convert a stream to a List. Moreover, we can convert a Stream to a Map.
1 2 3 | Map<Integer,String> evenIntegerMap = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toMap((k) -> k, (v) -> v.toString()));
|
Above we use the Collectors.toMap()
method to convert the List to a Map<Integer,String>
.
The Stream API provides methods for converting elements from one type to another. For example, below we convert a List of Integers to a List of Strings.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Test
public void operations_map() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<String> expected = new LinkedList<>();
expected.add("1");
expected.add("2");
expected.add("3");
List<String> integerStringList = integerList.stream()
.map((i) -> i.toString())
.collect(Collectors.toList());
assertThat(integerStringList, is(expected));
}
|
The flatMap
method can also be used to flatten a list of sequences to a single sequence.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test
public void operations_flatMap() {
Object[][] arraryOfArrays = new Object[][] {
new Object[] {1,2,3},
new Object[] {4,5,6}
};
Object[] expected = new Object[] {1,2,3,4,5,6};
Object[] actual = Arrays.stream(arraryOfArrays).flatMap((o) -> Arrays.stream(o)).toArray();
assertArrayEquals(expected,actual);
}
|
Above we convert a multidimensional array to an array with a single dimension.
The Stream API provides the reduce method that allows us to reduce a sequence of elements to a single value. For example, let’s say we want to sum a list of integers.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test
public void operations_reduce() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
Integer expected = 6;
Integer result = integerList.stream()
.reduce(0,(x,y) -> x + y);
assertEquals(expected, result);
}
|
Above we use the reduce method passing an initial value of 0, and a lambda expression to sum each of the integers in a List.
In this tutorial we introduced Streams in Java. We also provided a brief overview of some of the main operations that can be performed with streams.