Craig Larman
Before we go into details ... Explain quote What does he really mean: idioms and technique are what is important.Adopt-a-JSR
Community involvement and community driven standards. Poll if people have heard of it?Concrete Examples focus discussion
A lot of the mistakes people made were similar similar to the mistakes I made, know several java champs similar mistake Conclusion: other people not at hackdays "We're not so different you and I"button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("button clicked"); } });existing code as data solution aim: register a callback for when someone clicks a button familiar with anonymous inner classes bulky and verbose
button.addActionListener( ? );outer method call is fine issue is what goes in the middle, something that represents an action.
button.addActionListener(event );well we know the that we're passing in something that gets given an event as a parameter
button.addActionListener(event -> System.out.println("button clicked") );previously required the type of the event can be inferred from context already in java 7: diamond operator
Runnable helloWorld = () -> System.out.println("Hello World");also possible to infer the type from an assignment no arguments variant of a lambda
String name = getUserName(); button.addActionListener(event -> System.out.println("hi " + name) );improvement over final variable capturing non-final variables that are 'effectively final'.
public interface ActionListener extends EventListener { public void actionPerformed(ActionEvent event); }here's the example method from earlier single abstract method
int count = 0; for (Artist artist : artists) { if (artist.isFrom("London")) { count++; } }familiar concept the control flow is driven by the code performing the operation can't do parallel iteration
artists.stream() .filter(artist -> artist.isFrom("London")) .count();same code rewritten to use internal iteraion there's no visible iteration! iteration is delegated to the stream this is why its an inversion of control: stream knows how to iterate effectively. you may call into streams, but they call you back, which lets them control the threading model.
List<String> collected = Stream.of("a", "b", "hello") .map(string -> string.toUpperCase()) .collect(toList()); assertEquals(asList("A", "B", "HELLO"), collected);in code Explain Stream.of explain general form point out the toUpperCase()
int sum = Stream.of(1, 2, 3, 4) .reduce(0, (acc, x) -> acc + x); assertEquals(10, sum);remember to mention the sum()
List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1") .filter(value -> isDigit(value.charAt(0))) .collect(toList()); assertEquals(asList("1abc"), beginningWithNumbers);retain only strings whose first character is a digit.
for a given an album, find the nationality of every band playing on that album
go through a worked example of how to put together a pipeline of stream operations.List<String> origins = album.getMusicians() .filter(artist -> artist.getName().startsWith("The")) .map(artist -> artist.getNationality()) .collect(toList());make joke about bands starting with the
str -> str.length String::length x -> foo.bar(x) foo::bar str -> new Name(str) Name::newone more note about introductory stuff
// Originally invalid Stream.of(1, 2, 3) .forEach(x -> System.out.println(x)); // Required extra ; Stream.of(1, 2, 3) .forEach(x -> System.out.println(x););Tripped up loads of people Incredibly confusing compiler message Issue was that println returned void and was a statement Need a ; at the end of statements thankfully fixed after being reported
Different reactions depending on whether something is presented as a loss or a gain.
Basic Psychology "Prospect theory shows that a loss is more significant than the equivalent gain" Coming from Scala: you might expect tuples and higher kinded types Coming from Java 7 you're happy with what's provided Also results in the general reaction from Scala devs that they aren't coming backList<String> origins = album.getMusicians() .filter(artist -> artist.getName().startsWith("The")) .map(artist -> artist.getNationality()) .collect(toList());Recall from earlier
album.getMusicians() .filter(artist -> artist.getName().startsWith("The")) .map(artist -> artist.getNationality()) // What's happened? .collect(toList());does nothing Streams are really builders for collections people not at all understanding eager vs lazy was a common mistake nothing is constructed untill the last call just building up a recipe key trick: anything that returns a Stream is lazy
Maybe ...
list.stream() .map(x -> 1.0 / Math.ceil(1 + Math.pow(x) + Math.atan2(y, x))) .collect(toList());Issue is that lambda expressions aren't reference-able. Say your lambda expression interacts with a database: how do you mock it? Anyone spot the subtle bug? atan2(0,0) is mathematically undefined, Java gives an answer NaN return, +ve/-ve 0, 10 infinity related special cases
double complexFunction(double x) { return 1.0 / Math.ceil(1 + Math.pow(x) + Math.atan2(0, x)); } list.stream() .map(this::complexFunction) .collect(toList());extract your lambda out to a function if its complex. Still get the code reuse and stream functionality through method references remember to explain what a method reference is. Method references really help testability for things like this.
// Streams list.stream() .filter(filteringFunction) .map(mappingFunction) .collect(toList()); // Ye olde for loop List<Bar> bars = new ArrayList<>(); for (Foo element : list) { if (filteringFunction(element) { Bar result = mappingFunction(element); bars.add(result); } }Issue is understanding intermediate values.
list.stream() .filter(filteringFunction) .peek(e -> System.out.println("Filtered value: " + e)); .map(mappingFunction) .map(e -> e); .collect(toList());could also be logging output or debugger breakpoint. similar to the unix tee program. Hands up.
Comparator<String> comparator = comparing(String::length); Comparator<String> comparator = comparing(str -> str.length);Comparator is a functional interface. provides some static methods for comparing by keys explanatory example
java: reference to comparing is ambiguous both method <T>comparing(java.util.function.ToIntFunction< ? super T>) in java.util.Comparator and method <T,U>comparing(java.util.function.Function< ? super T,? extends U>) in java.util.Comparator matchOverload resolution failure. current state of play, but not likely to have a language fix may change libraries
// Generic object variant public static <T, U extends Comparable< ? super U>> Comparator<T> comparing(Function< ? super T, ? extends U> keyExtractor) // Specialised primitive variant public static <T> Comparator<T> comparing(ToIntFunction< ? super T> keyExtractor)overloads are an issue explain primitive specialised variant overloads + type inference can be a bad combo, quite unusual recommendation to library writers: don't overload on functional interfaces
Thinking in terms of the input to output relationship and not a sequence of steps
What is it? Functional means different things to different communities clojure: immutability, haskell: purity + monads, scala: a series of nouns which can be used for trolling. Biggest issue that I saw: easy to develop and understand, but takes a bit of practise. In the same way that patterns such as SOLID exist in OOP and take a while to develop.List<Integer> numbers = Arrays.asList(1, 2, 3); numbers.forEach(x -> { System.out.println(x); });Pretty consistent in understanding internal vs external iteration. not initially trying higher order functions.
Eg: capture non-final local variables
A lot of people want features which aren’t in the direction of java 8 doesn't make parallelism easier.Count the number of instances of each word in a document.
Example problem from the devoxx uk hackday nominally simple problem Required understanding of the collectors abstraction. Given a reader as input.reader.lines() .flatMap(s -> s.splitAsStream(" ")) .collect(groupingBy(s -> s, counting()));Explain code explain the breakdown emphasize the downstream collectors
reader.lines() .flatMap(s -> Stream.of(s.split(" "))) .collect(groupingBy(s -> s, reducing(s -> 1, Integer::sum))); // Map entries for "dad" // [ "dad", "dad", "dad" ] => [1, 1, 1] => 3Counting not originally there. Subsequently added. People not only failed to get there, but also failed to understand the solution when presented
Map<String, List<String>> initial = br.lines() .flatMap(s -> Arrays.stream(s.split(" "))) .collect(groupingBy(s -> s)); Map<Map.Entry<String, Integer>, Integer> freq1 = initial .entrySet().stream() .map(entry -> new AbstractMap.SimpleImmutableEntry<String, Integer>(entry.getKey(), entry.getValue().size())) .collect(Collectors.toMap(entry -> entry.getValue()));nearly right to begin with building the map up doesn't realise about downstream collectors
Supplier<HashMap<String, Integer>> supplier = () -> new HashMap<String, Integer>(); BiConsumer<HashMap<String, Integer>, Map.Entry<String, Integer>> accum = (HashMap<String, Integer> result, Map.Entry<String, Integer> entry) -> result.put(entry.getKey(), entry.getValue()); BiConsumer<HashMap<String, Integer>, HashMap<String, Integer>> merger = HashMap::putAll; Map<String, Integer> freq2 = initial.entrySet().stream() .map(entry -> new AbstractMap.SimpleImmutableEntry<String, Integer>(entry.getKey(), entry.getValue().size())) .collect(supplier, accum, merger);downhill from there, don't worry about understanding this code make point of weird approach if things start looking like this also, choose first steps wisely sometimes indicates a lack of library knowledge you'll write something like this, treat it as a learning experience not "FP is unreadable."
@RichardWarburto