© Copyright Azul Systems 2016
© Copyright Azul Systems 2015
@speakjava
Streams in JDK 8:
The Good, The Bad & The Ugly
Simon Ritter
Deputy CTO, Azul Systems
1
© Copyright Azul Systems 2016
Goals Of This BoF
 Look at Streams code
 Discuss whether it’s good, bad or ugly
 Learn some things to do
 And some things not to do
 Idea came about because someone didn’t like my code
2
© Copyright Azul Systems 2016
Problem To Solve
 Code to extract key-value pairs from HTTP parameters
 Results:
– action, validate
– key, [10, 22, 10, 33]
3
action=validate&key=10&key=22&key=10&key=33
© Copyright Azul Systems 2016
Non-Stream Code
private void parseQuery(String query, Map parameters) {
if (query != null) {
String pairs[] = query.split("[&]");
for (String pair : pairs) {
String param[] = pair.split("[=]");
String key = null;
String value = null;
if (param.length > 0)
key = URLDecoder.decode(param[0], ENC);
if (param.length > 1)
value = URLDecoder.decode(param[1], ENC);
© Copyright Azul Systems 2016
Non-Stream Code
if (parameters.containsKey(key)) {
Object obj = parameters.get(key);
if (obj instanceof List) {
List values = (List)obj;
values.add(value);
} else if(obj instanceof String) {
List values = new ArrayList();
values.add((String)obj);
values.add(value);
parameters.put(key, values);
}
} else
parameters.put(key, value);
© Copyright Azul Systems 2016
My Stream Code (First Pass)
 Two problems
– Returns a null (ooh, bad!)
– Does not URLDecoder.decode() like original code
 decode() throws an exception
private Map<String, List<String>> parseQuery(String query) {
return (query == null) ? null : Arrays.stream(query.split("[&]"))
.collect(groupingBy(s -> (s.split("[=]"))[0],
mapping(s -> (s.split("[=]"))[1], toList())));
}
© Copyright Azul Systems 2016
My Stream Code (Updated)
AbstractMap.SimpleImmutableEntry<String, String>
splitKeyValue(String keyValue) {
String[] parts = keyValue.split(“[=]”);
try {
return new AbstractMap.SimpleImmutableEntry<>(
URLDecoder.decode(parts[0], ENC),
URLDecoder.decode(parts[1], ENC));
} catch (UnsupportedEncodingException uee) {
logger.warning(“Exception processing key-value pair”);
return null;
}
}
© Copyright Azul Systems 2016
My Stream Code (Updated)
private Map<String, List<String>> parseQuery(String query) {
return (query == null) ? new HashMap<String, List<String>>() :
Arrays.stream(query.split("[&]"))
.map(this::splitKeyValue)
.filter(e -> e != null)
.collect(groupingBy(Map.Entry::getKey,
mapping(Map.Entry::getValue, toList())));
}
© Copyright Azul Systems 2016
Stream Reuse (Good or Bad?)
IntStream stream = IntStream.of(1, 10);
stream.forEach(System.out::println);
/* Efficient use of resources? */
stream.forEach(System.out::println);
IllegalStateException: stream has already been operated upon or closed
Streams are single use
© Copyright Azul Systems 2016
Filter Confusion
Files.walk(Paths.get("."))
.filter(p -> !p.toFile().getName().startsWith("."))
.forEach(System.out::println);
Stream contains all
files and directories
Only checking the file name
(not parent directory)foo
bar
.home/baz
© Copyright Azul Systems 2016
Don’t Forget The Terminator
IntStream.range(1, 5)
.peek(System.out::println)
.peek(i -> {
if (i == 5)
throw new RuntimeException("bang!");
});
System.out.println(“Done!”);
Done!
© Copyright Azul Systems 2016
Streams Can Be Infinite
Even when you don’t want them to be
IntStream.iterate(0, i -> (i + 1) % 2)
.distinct()
.limit(10)
.forEach(System.out::println);
Infinite stream of 0, 1, 0, 1...
Only 0 and 1
10 elements will
never be found in
the stream
Prints
0
1
(But code never proceeds)
© Copyright Azul Systems 2016 13
Concatenate the first word of each string in a List
Simple Exercise
StringBuilder sb = new StringBuilder();
input.forEach(s -> sb.append(s.charAt(0)));
String result = sb.toString();
String result = input.stream()
.map(s -> s.substring(0, 1))
.reduce("", (a, b) -> a + b);
© Copyright Azul Systems 2016
Simple Exercise
14
StringBuilder sb = new StringBuilder();
Optional<String> optResult = input.stream()
.map(s -> s.substring(0, 1)
.reduce((a, b) -> sb.append(b).toString());
String result = optResult.orElse(“”);
FAIL!!
result = “bcdef”
© Copyright Azul Systems 2016 15
Simple Exercise
StringBuilder sb = new StringBuilder();
Optional<String> optResult = input.stream()
.map(s -> s.substring(0, 1)
.reduce((a, b) -> {
if (a.length() == 1)
sb.append(a);
return sb.append(b).toString();
});
String result = optResult.orElse(“”);
© Copyright Azul Systems 2016 16
Simple Exercise
String result = input.stream()
.map(s -> s.substring(0, 1))
.collect(Collector.of(
StringBuilder::new, // Supplier
StringBuilder::append, // Accumulator
StringBuilder::append, // Combiner
StringBuilder::toString, // Finisher
new Collector.Characteristics[0]));
© Copyright Azul Systems 2016 17
Simple Exercise
String result = input.stream()
.map(s -> s.substring(0, 1))
.collect(Collectors.joining());
© Copyright Azul Systems 2016
Which Is Better, And Why?
18
points.stream()
.filter(p -> p.isInView())
.map(p -> p.invert())
.forEach(p -> System.out.println(p));
points.stream()
.filter(GraphicPoint::isInView)
.map(GraphicPoint::invert)
.forEach(System.out::println);
© Copyright Azul Systems 2016
Parallel Streams Are Faster, Right?
 Not guaranteed
 A parallel stream will definitey do more work
– It just might finish quicker
 Parallel streams use the common fork-join pool
– Defaults to number of threads = number of CPUs
© Copyright Azul Systems 2016
Nesting Parallel Streams
List<Review> result = reviews.parallelStream()
.filter(review -> review.getImages()
.stream()
.parallel()
.anyMatch(image -> image.getIdImage() == 123))
.collect(Collectors.toList());
This will work, but will probably give worse
performance than using a serial stream
© Copyright Azul Systems 2016
Multi-Sort (The Bad)
patientRecords.stream()
.filter(this::isValidRecord)
.sorted(comparing(Patient::getName))
.sorted(comparing(Patient::getPhysician))
.sorted(comparing(Patient::getMedication))
.collect(toList());
© Copyright Azul Systems 2016
Multi-Sort (The Good)
patientRecords.stream()
.filter(this::isValidRecord)
.sorted(comparing(Patient::getMedication)
.thenComparing(comparing(Patient::getPhysician))
.thenComparing(comparing(Patient::getName)))
.collect(toList());
© Copyright Azul Systems 2016
Imperative Streams
LongAdder newMethodCount = new LongAdder();
map.get(class).stream()
.forEach(method -> {
output.println(val);
if (isNewMethod(class, method))
newMethodCount.increment();
});
© Copyright Azul Systems 2016
A Bit More Functional
int count = functionalParameterMethodMap.get(c).stream()
.mapToInt(m -> {
int newMethod = 0;
output.println(m);
if (isNewMethod(c, m))
newMethod = 1;
return newMethod
})
.sum();
There is still state
being modified in the
Lambda
© Copyright Azul Systems 2016
Almost Functional (But Not Quite)
int count = functionalParameterMethodMap.get(nameOfClass)
.stream()
.peek(method -> output.println(method))
.mapToInt(m -> isNewMethod(nameOfClass, m) ? 1 : 0)
.sum();
Strictly speaking printing is
a side effect, which is not
purely functional
© Copyright Azul Systems 2016
Conclusions
 Streams are powerful
– With great power comes great responsibility
 Try not to think imperatively
– forEach is good, but not for each (and every) situation
 Part of streams power is clarity of code
– That doesn’t mean as few lines as possible!
 Parallel does not guarantee faster
26
© Copyright Azul Systems 2016
© Copyright Azul Systems 2015
@speakjava
Streams in JDK 8:
The Good, The Bad & The Ugly
Simon Ritter
Deputy CTO, Azul Systems
27

Streams: The Good, The Bad And The Ugly

  • 1.
    © Copyright AzulSystems 2016 © Copyright Azul Systems 2015 @speakjava Streams in JDK 8: The Good, The Bad & The Ugly Simon Ritter Deputy CTO, Azul Systems 1
  • 2.
    © Copyright AzulSystems 2016 Goals Of This BoF  Look at Streams code  Discuss whether it’s good, bad or ugly  Learn some things to do  And some things not to do  Idea came about because someone didn’t like my code 2
  • 3.
    © Copyright AzulSystems 2016 Problem To Solve  Code to extract key-value pairs from HTTP parameters  Results: – action, validate – key, [10, 22, 10, 33] 3 action=validate&key=10&key=22&key=10&key=33
  • 4.
    © Copyright AzulSystems 2016 Non-Stream Code private void parseQuery(String query, Map parameters) { if (query != null) { String pairs[] = query.split("[&]"); for (String pair : pairs) { String param[] = pair.split("[=]"); String key = null; String value = null; if (param.length > 0) key = URLDecoder.decode(param[0], ENC); if (param.length > 1) value = URLDecoder.decode(param[1], ENC);
  • 5.
    © Copyright AzulSystems 2016 Non-Stream Code if (parameters.containsKey(key)) { Object obj = parameters.get(key); if (obj instanceof List) { List values = (List)obj; values.add(value); } else if(obj instanceof String) { List values = new ArrayList(); values.add((String)obj); values.add(value); parameters.put(key, values); } } else parameters.put(key, value);
  • 6.
    © Copyright AzulSystems 2016 My Stream Code (First Pass)  Two problems – Returns a null (ooh, bad!) – Does not URLDecoder.decode() like original code  decode() throws an exception private Map<String, List<String>> parseQuery(String query) { return (query == null) ? null : Arrays.stream(query.split("[&]")) .collect(groupingBy(s -> (s.split("[=]"))[0], mapping(s -> (s.split("[=]"))[1], toList()))); }
  • 7.
    © Copyright AzulSystems 2016 My Stream Code (Updated) AbstractMap.SimpleImmutableEntry<String, String> splitKeyValue(String keyValue) { String[] parts = keyValue.split(“[=]”); try { return new AbstractMap.SimpleImmutableEntry<>( URLDecoder.decode(parts[0], ENC), URLDecoder.decode(parts[1], ENC)); } catch (UnsupportedEncodingException uee) { logger.warning(“Exception processing key-value pair”); return null; } }
  • 8.
    © Copyright AzulSystems 2016 My Stream Code (Updated) private Map<String, List<String>> parseQuery(String query) { return (query == null) ? new HashMap<String, List<String>>() : Arrays.stream(query.split("[&]")) .map(this::splitKeyValue) .filter(e -> e != null) .collect(groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toList()))); }
  • 9.
    © Copyright AzulSystems 2016 Stream Reuse (Good or Bad?) IntStream stream = IntStream.of(1, 10); stream.forEach(System.out::println); /* Efficient use of resources? */ stream.forEach(System.out::println); IllegalStateException: stream has already been operated upon or closed Streams are single use
  • 10.
    © Copyright AzulSystems 2016 Filter Confusion Files.walk(Paths.get(".")) .filter(p -> !p.toFile().getName().startsWith(".")) .forEach(System.out::println); Stream contains all files and directories Only checking the file name (not parent directory)foo bar .home/baz
  • 11.
    © Copyright AzulSystems 2016 Don’t Forget The Terminator IntStream.range(1, 5) .peek(System.out::println) .peek(i -> { if (i == 5) throw new RuntimeException("bang!"); }); System.out.println(“Done!”); Done!
  • 12.
    © Copyright AzulSystems 2016 Streams Can Be Infinite Even when you don’t want them to be IntStream.iterate(0, i -> (i + 1) % 2) .distinct() .limit(10) .forEach(System.out::println); Infinite stream of 0, 1, 0, 1... Only 0 and 1 10 elements will never be found in the stream Prints 0 1 (But code never proceeds)
  • 13.
    © Copyright AzulSystems 2016 13 Concatenate the first word of each string in a List Simple Exercise StringBuilder sb = new StringBuilder(); input.forEach(s -> sb.append(s.charAt(0))); String result = sb.toString(); String result = input.stream() .map(s -> s.substring(0, 1)) .reduce("", (a, b) -> a + b);
  • 14.
    © Copyright AzulSystems 2016 Simple Exercise 14 StringBuilder sb = new StringBuilder(); Optional<String> optResult = input.stream() .map(s -> s.substring(0, 1) .reduce((a, b) -> sb.append(b).toString()); String result = optResult.orElse(“”); FAIL!! result = “bcdef”
  • 15.
    © Copyright AzulSystems 2016 15 Simple Exercise StringBuilder sb = new StringBuilder(); Optional<String> optResult = input.stream() .map(s -> s.substring(0, 1) .reduce((a, b) -> { if (a.length() == 1) sb.append(a); return sb.append(b).toString(); }); String result = optResult.orElse(“”);
  • 16.
    © Copyright AzulSystems 2016 16 Simple Exercise String result = input.stream() .map(s -> s.substring(0, 1)) .collect(Collector.of( StringBuilder::new, // Supplier StringBuilder::append, // Accumulator StringBuilder::append, // Combiner StringBuilder::toString, // Finisher new Collector.Characteristics[0]));
  • 17.
    © Copyright AzulSystems 2016 17 Simple Exercise String result = input.stream() .map(s -> s.substring(0, 1)) .collect(Collectors.joining());
  • 18.
    © Copyright AzulSystems 2016 Which Is Better, And Why? 18 points.stream() .filter(p -> p.isInView()) .map(p -> p.invert()) .forEach(p -> System.out.println(p)); points.stream() .filter(GraphicPoint::isInView) .map(GraphicPoint::invert) .forEach(System.out::println);
  • 19.
    © Copyright AzulSystems 2016 Parallel Streams Are Faster, Right?  Not guaranteed  A parallel stream will definitey do more work – It just might finish quicker  Parallel streams use the common fork-join pool – Defaults to number of threads = number of CPUs
  • 20.
    © Copyright AzulSystems 2016 Nesting Parallel Streams List<Review> result = reviews.parallelStream() .filter(review -> review.getImages() .stream() .parallel() .anyMatch(image -> image.getIdImage() == 123)) .collect(Collectors.toList()); This will work, but will probably give worse performance than using a serial stream
  • 21.
    © Copyright AzulSystems 2016 Multi-Sort (The Bad) patientRecords.stream() .filter(this::isValidRecord) .sorted(comparing(Patient::getName)) .sorted(comparing(Patient::getPhysician)) .sorted(comparing(Patient::getMedication)) .collect(toList());
  • 22.
    © Copyright AzulSystems 2016 Multi-Sort (The Good) patientRecords.stream() .filter(this::isValidRecord) .sorted(comparing(Patient::getMedication) .thenComparing(comparing(Patient::getPhysician)) .thenComparing(comparing(Patient::getName))) .collect(toList());
  • 23.
    © Copyright AzulSystems 2016 Imperative Streams LongAdder newMethodCount = new LongAdder(); map.get(class).stream() .forEach(method -> { output.println(val); if (isNewMethod(class, method)) newMethodCount.increment(); });
  • 24.
    © Copyright AzulSystems 2016 A Bit More Functional int count = functionalParameterMethodMap.get(c).stream() .mapToInt(m -> { int newMethod = 0; output.println(m); if (isNewMethod(c, m)) newMethod = 1; return newMethod }) .sum(); There is still state being modified in the Lambda
  • 25.
    © Copyright AzulSystems 2016 Almost Functional (But Not Quite) int count = functionalParameterMethodMap.get(nameOfClass) .stream() .peek(method -> output.println(method)) .mapToInt(m -> isNewMethod(nameOfClass, m) ? 1 : 0) .sum(); Strictly speaking printing is a side effect, which is not purely functional
  • 26.
    © Copyright AzulSystems 2016 Conclusions  Streams are powerful – With great power comes great responsibility  Try not to think imperatively – forEach is good, but not for each (and every) situation  Part of streams power is clarity of code – That doesn’t mean as few lines as possible!  Parallel does not guarantee faster 26
  • 27.
    © Copyright AzulSystems 2016 © Copyright Azul Systems 2015 @speakjava Streams in JDK 8: The Good, The Bad & The Ugly Simon Ritter Deputy CTO, Azul Systems 27