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
, DoubleStream
Stream.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::new
ArrayList::add
ArrayList::addAll
Collections.unmodifiableList
It 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
Predicate
Map<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.