Streams in Java
Java stream API is available in java 8 and is completely different from InputStream and OutputStream of java I/O. Streams are playing a vital role to bring functional programming to java. In this tutorial you will learn the most powerful operations offered by streams API such as reduce, collect, flatMap and parallel streams. Before starting with stream API, you must have understanding of lambda expressions provided in java and i discussed in “Interfaces in Java” or “functional interfaces” or “collection methods“.
How does stream work?
A stream contains a sequence of elements and allows to perform different kind of operations on these elements. There are two type of steam operations:
- Intermediate : return a stream so we can perform multiple operations and are without semicolon. In the example below filter, map and sorted are intermediate operations.
- Terminal : Are void or return non-stream results. Such as forEach is a terminal operation in the example below.
package java8; import java.util.Arrays; import java.util.List; public class StreamEx { public static void main(String[] args) { List myList = Arrays.asList("A1", "A2", "B1", "C1", "C2","D1","D2"); myList .stream() .filter(s -> s.endsWith("1")) .map(String::toLowerCase) .sorted() .forEach(System.out::println); } }
Output
a1 b1 c1 d1
Most of the above stated operations accept some kind of lambda expression parameter using a functional interface to specify the exact behavior of the operation either stateless or non-interfering.
- Non-Interfering : Does not modify the data source of the stream
- Stateless : Is a deterministic operation. The state might change during execution.
Streams can be applied on different data structures such as collections, lists and sets. Stream API provides new methods stream() and parallelStream() to create sequential or parallel stream.
Different types of sequential streams
Using stream method on a list of objects returns a regular stream and we don’t necessarily need to create collections in order to work with streams. Following example is using a stream method.
List myList = Arrays.asList("A1", "A2", "B1", "C1", "C2","D1","D2"); myList .stream() .filter(s -> s.endsWith("1")) .map(String::toLowerCase) .sorted() .forEach(System.out::println);
Use Stream.of() to create a stream of object references.
package java8; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { List myList = Arrays.asList("A1", "A2", "B1", "C1", "C2","D1","D2"); Stream.of(myList.toArray()) .findAny() .ifPresent(System.out::println); } }
To work with primitive data type, Streams provides some special operation to work with int, long and double data types.E.g IntStream, LongStream and DoubleStream.
Using IntStream
package java8; import java.util.stream.IntStream; public class StreamEx { public static void main(String[] args) { IntStream.range(1,10) .forEach(System.out::println); } }
Output is 1,2,3,4,5,6,7,8,9
Primitive streams are behaving like regular object streams with additional terminal aggregate operations sum() and average().
package java8; import java.util.Arrays; public class StreamEx { public static void main(String[] args) { Arrays.stream(new int[] {10, 20, 30}) .map(n -> n + 1) .max() .ifPresent(System.out::println); Arrays.stream(new int[] {10, 20, 30}) .map(n -> n + 1) .average() .ifPresent(System.out::println); } }
Transforming primitive strean to object streams
package java8; import java.util.stream.IntStream; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { //String object to int stream Stream.of("a1", "a2", "a3") .map(s -> s.substring(1)) .mapToInt(Integer::parseInt) .max() .ifPresent(System.out::println); //int stream to object stream IntStream.range(1, 4) .mapToObj(i -> "a" + i) .forEach(System.out::println); //Double to int and int to object stream Stream.of(1.0, 2.0, 3.0) .mapToInt(Double::intValue) .mapToObj(i -> "a" + i) .forEach(System.out::println); } }
Output
3 a1 a2 a3 a1 a2 a3
How stream operations are processed?
Intermediate operation are lazy and will be executed only when a terminal operation is present. The following example doesn’t print anything on console. The reason is terminal operation is missing.
package java8; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { Stream.of("alpha", "bravo", "charlie") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }); } }
Adding a terminal operation, both terminal and intermediate will be executed.
package java8; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { Stream.of("alpha", "bravo", "charlie") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("forEach: " + s)); } }
Output :
map: alpha forEach: ALPHA map: bravo forEach: BRAVO map: charlie forEach: CHARLIE
The other issue with this operation is that it execute operations one after another on all elements of the stream. To avoid this behavior we can reduce the actual number of operations. In the example below if anyMatch operation returns true as the predicate applies to the given input element. Due to vertical execution of the stream, the operation will return true once element “charlie” passed. In this case map has only to be executed three time instead of mapping all the elements of stream.
package java8; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { Stream.of("alpha", "bravo", "charlie", "beta") .map(s -> { System.out.println("map: " + s); return s.toUpperCase(); }) .anyMatch(s -> { System.out.println("anyMatch: " + s); return s.endsWith("E"); }); } }
Output:
map: alpha anyMatch: ALPHA map: bravo anyMatch: BRAVO map: charlie anyMatch: CHARLIE
Normally, a stream can’t be reused and stream get closed after a terminal operation. To overcome this limitation, a new stream chain know as stream supplier which can be used for different terminal operations and construct a new stream with all intermediate operations already set up. Follow is an example which allow us to reuse the stream for different terminal operations.
package java8; import java.util.function.Supplier; import java.util.stream.Stream; public class StreamEx { public static void main(String[] args) { Supplier<Stream> streamSupplier = () -> Stream.of("alpha", "bravo", "charlie", "beta") .filter(s -> s.startsWith("a")); System.out.println(streamSupplier.get().count()); streamSupplier.get().sorted(); streamSupplier.get().forEach(System.out::println); streamSupplier.get().anyMatch(s -> true); streamSupplier.get().noneMatch(s -> true); } }
No Responses