Java8 | Stream API
What we need to know about Stream API
- It is another top subject that comes with Java 8.
- It added java.util.stream with Java8.
- A stream in Java is a sequence of data.
- A stream pipeline is the operations that run on a stream to produce results.
- The pipeline has three sections.
Source: Where the stream comes from(raw data)(from a collection or an array or using Stream methods such as of() and generate())
Intermediate operations: Transforms the stream into another one.(such as map(), filter(), distinct(), and sorted() methods in the Stream Interface)
Terminal operation: Actually produces a result.(such as sum(), collect(), forEach() and reduce() methods in the Stream Interface)
Stream Hierarchy
Stream Source
- We created Stream Source as follows,
Stream<String> stream1 = Stream.empty();
Stream<Integer> stream2 = Stream.of(1);
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6, 7);
- We can convert the list to stream
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> fromList = list.stream();
- We can create random numbers and iterate and we should use limit() method.
- If we don’t use it, it creates infinite numbers.
Stream<Double> randoms = Stream.generate(Math::random).limit(5);
Stream<Integer> oddNumbers = Stream.iterate(1, n -> n + 2).limit(5);
- And Stream Pipeline flows as follows
Arrays.stream(Object.class.getMethods()) // source
.map(Method::getName) // intermediate op
.distinct() // intermediate op
.forEach(System.out::println); // terminal operation
- We can use range and rangeClosed methods
- range doesn’t involve the endExclusive (so, 1…9)
- rangeClosed involves the endExclusive
IntStream.range(1, 10).forEach(System.out::println);
IntStream.rangeClosed(1, 5)
.map(e -> e * 2)
.forEach(System.out::println);
- We can create a stream of numbers by giving the starting and ending values.
IntStream.iterate(10, i -> i + 5)
.limit(6)
.forEach(System.out::println);
- We should careful the return type While we create a stream
// public static IntStream stream(int[] array) {
IntStream intStream = Arrays.stream(new int[] { 1, 2, 3, 4, 5 });
// public static <T> Stream<T> stream(T[] array) {
Stream<Integer> integerStream = Arrays.stream(new Integer[] { 1, 2, 3, 4, 5 });
- We can use Pattern.compile with regex
//public static Pattern compile(String regex)
// public Stream<String> splitAsStream(final CharSequence input)
Pattern.compile(" ")
.splitAsStream("java 8 streams")
.forEach(System.out::println);
Intermediate Operations
peek()
Stream<T> peek(Consumer<? super T> action);
- Returns the same elements in the stream, but also executes the passed consume lambda expression on the elements.
- The peek() method is meant primarily for debugging purposes. it helps us understand how the elements are transformed in the pipeline. do not use it in production code.
- If we don’t add some methods such as .count(), collect(), we can’t see any output. Because, according to the program, the result can be perceived as unchanged.
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e));//this code doesn't give any outputs,
//we should add this -> .collect(Collectors.toList()); <-
Another example;
Stream.of(1, 2, 3, 4, 5)
.peek(i -> System.out.printf("%d ", i))
.map(i -> i * i)
.peek(i -> System.out.printf("%d ", i)).count();
//if we didn't add the .count(), we wouldn't' see any outputs
filter()
Stream<T> filter(Predicate<? super T> predicate);
- The filter() method in the Stream interface is used for removing the elements that do not match the given condition.
- Here is a simple example that uses Stream’s filter() method for removing odd integers
IntStream.rangeClosed(0,10)
.filter(i -> (i % 2 == 0))
.map(i -> i*3)
.forEach(System.out::println);
- Here is a simple example that uses Stream’s filter() method for removing empty Strings
Stream.of("Hasan", "Kadir", "", "Java")
.filter(name -> !name.isEmpty())
.forEach(System.out::println);
distinct()
Stream<T> distinct();
- The distinct() method returns a stream with duplicate values remove
Stream.of("Java", "Java", "Java8", "JAva")
.distinct()
.forEach(System.out::println);
skip() and limit()
- The skip(n) method is an intermediate operation that discards the first n elements of a stream. The n parameter can’t be negative, and if it’s higher than the size of the stream, skip() returns an empty stream.
- The limit(n) method is another intermediate operation that returns a stream not longer than the requested size. As before, the n parameter can’t be negative.
Stream<Integer> s = Stream.iterate(10, n -> n + 2);
s.skip(3).limit(2).forEach(System.out::println); //16, 18
map()
- The map() method on streams is for transforming data. Don’t confuse it with the Map interface, which maps keys to values
Stream<String> s = Stream.of("hasan", "kadir", "demircan");
s.map(String::length).forEach(System.out::print); // 558
- We can use it to find the only data in the list we have.
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5", "6");
List<Integer> even = numbers.stream()
.map(Integer::valueOf)
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
- Another map example
List<Match> matchList = Arrays.asList(
new Match("FootballMatch", 11),
new Match("BasketballMatch", 7),
new Match("Soccer", 7),
new Match("Tennis", 2)
);
Optional<String> matchName = matchList.stream()
.map(Match::getName)
.filter(name -> name.endsWith("Match"))
.sorted()
.findFirst();
System.out.println("Result: " + matchName.orElse("match no found"));
flatMap()
- Java 8 Stream flatMap() method is used to flatten a Stream of collections to a stream of objects. The objects are combined from all the collections in the original Stream.
- The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream and then flattening the resulting elements into a new stream.
Stream.flatMap()
helps in convertingStream<Collection<T>>
toStream<T>
.
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap() vs Map()
- The Stream’s map() method produces or returns a single result value, which is sent to the Output stream
- The Stream’s
flatMap()
method produces or returns a Stream of elements - The
map()
method is only used for modification while theflatMap()
method is used for both flattening and modification
flatMap() = Flattening + map()
For Example;
When we use Map()
String[] string = "you never know what you have until you clean your room".split(" ");
Arrays.stream(string)
.map(word -> word.split(""))
.distinct()
.forEach(System.out::println);//This code prints gibberish like this:Ljava.lang.String;@5f184fc6[Ljava.lang.String;@3feba861[Ljava.lang.String;@5b480cf9[
- Why? Because the word.split() returns a String[] and distinct() removes duplicate references. Since the elements in the stream are of type String[], the forEach() prints calls the default toString() implementation that prints something that is not human-readable.
- One way to solve this problem is to use Arrays.stream() again on word.split(“”), and convert the resulting streams into individual entries (i.e., “flatten” the streams) as in: flatMap(word -> Arrays.stream(word.split(“”))).
When we use flatMap()
String[] string = "you never know what you have until you clean your room".split(" ");
Arrays.stream(string)
.flatMap(word -> Arrays.stream(word.split("")))
.distinct()
.forEach(System.out::println);//This code prints gibberish like this:
y
o
u
n
(etc..)
Another example with flatMap()
- We imagine that we have two arrays within ArrayList,
- So, how can we process this case(sorting, multiply, etc..)?
List<List<Integer>> intsOfInts = Arrays.asList(Arrays.asList(1, 3, 5), Arrays.asList(2, 4));
intsOfInts.stream()
.flatMap(Collection::stream)
.sorted()
.map(i -> i * i)
.forEach(System.out::println);
- When we investigate step by step;
sorted()
- Sorts the elements of a Stream using natural ordering; the element class must implement the Comparable interface.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
Example for sorted() method;
Stream<String> s = Stream.of("brown-", "bear-", "bee-");
s.sorted().forEach(System.out::print);
// bear-bee-brown-
Note: We can get ‘java.lang.OutOfMemoryError: Java heap space
’ when we use sorted()
For example;
- This example tries to generate infinite String and tries sorted. Then get
OutOfMemoryError
Stream.generate(() -> "Elsa")
.filter(n -> n.length() == 4)
.sorted()
.limit(2)
.forEach(System.out::println);
- We can change the code like that,
Stream.generate(() -> "Elsa")
.filter(n -> n.length() == 4)
.limit(2)
.sorted()
.forEach(System.out::println);
Terminal Operation
count()
- The count() method determines the number of elements in a finite stream.
- We can only call the terminal method once on a stream.
Stream<String> s1 = Stream.of("k1", "k2", "k3");
long countElement = s1.count();
System.out.println(countElement);
min() and max()
- We can find min or max values that the String or Array.
- It is checked to alphabetical order
Stream<String> stream1 = Stream.of("monkey", "ape", "bonobo");
Stream<String> stream2 = Stream.of("monkey", "ape", "bonobo");
Comparator<String> comp = Comparator.comparingInt(String::length);
Optional<String> min = stream1.min(comp);
Optional<String> max = stream2.max(comp);
System.out.println(min);
System.out.println(max);
Another example,
String[] string = "you never know what you have until you clean your room".split(" ");
String[] string2 = "you never know what you have until you clean your room".split(" ");
String[] string3 = "you never know what you have until you clean your room".split(" ");
System.out.println(Arrays.stream(string).min(String::compareTo).get());
System.out.println(Arrays.stream(string2).max(String::compareTo).get());
Comparator<String> lengthCompare = Comparator.comparingInt(String::length);
System.out.println(Arrays.stream(string3).min(lengthCompare).get());
findAny() and findFirst()
- findAny(), returns one of the elements from the stream; if there is no element present in the stream, it returns an empty Optional<T> object.
- findFirst(), returns the first element from the stream; if there is no element present in the stream, it returns an empty Optional<T> object.
- findFirst() return the first elements of the stream but findAny() is free to select any element in the stream. (especially when we use parallel streaming)
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
Stream<String> infinite = Stream.generate(() -> "chimp");
s.findAny().ifPresent(System.out::println); // monkey
infinite.findAny().ifPresent(System.out::println); // chimp
Another example;
// DoubleStream filter(DoublePredicate predicate);
OptionalDouble temperature = DoubleStream.of(-10.1, -5.4, 6.0, -3.4, 8.9, 2.2)
.filter(temp -> temp > 0)
.findFirst();
// DoubleStream filter(DoublePredicate predicate);
OptionalDouble temperature2 = DoubleStream.of(-10.1, -5.4, 6.0, -3.4, 8.9, 2.2)
.filter(temp -> temp > 0)
.findAny();
System.out.println(temperature);
System.out.println(temperature2);
anyMatch(), allMatch(), noneMatch()
- anyMatch(), returns true if there is any elements in the stream that matches the given predicate. Returns false if the stream is empty or if there are no matching elements.
- allMatch(), returns true only if all elements in the stream matches the given predicate. Returns true if the stream is empty without evaluating the predicate.
- noneMatch(), returns true only if none of the elements in the stream matches the given predicate. Returns true if the stream is empty without evaluating the predicate!
- These may or may not terminate for infinite streams. It depends on the data
- Like the find methods, they are not reductions because they do not
- Necessarily look at all of the elements.
List<String> list = Arrays.asList("monkey", "2", "chimp");
Stream<String> infinite = Stream.generate(() -> "chimp");
Stream<String> infinite2 = Stream.generate(() -> "chimp");
Predicate<String> pred = x -> Character.isLetter(x.charAt(0));
System.out.println(list.stream().anyMatch(pred)); // true
System.out.println(list.stream().allMatch(pred)); // false
System.out.println(list.stream().noneMatch(pred));// false
// infinitive
System.out.println(infinite.anyMatch(pred)); // true
// System.out.println(infinite.allMatch(pred)); // true -> endless loop
System.out.println(infinite2.noneMatch(pred)); // false
forEach()
- The forEach() method on the stream is the terminal operation in the pipeline.
- Java provides a new method forEach() to iterate the elements.
void forEach(Consumer<? super T> action);
For example;
Stream<String> s = Stream.of("Monkey", "Gorilla", "Bonobo");
s.forEach(System.out::print); // MonkeyGorillaBonobo
// Consumer
// void accept(T t);
List<String> list = new ArrayList<>();
s.forEach((str) ->
{
String sUpper = str.toUpperCase();
list.add(sUpper);
});
reduce()
- The reduce() method combines a stream into a single object. As you can tell from the name, it is a reduction.
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
For Example;
Stream<Integer> stream = Stream.of(3, 5, 6);
Stream<Integer> stream2 = Stream.of(3, 5, 6);
// T reduce(T identity, BinaryOperator<T> accumulator);
System.out.println(stream.reduce(10, (a, b) -> a * b));
//Sout: 900
// Optional<T> reduce(BinaryOperator<T> accumulator);
System.out.println(stream2.reduce((a, b) -> a * b));
//Sout: 900Optional[90]
For Example;
- Instead of writing for forEach for concating process , we can use reduce() and the words will be concat.
Stream<String> stream = Stream.of("w1", "o2", "l3", "f4");
String word = stream.reduce("X", String::concat);
System.out.println(word); // Xw1o2l3f4
or more complicated example;
Stream<String> stream2 = Stream.of("w", "o", "l", "f");
String word2 = stream2.reduce("", (s, c) -> s + c +"--");
System.out.println(word2); //w--o--l--f--
collect()
- The collect() method is a special type of reduction called a mutable reduction.
- It is more efficient than a regular reduction because we use the same mutable object while accumulating.
<R,A> R collect(Collector<? super T, A,R> collector)
For example;
Collectors.toList()
String frenchCounting = "un:deux:trois:quatre";
List<String> gmailList = Pattern.compile(":")
.splitAsStream(frenchCounting)
.collect(Collectors.toList());
//[un, deux, trois, quatre]
Collectors.toSet()
String[] roseQuote = "a rose is a rose is a rose".split(" ");
Set<String> words = Arrays.stream(roseQuote).collect(Collectors.toSet());
toMap()
Map<String, Integer> nameLength = Stream.of("Arnold", "Alois", "Schwarzenegger")
.collect(Collectors.toMap(name -> name, String::length));
StringBuilder()
Stream<String> stream = Stream.of("w", "o", "l", "f");
StringBuilder word = stream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append);
treeSet()
Stream<String> stream = Stream.of("w", "o", "l", "f");
TreeSet<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
I would like to finish my examples,
I hope it helps to understand stream flow :)
Github: Stream
References:
Stream
Oracle Study Books