A Sequence of Elements
No solution without a problem
Please use a little bit of imagination.
Let's try something
public void naive01()
{
final List<String> names =
Arrays.asList(
"B", "Sara", "", "HARRY", "will",
"Tom", "Vic", "Alf", "MAria", null);
// our intermediate result
final List<String> result = new ArrayList<>();
// process the input
for (String name : names)
{
// don't do empty or single letters
if (name != null && name.length() > 1)
{
// fix the casing and store result
result.add(
name.substring(0, 1).toUpperCase()
+ name.substring(1).toLowerCase());
}
}
// sort
Collections.sort(result);
// display the first three
for (int i = 0; i < Math.min(3, result.size()); i++)
{
System.out.println(result.get(i));
}
}
Extremely bulky and hard to see what is done where.
The same result by using streams
public void naive01()
{
final List<String> names =
Arrays.asList(
"B", "Sara", "", "HARRY", "will",
"Tom", "Vic", "Alf", "MAria", null);
// our intermediate result
final List<String> result = new ArrayList<>();
// process the input
for (String name : names)
{
// don't do empty or single letters
if (name != null && name.length() > 1)
{
// fix the casing and store result
result.add(
name.substring(0, 1).toUpperCase()
+ name.substring(1).toLowerCase());
}
}
// sort
Collections.sort(result);
// display the first three
for (int i = 0; i < Math.min(3, result.size()); i++)
{
System.out.println(result.get(i));
}
}
public void stream01()
{
final List<String> names =
Arrays.asList(
"B", "Sara", "", "HARRY", "will",
"Tom", "Vic", "Alf", "MAria", null);
names.stream()
.filter(s -> s != null)
.filter(s -> s.length() > 1)
.map(s -> s.toLowerCase())
.map(s -> s.substring(0, 1).toUpperCase())
.sorted()
.limit(3)
.forEach(System.out::println);
}
That is lean and clear, isn't?
The long version
Streams are similar to our reader or writers in the sense of nesting... just fancier.
Just in case version 1 was too much
// print 1 to 10
IntStream.range(1, 11) // inclusive, exclusive!
.forEach(System.out::println);
// print even numbers of 1 to 10
IntStream.range(1, 11)
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
// return the sum of all even numbers between 1 and 100
final int sum = IntStream.range(1, 101)
.filter(i -> i % 2 == 0)
.sum();
That is quite simple, isn't it?
Where to get a stream from?
stream() and parallelStream()Arrays.stream(Object[]) produces streamsIntStream, LongStream, DoubleStreamStream.of(T... values)String.chars() produces a stream of chars// just numbers
IntStream.range(1, 11)...;
// from a list
final List<String> names = Arrays.asList("B", "Sara", "MAria");
names.stream()...;
// from a entry set of a map
final Map<String, Integer> map = new HashMap<>();
map.entrySet().stream()...;
// stream of elements
Stream.of("a1", "a2", "a3")...;
final String[] array = new String[]{"a", "b", "c"};
Arrays.stream(array)...;
"Streamable" is not an interface. If a source should support streaming it has to implement a method returning a Stream<E>.
void forEach(Consumer<? super T> action)
@Test
public void forEach()
{
Stream.of("d2", "a2", "b1", "b3", "c")
.forEach(System.out::println);
IntStream.range(1, 11)
.forEach(System.out::println);
"Foobar is a nice word".chars()
.forEach(c -> System.out.print(Character.valueOf((char) c)));
}
Often used terminal operation, exists also without a stream as source.
Stream<T> filter(Predicate<? super T> predicate)
Predicate<T> as function@Test
public void filters()
{
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> true)
.forEach(System.out::println);
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.length() > 1)
.filter(s -> Integer.parseInt(s.substring(1)) == 1)
.forEach(System.out::println);
"Foobar is a nice word".chars()
.filter(c -> c > 32)
.forEach(c -> System.out.print(Character.valueOf((char) c)));
}
Filters should be simple to make the code easy to read.
Stream<T> distinct()
@Test
public void distinct()
{
Stream.of("a", "b", "a", "b", "c")
.distinct()
.forEach(System.out::println);
}
long count()
@Test
public void count()
{
long result = Stream.of("a", "b", "a", "b", "c")
.distinct()
.count();
}
We just need stuff to play with.
boolean *Match(Predicate<? super T> predicate)
allMatch, noneMatch, anyMatch@Test
public void matches()
{
// we have at least one match, false when stream is empty
boolean b1 = Stream.of("a", "b", "a", "b", "c").anyMatch(s -> s.equals("a"));
// everything matches, true when stream is empty
boolean b2 = Stream.of("a", "b", "a", "b", "c").allMatch(s -> s.equals("a"));
// none matches, true when stream is empty
boolean b3 = Stream.of("a", "b", "a", "b", "c").noneMatch(s -> s.equals("g"));
}
Useful for determining if we have needed data.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
@Test
public void map()
{
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> new StringBuilder(s))
.map(s -> s.reverse())
.forEach(System.out::println);
Stream.of("d2", "a2", "b1", "b3", "c")
.mapToInt(s -> s.length())
.map(i -> i + 1)
.forEach(System.out::println);
"Foobar is a nice word".chars()
.map(Character::toUpperCase)
.mapToObj(c -> {
String s = String.valueOf(c);
return s + s;
})
.forEach(System.out::print);
}
Keep it simple and efficient.
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
@Test
public void flatMap()
{
// need all words larger 2 characters
Stream.of("Java in Action", "BDD in Action", "Maven in Action")
.flatMap(s -> Arrays.stream(s.split("\\s")))
.filter(s -> s.length() > 2)
.forEach(System.out::println);
}
Takes a moment to understand flatMap vs. map.
Stream<T> peek(Consumer<? super T> action)
@Test
public void peek()
{
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Afrer filter: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("After map: " + e))
.forEach(s -> {});
}
After Filter: three
After Map: THREE
After Filter: four
After Map: FOUR
Use with caution.
Stream<T> limit(long n)
@Test
public void limit()
{
Stream.of("a", "b", "a", "b", "c")
.limit(1)
.forEach(System.out::println);
}
Stream<T> skip(long n)
@Test
public void skip()
{
Stream.of("a", "b", "a", "b", "c")
.skip(2)
.forEach(System.out::println);
}
Nice little things.
Stream<T> sorted()
Comparable@Test
public void sorted()
{
Stream.of("a", "b", "a", "b", "c")
.sorted()
.forEach(System.out::println);
}
Stream<T>
sorted(Comparator<? super T> comparator)
Comparable@Test
public void sorted2()
{
Stream.of("a", "b", "a", "b", "c")
.sorted((s1, s2) -> s2.compareTo(s1))
.forEach(System.out::println);
}
@Test
public void sorted2()
{
Stream.of(new Vehicle("Maybach", 20), new Vehicle("Audi", 10))
.sorted(
Comparator.comparing(Vehicle::getBrand)
.thenComparing(Comparator.comparing(Vehicle::getAge)))
.forEach(System.out::println);
}
Get fancy with sorting.
How to get to meaningful results
So far, we have gotten only a few results back
find or match without knowing anything about reduction aka we reduced our stream to a single resultreduce()collect()Collector that can run reduction operations, hence the naming
is not absolutely cleanThe naming will become a little complicated.
Provided reduction and collection operations
max()min()count()average()summaryStatistics()sum()boolean allMatch(Predicate)boolean anyMatch(Predicate)boolean noneMatch(Predicate)Optional findAny()Optional findFirst()These are easy to remember and use.
How collecting works aka mutable reductions
ArrayList::newArrayList::addArrayList::addAllCollections.unmodifiableListIt gets more complicated before it gets simpler.
Let's collect the hard way first
// java.util.stream.Stream
<R> R collect(
Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
List<String> asList1 = Stream.of("a", "b", "a", "b", "c")
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
List<String> asList2 = Stream.of("a", "b", "c", "d", "e")
.collect(
() -> new ArrayList<String>(),
(result, element) -> result.add(element),
(result, intermediate) -> result.addAll(intermediate));
R: the type of the resultsupplier: a function that creates a new result container. For parallel execution it must return a fresh value each time.accumulator: an associative, non-interfering, stateless function for incorporating an additional element into a resultcombiner: an associative, non-interfering, stateless function for combining two results into a new result, needed for parallel operations
Make collect operations reusable
java.util.stream.Collector defines an interfaceof methodjava.util.stream.Collectors defines a huge range of ready to use collectorsWe get to predefined Collectors later.
Basic custom collector usage
// java.util.stream.Stream
<R,A> R collect(Collector<? super T,A,R> collector)
// as seen before
List<String> asList1 = Stream.of("a", "b", "c", "d", "e")
.collect(
() -> new ArrayList<String>(),
(result, element) -> result.add(element),
(result, intermediate) -> result.addAll(intermediate));
final Collector<String, List<String>, List<String>> listCollector = Collector.of(
() -> new ArrayList<String>(),
(result, element) -> result.add(element),
(result, intermediate) -> {
result.addAll(intermediate);
return result;
});
final List<String> asList3 = Stream.of("a", "b", "c", "d", "e")
.collect(listCollector);
// interface java.util.stream.Collector
static <T, R> Collector<T, R, R> of(
Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Collector.Characteristics... characteristics)
T: the type of input elementsR: the type of intermediate accumulation result, and final resultsupplier: the supplier function
accumulator: the accumulator function
combiner: the combiner function
characteristics: the collector characteristics
It will get even fancier... stay tuned.
Enhance collectors with finishers
static <T, A, R> Collector<T, A, R> of(
Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Function<A, R> finisher,
Collector.Characteristics... characteristics)
T: the type of input elementsR: the type of intermediate accumulation result, and final resultsupplier: the supplier function
accumulator: the accumulator function
combiner: the combiner function
finisher: the finisher function
characteristics: the collector characteristics
final Collector<String, List<String>, List<String>> withFinisher =
Collector.of(
() -> new ArrayList<String>(),
(result, element) -> result.add(element),
(result, intermediate) -> {
result.addAll(intermediate); return result;
},
(result) -> {
Collections.shuffle(result);
return result;
});
final List<String> asList4 = Stream.of("a", "b", "c", "d", "e")
.collect(withFinisher);
Ready to use collectors
java.util.stream.Collectors define many ready to use collectorsYeah, naming and redudandancy...
Examples of Math operations
// summing
long total = Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(Collectors.summingLong(Vehicle::getValue));
// averaging
double avgAge = Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(Collectors.averagingDouble(Vehicle::getAge));
// stats
final IntSummaryStatistics s =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(Collectors.summarizingInt(Vehicle::getAge));
/*
void combine(IntSummaryStatistics other)
double getAverage()
long getCount()
int getMax()
int getMin()
long getSum()
*/
// count
final long count =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(
Collectors.minBy(
.collect(Collectors.counting());
// min
final Optional<Vehicle> newest =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(
Collectors.minBy(
Comparator.comparingInt(Vehicle::getAge)));
// max
final Optional<Vehicle> newest =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000))
.collect(
Collectors.maxBy(
Comparator.comparingInt(Vehicle::getAge)));
Pretty simple...
How to simply "collect" all together
// collect in a list
final List<String> result = Stream.of("a", "b", "c", "b")
.collect(Collectors.toList());
// collect with a set
final Set<String> result = Stream.of("a", "b", "c", "b")
.collect(Collectors.toSet());
// map to a map with mapping definition
final Map<String, Integer> result =
Stream.of("a", "b", "c", "b")
.collect(
Collectors.toMap(String::toString, String::length));
// provide your own collection and have a sorted result
final Set<String> result =
Stream.of("a", "b", "c", "b")
.collect(
Collectors.toCollection(TreeSet::new));
That is still simple.
How to do two-dimensional partitions
PredicateMap<Boolean, List<T>>// partition cars by restored state
final Map<Boolean, List<Vehicle>> result =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000, true))
.collect(Collectors.partitioningBy(Vehicle::isRestored));
{
false = [Vehicle [brand=Maybach, age=20, value=10000, restored=false]],
true = [Vehicle [brand=Audi, age=10, value=20000, restored=true]]
}
// partition cars by restored state first
// and collected by age per partition next
final Map<Boolean, List<Vehicle>> result =
Stream.of(
new Vehicle("Maybach", 20, 10000), new Vehicle("Audi", 10, 20000, true))
.collect(
Collectors.partitioningBy(Vehicle::isRestored,
Collectors.averagingInt(Vehicle::getAge)));
{
false = 20.0,
true = 10.0
}
So what is groupingBy...
How to do n-dimensional partitions
Map<K, List<T>>groupingByConcurrent// group by brand
final Map<String, List<Vehicle>> result =
Stream.of(
new Vehicle("Maybach", 20, 10000),
new Vehicle("Audi", 10, 20000, true),
new Vehicle("Audi", 15, 232000))
.collect(Collectors.groupingBy(Vehicle::getBrand));
{
Maybach = [
Vehicle [brand=Maybach, age=20, value=10000, restored=false]
],
Audi = [
Vehicle [brand=Audi, age=10, value=20000, restored=true],
Vehicle [brand=Audi, age=15, value=232000, restored=false]
]
}
// group by brand and collect the values
final Map<String, Double> result =
Stream.of(
new Vehicle("Maybach", 20, 100000),
new Vehicle("Audi", 10, 100000, true),
new Vehicle("Audi", 15, 200000))
.collect(
Collectors.groupingBy(Vehicle::getBrand,
Collectors.averagingInt(Vehicle::getValue)));
{
Maybach = 100000.0,
Audi = 150000.0
}
And of course you can go nuts with cascaded groupings.